2019-05-03 06:17:27 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-05-26 08:40:33 +08:00
|
|
|
class PostMover
|
|
|
|
attr_reader :original_topic, :destination_topic, :user, :post_ids
|
|
|
|
|
2013-07-03 03:47:15 +08:00
|
|
|
def self.move_types
|
|
|
|
@move_types ||= Enum.new(:new_topic, :existing_topic)
|
|
|
|
end
|
|
|
|
|
2024-11-27 03:30:25 +08:00
|
|
|
# options:
|
|
|
|
# freeze_original: :boolean - if true, the original topic will be frozen but not deleted and posts will be "copied" to topic
|
|
|
|
def initialize(original_topic, user, post_ids, move_to_pm: false, options: {})
|
2013-05-26 08:40:33 +08:00
|
|
|
@original_topic = original_topic
|
2024-12-06 07:10:32 +08:00
|
|
|
@original_topic_title = original_topic.title
|
2013-05-26 08:40:33 +08:00
|
|
|
@user = user
|
|
|
|
@post_ids = post_ids
|
2024-12-06 07:10:32 +08:00
|
|
|
# For now we store a copy of post_ids. If `freeze_original` is present, we will have new post_ids.
|
|
|
|
# When we create the new posts, we will pluck out post_ids out of this and replace with updated ids.
|
|
|
|
@post_ids_after_move = post_ids
|
2018-12-31 19:47:22 +08:00
|
|
|
@move_to_pm = move_to_pm
|
2024-11-27 03:30:25 +08:00
|
|
|
@options = options
|
2013-05-26 08:40:33 +08:00
|
|
|
end
|
|
|
|
|
2023-05-26 02:38:34 +08:00
|
|
|
def to_topic(id, participants: nil, chronological_order: false)
|
2013-07-03 03:47:15 +08:00
|
|
|
@move_type = PostMover.move_types[:existing_topic]
|
2024-11-13 04:35:20 +08:00
|
|
|
@creating_new_topic = false
|
2023-05-26 02:38:34 +08:00
|
|
|
@chronological_order = chronological_order
|
2013-07-03 03:47:15 +08:00
|
|
|
|
2018-12-31 19:47:22 +08:00
|
|
|
topic = Topic.find_by_id(id)
|
2019-03-15 01:37:51 +08:00
|
|
|
if topic.archetype != @original_topic.archetype &&
|
|
|
|
[@original_topic.archetype, topic.archetype].include?(Archetype.private_message)
|
|
|
|
raise Discourse::InvalidParameters
|
|
|
|
end
|
2018-12-31 19:47:22 +08:00
|
|
|
|
|
|
|
Topic.transaction { move_posts_to topic }
|
|
|
|
add_allowed_users(participants) if participants.present? && @move_to_pm
|
2019-07-23 01:02:21 +08:00
|
|
|
enqueue_jobs(topic)
|
2018-12-31 19:47:22 +08:00
|
|
|
topic
|
2013-05-26 08:40:33 +08:00
|
|
|
end
|
|
|
|
|
2018-07-07 00:21:32 +08:00
|
|
|
def to_new_topic(title, category_id = nil, tags = nil)
|
2013-07-03 03:47:15 +08:00
|
|
|
@move_type = PostMover.move_types[:new_topic]
|
2024-11-13 04:35:20 +08:00
|
|
|
@creating_new_topic = true
|
2013-07-03 03:47:15 +08:00
|
|
|
|
2015-11-07 12:17:47 +08:00
|
|
|
post = Post.find_by(id: post_ids.first)
|
|
|
|
raise Discourse::InvalidParameters unless post
|
2018-12-31 19:47:22 +08:00
|
|
|
archetype = @move_to_pm ? Archetype.private_message : Archetype.default
|
2015-11-07 12:17:47 +08:00
|
|
|
|
2019-07-23 01:02:21 +08:00
|
|
|
topic =
|
|
|
|
Topic.transaction do
|
2018-07-07 00:21:32 +08:00
|
|
|
new_topic =
|
|
|
|
Topic.create!(
|
2015-11-07 12:17:47 +08:00
|
|
|
user: post.user,
|
2013-05-26 08:40:33 +08:00
|
|
|
title: title,
|
2015-11-07 12:17:47 +08:00
|
|
|
category_id: category_id,
|
2018-12-31 19:47:22 +08:00
|
|
|
created_at: post.created_at,
|
|
|
|
archetype: archetype,
|
2013-05-26 08:40:33 +08:00
|
|
|
)
|
2018-07-07 00:21:32 +08:00
|
|
|
DiscourseTagging.tag_topic_by_names(new_topic, Guardian.new(user), tags)
|
|
|
|
move_posts_to new_topic
|
2018-07-20 15:13:27 +08:00
|
|
|
watch_new_topic
|
2020-08-13 14:00:14 +08:00
|
|
|
update_topic_excerpt new_topic
|
2018-07-18 20:23:32 +08:00
|
|
|
new_topic
|
2013-05-26 08:40:33 +08:00
|
|
|
end
|
2019-07-23 01:02:21 +08:00
|
|
|
enqueue_jobs(topic)
|
|
|
|
topic
|
2013-05-26 08:40:33 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2020-08-13 14:00:14 +08:00
|
|
|
def update_topic_excerpt(topic)
|
|
|
|
topic.update_excerpt(topic.first_post.excerpt_for_topic)
|
|
|
|
end
|
|
|
|
|
2013-05-26 08:40:33 +08:00
|
|
|
def move_posts_to(topic)
|
|
|
|
Guardian.new(user).ensure_can_see! topic
|
|
|
|
@destination_topic = topic
|
|
|
|
|
2021-06-30 22:28:18 +08:00
|
|
|
# when a topic contains some posts after moving posts to another topic we shouldn't close it
|
|
|
|
# two types of posts should prevent a topic from closing:
|
|
|
|
# 1. regular posts
|
|
|
|
# 2. almost all whispers
|
|
|
|
# we should only exclude whispers with action_code: 'split_topic'
|
|
|
|
# because we use such whispers as a small-action posts when moving posts to the secret message
|
|
|
|
# (in this case we don't want everyone to see that posts were moved, that's why we use whispers)
|
|
|
|
original_topic_posts_count =
|
|
|
|
@original_topic
|
|
|
|
.posts
|
|
|
|
.where(
|
|
|
|
"post_type = ? or (post_type = ? and action_code != 'split_topic')",
|
|
|
|
Post.types[:regular],
|
|
|
|
Post.types[:whisper],
|
|
|
|
)
|
|
|
|
.count
|
|
|
|
moving_all_posts = original_topic_posts_count == posts.length
|
|
|
|
|
2023-05-26 02:38:34 +08:00
|
|
|
@first_post_number_moved =
|
|
|
|
posts.first.is_first_post? ? posts[1]&.post_number : posts.first.post_number
|
|
|
|
|
2024-11-27 03:30:25 +08:00
|
|
|
if @options[:freeze_original] # in this case we need to add the moderator post after the last copied post
|
|
|
|
from_posts = @original_topic.ordered_posts.where("post_number > ?", posts.last.post_number)
|
|
|
|
shift_post_numbers(from_posts) if !moving_all_posts
|
|
|
|
|
|
|
|
@first_post_number_moved = posts.last.post_number + 1
|
|
|
|
end
|
|
|
|
|
2013-05-26 08:40:33 +08:00
|
|
|
move_each_post
|
2023-05-26 02:38:34 +08:00
|
|
|
handle_moved_references
|
|
|
|
|
2020-12-04 05:43:42 +08:00
|
|
|
create_moderator_post_in_original_topic
|
2013-05-26 08:40:33 +08:00
|
|
|
update_statistics
|
2013-07-17 14:40:15 +08:00
|
|
|
update_user_actions
|
2018-02-02 22:00:52 +08:00
|
|
|
update_last_post_stats
|
2020-01-23 10:01:10 +08:00
|
|
|
update_upload_security_status
|
2021-03-29 09:25:48 +08:00
|
|
|
update_bookmarks
|
2013-07-17 14:40:15 +08:00
|
|
|
|
2021-05-28 21:33:10 +08:00
|
|
|
close_topic_and_schedule_deletion if moving_all_posts
|
2015-12-30 05:01:49 +08:00
|
|
|
|
2015-04-06 15:27:05 +08:00
|
|
|
destination_topic.reload
|
2024-12-04 03:16:20 +08:00
|
|
|
DiscourseEvent.trigger(
|
|
|
|
:posts_moved,
|
|
|
|
destination_topic_id: destination_topic.id,
|
|
|
|
original_topic_id: original_topic.id,
|
|
|
|
)
|
2013-07-17 14:40:15 +08:00
|
|
|
destination_topic
|
2019-08-01 23:47:19 +08:00
|
|
|
end
|
|
|
|
|
2023-05-26 02:38:34 +08:00
|
|
|
def handle_moved_references
|
|
|
|
move_incoming_emails
|
|
|
|
move_notifications
|
|
|
|
update_reply_counts
|
|
|
|
update_quotes
|
|
|
|
move_first_post_replies
|
|
|
|
delete_post_replies
|
|
|
|
copy_shifted_post_timings_to_temp
|
|
|
|
delete_invalid_post_timings
|
|
|
|
copy_shifted_post_timings_from_temp
|
|
|
|
move_post_timings
|
|
|
|
copy_first_post_timings
|
|
|
|
copy_topic_users
|
|
|
|
end
|
|
|
|
|
2013-05-26 08:40:33 +08:00
|
|
|
def move_each_post
|
2023-05-26 02:38:34 +08:00
|
|
|
if @chronological_order
|
|
|
|
move_each_post_chronological
|
|
|
|
else
|
|
|
|
move_each_post_sequential
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def move_each_post_sequential
|
2014-08-21 02:14:56 +08:00
|
|
|
max_post_number = destination_topic.max_post_number + 1
|
|
|
|
|
2019-07-23 01:02:21 +08:00
|
|
|
@post_creator = nil
|
2014-08-21 02:14:56 +08:00
|
|
|
@move_map = {}
|
|
|
|
@reply_count = {}
|
|
|
|
posts.each_with_index do |post, offset|
|
2019-09-07 02:48:57 +08:00
|
|
|
@move_map[post.post_number] = offset + max_post_number
|
|
|
|
|
2014-08-21 02:14:56 +08:00
|
|
|
if post.reply_to_post_number.present?
|
|
|
|
@reply_count[post.reply_to_post_number] = (@reply_count[post.reply_to_post_number] || 0) + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
posts.each do |post|
|
2023-05-26 02:38:34 +08:00
|
|
|
metadata = movement_metadata(post, new_post_number: @move_map[post.post_number])
|
2019-08-01 23:47:19 +08:00
|
|
|
new_post = post.is_first_post? ? create_first_post(post) : move(post)
|
|
|
|
|
|
|
|
store_movement(metadata, new_post)
|
2019-02-18 09:59:50 +08:00
|
|
|
|
|
|
|
if @move_to_pm && !destination_topic.topic_allowed_users.exists?(user_id: post.user_id)
|
2019-02-11 19:34:21 +08:00
|
|
|
destination_topic.topic_allowed_users.create!(user_id: post.user_id)
|
2018-12-31 19:47:22 +08:00
|
|
|
end
|
2013-05-26 08:40:33 +08:00
|
|
|
end
|
2023-05-26 02:38:34 +08:00
|
|
|
end
|
2016-07-13 23:34:21 +08:00
|
|
|
|
2023-05-26 02:38:34 +08:00
|
|
|
def move_each_post_chronological
|
|
|
|
destination_posts = destination_topic.ordered_posts.with_deleted
|
|
|
|
|
|
|
|
# drops posts from destination_topic until it finds one that was created after posts.first
|
|
|
|
min_created_at = posts.first.created_at
|
|
|
|
moved_posts = destination_posts.drop_while { |post| post.created_at <= min_created_at }
|
|
|
|
|
|
|
|
# if no post in destination_topic was created after posts.first it's equal to sequential
|
|
|
|
if moved_posts.empty?
|
|
|
|
initial_post_number = destination_topic.max_post_number + 1
|
|
|
|
else
|
|
|
|
initial_post_number = moved_posts.first.post_number
|
|
|
|
end
|
|
|
|
|
|
|
|
last_index = 0
|
|
|
|
posts.each do |post|
|
|
|
|
while last_index < moved_posts.length && moved_posts[last_index].created_at <= post.created_at
|
|
|
|
last_index += 1
|
|
|
|
end
|
|
|
|
|
|
|
|
moved_posts.insert(last_index, post)
|
|
|
|
end
|
|
|
|
|
|
|
|
@post_creator = nil
|
|
|
|
@move_map = {}
|
|
|
|
@shift_map = {}
|
|
|
|
@reply_count = {}
|
|
|
|
next_post_number = initial_post_number
|
|
|
|
moved_posts.each do |post|
|
|
|
|
if post.topic_id == destination_topic.id
|
|
|
|
# avoid shifting to a lower post number
|
|
|
|
next_post_number = post.post_number if post.post_number > next_post_number
|
|
|
|
|
|
|
|
@shift_map[post.post_number] = next_post_number
|
|
|
|
else
|
|
|
|
@move_map[post.post_number] = next_post_number
|
|
|
|
|
|
|
|
if post.reply_to_post_number.present?
|
|
|
|
@reply_count[post.reply_to_post_number] = (@reply_count[post.reply_to_post_number] || 0) +
|
|
|
|
1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
next_post_number += 1
|
|
|
|
end
|
|
|
|
|
|
|
|
moved_posts.reverse_each do |post|
|
|
|
|
if post.topic_id == destination_topic.id
|
|
|
|
metadata = movement_metadata(post, new_post_number: @shift_map[post.post_number])
|
|
|
|
new_post = move_same_topic(post)
|
|
|
|
else
|
|
|
|
metadata = movement_metadata(post, new_post_number: @move_map[post.post_number])
|
|
|
|
new_post = post.is_first_post? ? create_first_post(post) : move(post)
|
|
|
|
|
|
|
|
if @move_to_pm && !destination_topic.topic_allowed_users.exists?(user_id: post.user_id)
|
|
|
|
destination_topic.topic_allowed_users.create!(user_id: post.user_id)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
store_movement(metadata, new_post)
|
|
|
|
end
|
|
|
|
|
|
|
|
# change topic owner if there's a new first post
|
|
|
|
destination_topic.update_column(:user_id, posts.first.user_id) if initial_post_number == 1
|
2013-05-26 08:40:33 +08:00
|
|
|
end
|
|
|
|
|
2014-08-21 02:14:56 +08:00
|
|
|
def create_first_post(post)
|
2019-07-23 01:02:21 +08:00
|
|
|
@post_creator =
|
|
|
|
PostCreator.new(
|
2013-05-26 08:40:33 +08:00
|
|
|
post.user,
|
|
|
|
raw: post.raw,
|
|
|
|
topic_id: destination_topic.id,
|
2016-07-06 04:48:14 +08:00
|
|
|
acting_user: user,
|
2017-11-24 18:13:19 +08:00
|
|
|
cook_method: post.cook_method,
|
|
|
|
via_email: post.via_email,
|
|
|
|
raw_email: post.raw_email,
|
2017-07-03 23:51:20 +08:00
|
|
|
skip_validations: true,
|
2018-02-08 19:55:32 +08:00
|
|
|
created_at: post.created_at,
|
2019-07-23 01:02:21 +08:00
|
|
|
guardian: Guardian.new(user),
|
|
|
|
skip_jobs: true,
|
2013-05-26 08:40:33 +08:00
|
|
|
)
|
2022-02-02 13:22:52 +08:00
|
|
|
new_post = @post_creator.create!
|
2017-02-10 22:35:04 +08:00
|
|
|
|
2017-11-24 18:13:19 +08:00
|
|
|
move_email_logs(post, new_post)
|
|
|
|
|
2017-02-10 22:35:04 +08:00
|
|
|
PostAction.copy(post, new_post)
|
2023-05-26 02:38:34 +08:00
|
|
|
|
2023-08-04 09:04:35 +08:00
|
|
|
PostRevision.copy(post, new_post)
|
|
|
|
|
|
|
|
attrs_to_update = {
|
|
|
|
reply_count: @reply_count[1] || 0,
|
|
|
|
version: post.version,
|
|
|
|
public_version: post.public_version,
|
|
|
|
}
|
2023-05-26 02:38:34 +08:00
|
|
|
|
|
|
|
if new_post.post_number != @move_map[post.post_number]
|
|
|
|
attrs_to_update[:post_number] = @move_map[post.post_number]
|
|
|
|
attrs_to_update[:sort_order] = @move_map[post.post_number]
|
|
|
|
end
|
|
|
|
|
|
|
|
new_post.update_columns(attrs_to_update)
|
2017-06-07 18:04:48 +08:00
|
|
|
new_post.custom_fields = post.custom_fields
|
|
|
|
new_post.save_custom_fields
|
2017-08-12 10:12:09 +08:00
|
|
|
|
2024-12-06 23:50:53 +08:00
|
|
|
# When freezing original, ensure the notification generated points
|
|
|
|
# to the newly created post, not the old OP
|
|
|
|
if @options[:freeze_original]
|
|
|
|
@post_ids_after_move =
|
|
|
|
@post_ids_after_move.map { |post_id| post_id == post.id ? new_post.id : post_id }
|
|
|
|
end
|
|
|
|
|
2023-10-20 17:56:50 +08:00
|
|
|
DiscourseEvent.trigger(:first_post_moved, new_post, post)
|
2017-08-12 10:12:09 +08:00
|
|
|
DiscourseEvent.trigger(:post_moved, new_post, original_topic.id)
|
|
|
|
|
2021-09-15 08:16:54 +08:00
|
|
|
# we don't want to keep the old topic's OP bookmarked when we are
|
|
|
|
# moving it into a new topic
|
2022-05-23 08:07:15 +08:00
|
|
|
Bookmark.where(bookmarkable: post).update_all(bookmarkable_id: new_post.id)
|
2021-09-15 08:16:54 +08:00
|
|
|
|
2017-06-07 18:04:48 +08:00
|
|
|
new_post
|
2013-05-26 08:40:33 +08:00
|
|
|
end
|
|
|
|
|
2014-08-21 02:14:56 +08:00
|
|
|
def move(post)
|
2017-06-02 06:00:04 +08:00
|
|
|
update = {
|
|
|
|
reply_count: @reply_count[post.post_number] || 0,
|
|
|
|
post_number: @move_map[post.post_number],
|
|
|
|
reply_to_post_number: @move_map[post.reply_to_post_number],
|
|
|
|
topic_id: destination_topic.id,
|
2021-10-12 14:31:18 +08:00
|
|
|
sort_order: @move_map[post.post_number],
|
|
|
|
baked_version: nil,
|
2017-06-02 06:00:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
update[:reply_to_user_id] = nil unless @move_map[post.reply_to_post_number]
|
|
|
|
|
2024-11-27 03:30:25 +08:00
|
|
|
moved_post =
|
|
|
|
if @options[:freeze_original]
|
|
|
|
post.dup
|
|
|
|
else
|
|
|
|
post
|
|
|
|
end
|
|
|
|
|
|
|
|
moved_post.attributes = update
|
2024-12-03 02:48:13 +08:00
|
|
|
moved_post.disable_rate_limits! if @options[:freeze_original]
|
2024-11-27 03:30:25 +08:00
|
|
|
moved_post.save(validate: false)
|
2017-08-12 10:12:09 +08:00
|
|
|
|
2024-12-06 07:10:32 +08:00
|
|
|
if moved_post.id != post.id
|
|
|
|
@post_ids_after_move =
|
|
|
|
@post_ids_after_move.map { |post_id| post_id == post.id ? moved_post.id : post_id }
|
|
|
|
end
|
|
|
|
|
2024-11-27 03:30:25 +08:00
|
|
|
DiscourseEvent.trigger(:post_moved, moved_post, original_topic.id)
|
2013-07-03 04:42:25 +08:00
|
|
|
|
|
|
|
# Move any links from the post to the new topic
|
2024-11-27 03:30:25 +08:00
|
|
|
moved_post.topic_links.update_all(topic_id: destination_topic.id)
|
2019-08-01 23:47:19 +08:00
|
|
|
|
2024-11-27 03:30:25 +08:00
|
|
|
moved_post
|
2019-08-01 23:47:19 +08:00
|
|
|
end
|
|
|
|
|
2023-05-26 02:38:34 +08:00
|
|
|
def move_same_topic(post)
|
|
|
|
update = {
|
|
|
|
post_number: @shift_map[post.post_number],
|
|
|
|
sort_order: @shift_map[post.post_number],
|
|
|
|
baked_version: nil,
|
|
|
|
}
|
|
|
|
|
|
|
|
if @shift_map[post.reply_to_post_number]
|
|
|
|
update[:reply_to_post_number] = @shift_map[post.reply_to_post_number]
|
|
|
|
end
|
|
|
|
|
|
|
|
post.attributes = update
|
|
|
|
post.save(validate: false)
|
|
|
|
|
|
|
|
post
|
|
|
|
end
|
|
|
|
|
|
|
|
def movement_metadata(post, new_post_number: nil)
|
2019-08-01 23:47:19 +08:00
|
|
|
{
|
|
|
|
old_topic_id: post.topic_id,
|
|
|
|
old_post_id: post.id,
|
|
|
|
old_post_number: post.post_number,
|
2024-12-06 07:10:32 +08:00
|
|
|
post_user_id: post.user_id,
|
2019-08-01 23:47:19 +08:00
|
|
|
new_topic_id: destination_topic.id,
|
2023-05-26 02:38:34 +08:00
|
|
|
new_post_number: new_post_number,
|
2019-08-01 23:47:19 +08:00
|
|
|
new_topic_title: destination_topic.title,
|
|
|
|
}
|
2013-05-26 08:40:33 +08:00
|
|
|
end
|
|
|
|
|
2019-08-01 23:47:19 +08:00
|
|
|
def store_movement(metadata, new_post)
|
|
|
|
metadata[:new_post_id] = new_post.id
|
2024-11-13 04:35:20 +08:00
|
|
|
metadata[:now] = Time.zone.now
|
|
|
|
metadata[:created_new_topic] = @creating_new_topic
|
2024-12-06 07:10:32 +08:00
|
|
|
metadata[:old_topic_title] = @original_topic_title
|
|
|
|
metadata[:user_id] = @user.id
|
2017-11-24 18:13:19 +08:00
|
|
|
|
2019-08-01 23:47:19 +08:00
|
|
|
DB.exec(<<~SQL, metadata)
|
2024-12-06 07:10:32 +08:00
|
|
|
INSERT INTO moved_posts(old_topic_id, old_topic_title, old_post_id, old_post_number, post_user_id, user_id, new_topic_id, new_topic_title, new_post_id, new_post_number, created_new_topic, created_at, updated_at)
|
|
|
|
VALUES (:old_topic_id, :old_topic_title, :old_post_id, :old_post_number, :post_user_id, :user_id, :new_topic_id, :new_topic_title, :new_post_id, :new_post_number, :created_new_topic, :now, :now)
|
2019-08-01 23:47:19 +08:00
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
2024-11-27 03:30:25 +08:00
|
|
|
def shift_post_numbers(from_posts)
|
|
|
|
from_posts.reverse_each { |post| post.update_columns(post_number: post.post_number + 1) }
|
|
|
|
end
|
|
|
|
|
2019-08-01 23:47:19 +08:00
|
|
|
def move_incoming_emails
|
|
|
|
DB.exec <<~SQL
|
|
|
|
UPDATE incoming_emails ie
|
|
|
|
SET topic_id = mp.new_topic_id,
|
|
|
|
post_id = mp.new_post_id
|
|
|
|
FROM moved_posts mp
|
|
|
|
WHERE ie.topic_id = mp.old_topic_id AND ie.post_id = mp.old_post_id
|
2023-05-26 02:38:34 +08:00
|
|
|
AND mp.old_topic_id <> mp.new_topic_id
|
2019-08-01 23:47:19 +08:00
|
|
|
SQL
|
2017-11-24 18:13:19 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def move_email_logs(old_post, new_post)
|
2018-07-18 13:55:35 +08:00
|
|
|
EmailLog.where(post_id: old_post.id).update_all(post_id: new_post.id)
|
2017-11-24 18:13:19 +08:00
|
|
|
end
|
|
|
|
|
2019-08-01 23:47:19 +08:00
|
|
|
def move_notifications
|
|
|
|
DB.exec <<~SQL
|
|
|
|
UPDATE notifications n
|
|
|
|
SET topic_id = mp.new_topic_id,
|
|
|
|
post_number = mp.new_post_number,
|
2019-07-21 03:36:18 +08:00
|
|
|
data = (data :: JSONB ||
|
|
|
|
jsonb_strip_nulls(
|
|
|
|
jsonb_build_object(
|
|
|
|
'topic_title', CASE WHEN data :: JSONB ->> 'topic_title' IS NULL
|
|
|
|
THEN NULL
|
2019-08-01 23:47:19 +08:00
|
|
|
ELSE mp.new_topic_title END
|
2019-07-21 03:36:18 +08:00
|
|
|
)
|
|
|
|
)) :: JSON
|
2019-08-01 23:47:19 +08:00
|
|
|
FROM moved_posts mp
|
|
|
|
WHERE n.topic_id = mp.old_topic_id AND n.post_number = mp.old_post_number
|
2019-08-13 04:59:43 +08:00
|
|
|
AND n.notification_type <> #{Notification.types[:watching_first_post]}
|
2019-08-01 23:47:19 +08:00
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
|
|
|
def update_reply_counts
|
|
|
|
DB.exec <<~SQL
|
|
|
|
UPDATE posts p
|
|
|
|
SET reply_count = GREATEST(0, reply_count - x.moved_reply_count)
|
|
|
|
FROM (
|
|
|
|
SELECT r.post_id, mp.new_topic_id, COUNT(1) AS moved_reply_count
|
|
|
|
FROM moved_posts mp
|
2020-01-18 00:24:49 +08:00
|
|
|
JOIN post_replies r ON (mp.old_post_id = r.reply_post_id)
|
2019-08-01 23:47:19 +08:00
|
|
|
GROUP BY r.post_id, mp.new_topic_id
|
|
|
|
) x
|
|
|
|
WHERE x.post_id = p.id AND x.new_topic_id <> p.topic_id
|
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
2019-11-12 21:16:39 +08:00
|
|
|
def update_quotes
|
|
|
|
DB.exec <<~SQL
|
|
|
|
UPDATE posts p
|
|
|
|
SET raw = REPLACE(p.raw,
|
|
|
|
', post:' || mp.old_post_number || ', topic:' || mp.old_topic_id,
|
|
|
|
', post:' || mp.new_post_number || ', topic:' || mp.new_topic_id),
|
|
|
|
baked_version = NULL
|
|
|
|
FROM moved_posts mp, quoted_posts qp
|
|
|
|
WHERE p.id = qp.post_id AND mp.old_post_id = qp.quoted_post_id
|
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
2019-08-02 04:07:21 +08:00
|
|
|
def move_first_post_replies
|
|
|
|
DB.exec <<~SQL
|
|
|
|
UPDATE post_replies pr
|
|
|
|
SET post_id = mp.new_post_id
|
2019-09-07 02:48:57 +08:00
|
|
|
FROM moved_posts mp
|
2019-08-02 04:07:21 +08:00
|
|
|
WHERE mp.old_post_id <> mp.new_post_id AND pr.post_id = mp.old_post_id AND
|
2020-01-18 00:24:49 +08:00
|
|
|
EXISTS (SELECT 1 FROM moved_posts mr WHERE mr.new_post_id = pr.reply_post_id)
|
2019-08-02 04:07:21 +08:00
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
2019-08-01 23:47:19 +08:00
|
|
|
def delete_post_replies
|
|
|
|
DB.exec <<~SQL
|
2020-02-04 07:26:13 +08:00
|
|
|
DELETE FROM post_replies pr USING moved_posts mp
|
|
|
|
WHERE (SELECT topic_id FROM posts WHERE id = pr.post_id) <>
|
|
|
|
(SELECT topic_id FROM posts WHERE id = pr.reply_post_id)
|
|
|
|
AND (pr.reply_post_id = mp.old_post_id OR pr.post_id = mp.old_post_id)
|
2019-07-21 03:36:18 +08:00
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
2023-05-26 02:38:34 +08:00
|
|
|
def copy_shifted_post_timings_to_temp
|
|
|
|
DB.exec("DROP TABLE IF EXISTS temp_post_timings") if Rails.env.test?
|
|
|
|
|
|
|
|
# copy post_timings for shifted posts to a temp table using the new_post_number
|
|
|
|
# they'll be copied back after delete_invalid_post_timings makes room for them
|
|
|
|
DB.exec <<~SQL
|
|
|
|
CREATE TEMPORARY TABLE temp_post_timings ON COMMIT DROP
|
|
|
|
AS (
|
|
|
|
SELECT pt.topic_id, mp.new_post_number as post_number, pt.user_id, pt.msecs
|
|
|
|
FROM post_timings pt
|
|
|
|
JOIN moved_posts mp
|
|
|
|
ON mp.old_topic_id = pt.topic_id
|
|
|
|
AND mp.old_post_number = pt.post_number
|
|
|
|
AND mp.old_topic_id = mp.new_topic_id
|
|
|
|
)
|
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
|
|
|
def copy_shifted_post_timings_from_temp
|
|
|
|
DB.exec <<~SQL
|
|
|
|
INSERT INTO post_timings (topic_id, user_id, post_number, msecs)
|
|
|
|
SELECT topic_id, user_id, post_number, msecs FROM temp_post_timings
|
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
2019-09-07 02:48:57 +08:00
|
|
|
def copy_first_post_timings
|
|
|
|
DB.exec <<~SQL
|
|
|
|
INSERT INTO post_timings (topic_id, user_id, post_number, msecs)
|
|
|
|
SELECT mp.new_topic_id, pt.user_id, mp.new_post_number, pt.msecs
|
|
|
|
FROM post_timings pt
|
|
|
|
JOIN moved_posts mp ON (pt.topic_id = mp.old_topic_id AND pt.post_number = mp.old_post_number)
|
|
|
|
WHERE mp.old_post_id <> mp.new_post_id
|
|
|
|
ON CONFLICT (topic_id, post_number, user_id) DO UPDATE
|
|
|
|
SET msecs = GREATEST(post_timings.msecs, excluded.msecs)
|
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
2019-10-09 02:52:55 +08:00
|
|
|
def delete_invalid_post_timings
|
2021-07-10 01:34:39 +08:00
|
|
|
DB.exec <<~SQL
|
2019-10-09 02:52:55 +08:00
|
|
|
DELETE
|
|
|
|
FROM post_timings pt
|
2021-07-10 01:34:39 +08:00
|
|
|
USING moved_posts mp
|
|
|
|
WHERE pt.topic_id = mp.new_topic_id
|
|
|
|
AND pt.post_number = mp.new_post_number
|
2019-10-09 02:52:55 +08:00
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
2019-09-07 02:48:57 +08:00
|
|
|
def move_post_timings
|
2024-12-10 12:57:28 +08:00
|
|
|
params = { post_ids: @post_ids_after_move }
|
|
|
|
|
|
|
|
DB.exec(<<~SQL, params)
|
2019-09-07 02:48:57 +08:00
|
|
|
UPDATE post_timings pt
|
|
|
|
SET topic_id = mp.new_topic_id,
|
|
|
|
post_number = mp.new_post_number
|
|
|
|
FROM moved_posts mp
|
|
|
|
WHERE pt.topic_id = mp.old_topic_id
|
|
|
|
AND pt.post_number = mp.old_post_number
|
|
|
|
AND mp.old_post_id = mp.new_post_id
|
2023-05-26 02:38:34 +08:00
|
|
|
AND mp.old_topic_id <> mp.new_topic_id
|
2024-12-10 12:57:28 +08:00
|
|
|
AND mp.new_post_id IN (:post_ids)
|
2019-09-07 02:48:57 +08:00
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
|
|
|
def copy_topic_users
|
|
|
|
params = {
|
|
|
|
old_topic_id: original_topic.id,
|
|
|
|
new_topic_id: destination_topic.id,
|
|
|
|
old_highest_post_number: destination_topic.highest_post_number,
|
2020-03-31 13:19:47 +08:00
|
|
|
old_highest_staff_post_number: destination_topic.highest_staff_post_number,
|
2019-09-07 02:48:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
DB.exec(<<~SQL, params)
|
2021-07-05 14:17:31 +08:00
|
|
|
INSERT INTO topic_users(user_id, topic_id, posted, last_read_post_number,
|
2019-09-07 02:48:57 +08:00
|
|
|
last_emailed_post_number, first_visited_at, last_visited_at, notification_level,
|
|
|
|
notifications_changed_at, notifications_reason_id)
|
|
|
|
SELECT tu.user_id,
|
|
|
|
:new_topic_id AS topic_id,
|
2020-03-31 13:19:47 +08:00
|
|
|
EXISTS(
|
|
|
|
SELECT 1
|
|
|
|
FROM posts p
|
|
|
|
WHERE p.topic_id = :new_topic_id
|
|
|
|
AND p.user_id = tu.user_id
|
|
|
|
LIMIT 1
|
|
|
|
) AS posted,
|
2019-10-11 23:31:20 +08:00
|
|
|
(
|
|
|
|
SELECT MAX(lr.new_post_number)
|
|
|
|
FROM moved_posts lr
|
|
|
|
WHERE lr.old_topic_id = tu.topic_id
|
|
|
|
AND lr.old_post_number <= tu.last_read_post_number
|
2023-05-26 02:38:34 +08:00
|
|
|
AND lr.old_topic_id <> lr.new_topic_id
|
2019-10-11 23:31:20 +08:00
|
|
|
) AS last_read_post_number,
|
|
|
|
(
|
|
|
|
SELECT MAX(le.new_post_number)
|
|
|
|
FROM moved_posts le
|
|
|
|
WHERE le.old_topic_id = tu.topic_id
|
|
|
|
AND le.old_post_number <= tu.last_emailed_post_number
|
2023-05-26 02:38:34 +08:00
|
|
|
AND le.old_topic_id <> le.new_topic_id
|
2019-10-11 23:31:20 +08:00
|
|
|
) AS last_emailed_post_number,
|
2019-09-07 02:48:57 +08:00
|
|
|
GREATEST(tu.first_visited_at, t.created_at) AS first_visited_at,
|
|
|
|
GREATEST(tu.last_visited_at, t.created_at) AS last_visited_at,
|
2020-03-31 13:19:47 +08:00
|
|
|
tu.notification_level,
|
2019-09-07 02:48:57 +08:00
|
|
|
tu.notifications_changed_at,
|
|
|
|
tu.notifications_reason_id
|
|
|
|
FROM topic_users tu
|
2019-10-11 23:31:20 +08:00
|
|
|
JOIN topics t ON (t.id = :new_topic_id)
|
2019-09-07 02:48:57 +08:00
|
|
|
WHERE tu.topic_id = :old_topic_id
|
|
|
|
AND GREATEST(
|
|
|
|
tu.last_read_post_number,
|
|
|
|
tu.last_emailed_post_number
|
2023-05-26 02:38:34 +08:00
|
|
|
) >= (SELECT MIN(mp.old_post_number) FROM moved_posts mp WHERE mp.old_topic_id <> mp.new_topic_id)
|
2019-09-07 02:48:57 +08:00
|
|
|
ON CONFLICT (topic_id, user_id) DO UPDATE
|
|
|
|
SET posted = excluded.posted,
|
|
|
|
last_read_post_number = CASE
|
|
|
|
WHEN topic_users.last_read_post_number = :old_highest_staff_post_number OR (
|
|
|
|
:old_highest_post_number < :old_highest_staff_post_number
|
|
|
|
AND topic_users.last_read_post_number = :old_highest_post_number
|
|
|
|
AND NOT EXISTS(SELECT 1
|
|
|
|
FROM users u
|
|
|
|
WHERE u.id = topic_users.user_id
|
|
|
|
AND (admin OR moderator))
|
|
|
|
) THEN
|
|
|
|
GREATEST(topic_users.last_read_post_number,
|
|
|
|
excluded.last_read_post_number)
|
|
|
|
ELSE topic_users.last_read_post_number END,
|
|
|
|
last_emailed_post_number = CASE
|
|
|
|
WHEN topic_users.last_emailed_post_number = :old_highest_staff_post_number OR (
|
|
|
|
:old_highest_post_number < :old_highest_staff_post_number
|
|
|
|
AND topic_users.last_emailed_post_number = :old_highest_post_number
|
|
|
|
AND NOT EXISTS(SELECT 1
|
|
|
|
FROM users u
|
|
|
|
WHERE u.id = topic_users.user_id
|
|
|
|
AND (admin OR moderator))
|
|
|
|
) THEN
|
|
|
|
GREATEST(topic_users.last_emailed_post_number,
|
|
|
|
excluded.last_emailed_post_number)
|
|
|
|
ELSE topic_users.last_emailed_post_number END,
|
|
|
|
first_visited_at = LEAST(topic_users.first_visited_at, excluded.first_visited_at),
|
|
|
|
last_visited_at = GREATEST(topic_users.last_visited_at, excluded.last_visited_at)
|
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
2013-05-26 08:40:33 +08:00
|
|
|
def update_statistics
|
|
|
|
destination_topic.update_statistics
|
|
|
|
original_topic.update_statistics
|
2021-07-10 03:50:24 +08:00
|
|
|
TopicUser.update_post_action_cache(
|
|
|
|
topic_id: [original_topic.id, destination_topic.id],
|
|
|
|
post_id: @post_ids,
|
|
|
|
)
|
2013-05-26 08:40:33 +08:00
|
|
|
end
|
|
|
|
|
2013-07-17 14:40:15 +08:00
|
|
|
def update_user_actions
|
|
|
|
UserAction.synchronize_target_topic_ids(posts.map(&:id))
|
|
|
|
end
|
|
|
|
|
2013-05-26 08:40:33 +08:00
|
|
|
def create_moderator_post_in_original_topic
|
2015-08-01 04:30:18 +08:00
|
|
|
move_type_str = PostMover.move_types[@move_type].to_s
|
2019-02-20 23:37:32 +08:00
|
|
|
move_type_str.sub!("topic", "message") if @move_to_pm
|
2015-08-01 04:30:18 +08:00
|
|
|
|
2017-09-14 21:52:09 +08:00
|
|
|
message =
|
|
|
|
I18n.with_locale(SiteSetting.default_locale) do
|
2018-04-14 00:47:36 +08:00
|
|
|
I18n.t(
|
|
|
|
"move_posts.#{move_type_str}_moderator_post",
|
|
|
|
count: posts.length,
|
|
|
|
topic_link:
|
2023-01-09 20:20:10 +08:00
|
|
|
(
|
2018-04-14 00:47:36 +08:00
|
|
|
if posts.first.is_first_post?
|
|
|
|
"[#{destination_topic.title}](#{destination_topic.relative_url})"
|
2023-01-09 20:20:10 +08:00
|
|
|
else
|
2023-11-13 11:06:25 +08:00
|
|
|
"[#{destination_topic.title}](#{posts.first.relative_url})"
|
2023-01-09 20:20:10 +08:00
|
|
|
end
|
|
|
|
),
|
2018-04-14 00:47:36 +08:00
|
|
|
)
|
2017-09-14 21:52:09 +08:00
|
|
|
end
|
|
|
|
|
2019-02-28 22:19:26 +08:00
|
|
|
post_type = @move_to_pm ? Post.types[:whisper] : Post.types[:small_action]
|
2017-09-14 21:52:09 +08:00
|
|
|
original_topic.add_moderator_post(
|
|
|
|
user,
|
|
|
|
message,
|
2019-02-28 22:19:26 +08:00
|
|
|
post_type: post_type,
|
2015-08-01 04:30:18 +08:00
|
|
|
action_code: "split_topic",
|
2013-05-26 08:40:33 +08:00
|
|
|
post_number: @first_post_number_moved,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
def posts
|
|
|
|
@posts ||=
|
|
|
|
begin
|
2017-11-24 00:16:05 +08:00
|
|
|
Post
|
|
|
|
.where(topic: @original_topic, id: post_ids)
|
|
|
|
.where.not(post_type: Post.types[:small_action])
|
2019-05-06 21:21:42 +08:00
|
|
|
.where.not(raw: "")
|
2017-11-24 00:16:05 +08:00
|
|
|
.order(:created_at)
|
2013-05-26 08:40:33 +08:00
|
|
|
.tap { |posts| raise Discourse::InvalidParameters.new(:post_ids) if posts.empty? }
|
|
|
|
end
|
|
|
|
end
|
2014-10-17 03:38:26 +08:00
|
|
|
|
2018-02-02 22:00:52 +08:00
|
|
|
def update_last_post_stats
|
2018-08-01 23:54:46 +08:00
|
|
|
post = destination_topic.ordered_posts.where.not(post_type: Post.types[:whisper]).last
|
2018-02-02 22:00:52 +08:00
|
|
|
if post && post_ids.include?(post.id)
|
|
|
|
attrs = {}
|
|
|
|
attrs[:last_posted_at] = post.created_at
|
|
|
|
attrs[:last_post_user_id] = post.user_id
|
2019-09-07 02:48:57 +08:00
|
|
|
attrs[:bumped_at] = Time.now
|
2018-06-05 15:29:17 +08:00
|
|
|
attrs[:updated_at] = Time.now
|
2018-02-02 22:00:52 +08:00
|
|
|
destination_topic.update_columns(attrs)
|
|
|
|
end
|
2014-10-17 03:38:26 +08:00
|
|
|
end
|
2018-07-18 20:23:32 +08:00
|
|
|
|
2020-01-23 10:01:10 +08:00
|
|
|
def update_upload_security_status
|
|
|
|
DB.after_commit { Jobs.enqueue(:update_topic_upload_security, topic_id: @destination_topic.id) }
|
|
|
|
end
|
|
|
|
|
2021-03-29 09:25:48 +08:00
|
|
|
def update_bookmarks
|
2021-04-14 07:10:53 +08:00
|
|
|
DB.after_commit do
|
|
|
|
Jobs.enqueue(:sync_topic_user_bookmarked, topic_id: @original_topic.id)
|
|
|
|
Jobs.enqueue(:sync_topic_user_bookmarked, topic_id: @destination_topic.id)
|
|
|
|
end
|
2021-03-29 09:25:48 +08:00
|
|
|
end
|
|
|
|
|
2018-07-20 15:13:27 +08:00
|
|
|
def watch_new_topic
|
2019-05-15 15:29:29 +08:00
|
|
|
if @destination_topic.archetype == Archetype.private_message
|
|
|
|
if @original_topic.archetype == Archetype.private_message
|
|
|
|
notification_levels =
|
|
|
|
TopicUser
|
|
|
|
.where(topic_id: @original_topic.id, user_id: posts.pluck(:user_id))
|
|
|
|
.pluck(:user_id, :notification_level)
|
|
|
|
.to_h
|
|
|
|
else
|
|
|
|
notification_levels =
|
|
|
|
posts
|
|
|
|
.pluck(:user_id)
|
|
|
|
.uniq
|
|
|
|
.map { |user_id| [user_id, TopicUser.notification_levels[:watching]] }
|
|
|
|
.to_h
|
|
|
|
end
|
|
|
|
else
|
|
|
|
notification_levels = [[@destination_topic.user_id, TopicUser.notification_levels[:watching]]]
|
|
|
|
end
|
|
|
|
|
|
|
|
notification_levels.each do |user_id, notification_level|
|
|
|
|
TopicUser.change(
|
|
|
|
user_id,
|
|
|
|
@destination_topic.id,
|
|
|
|
notification_level: notification_level,
|
|
|
|
notifications_reason_id:
|
|
|
|
TopicUser.notification_reasons[
|
|
|
|
destination_topic.user_id == user_id ? :created_topic : :created_post
|
2023-01-09 20:20:10 +08:00
|
|
|
],
|
2019-05-15 15:29:29 +08:00
|
|
|
)
|
|
|
|
end
|
2018-07-18 20:23:32 +08:00
|
|
|
end
|
2018-12-31 19:47:22 +08:00
|
|
|
|
|
|
|
def add_allowed_users(usernames)
|
2024-05-27 18:27:13 +08:00
|
|
|
return if usernames.blank?
|
2018-12-31 19:47:22 +08:00
|
|
|
|
|
|
|
names = usernames.split(",").flatten
|
|
|
|
User
|
|
|
|
.where(username: names)
|
|
|
|
.find_each do |user|
|
|
|
|
unless destination_topic.topic_allowed_users.where(user_id: user.id).exists?
|
|
|
|
destination_topic.topic_allowed_users.build(user_id: user.id)
|
|
|
|
end
|
2023-01-09 20:20:10 +08:00
|
|
|
end
|
2018-12-31 19:47:22 +08:00
|
|
|
destination_topic.save!
|
|
|
|
end
|
2019-07-21 03:36:18 +08:00
|
|
|
|
2019-07-23 01:02:21 +08:00
|
|
|
def enqueue_jobs(topic)
|
|
|
|
@post_creator.enqueue_jobs if @post_creator
|
|
|
|
|
2024-12-06 07:10:32 +08:00
|
|
|
Jobs.enqueue(:notify_moved_posts, post_ids: @post_ids_after_move, moved_by_id: user.id)
|
2020-12-04 05:43:42 +08:00
|
|
|
|
2019-07-23 01:02:21 +08:00
|
|
|
Jobs.enqueue(:delete_inaccessible_notifications, topic_id: topic.id)
|
|
|
|
end
|
2021-05-28 21:33:10 +08:00
|
|
|
|
|
|
|
def close_topic_and_schedule_deletion
|
|
|
|
@original_topic.update_status("closed", true, @user)
|
2024-11-27 03:30:25 +08:00
|
|
|
return if @options[:freeze_original] # we only close the topic when freezing it
|
2021-05-28 21:33:10 +08:00
|
|
|
|
|
|
|
days_to_deleting = SiteSetting.delete_merged_stub_topics_after_days
|
2024-08-07 21:05:40 +08:00
|
|
|
if days_to_deleting == 0
|
2024-11-21 22:12:06 +08:00
|
|
|
is_allowed_to_delete_after_merge =
|
|
|
|
DiscoursePluginRegistry.apply_modifier(
|
|
|
|
:is_allowed_to_delete_after_merge,
|
|
|
|
Guardian.new(@user).can_delete?(@original_topic),
|
|
|
|
@original_topic,
|
|
|
|
@user,
|
|
|
|
)
|
|
|
|
if is_allowed_to_delete_after_merge
|
2024-08-07 21:05:40 +08:00
|
|
|
first_post = @original_topic.ordered_posts.first
|
|
|
|
|
|
|
|
PostDestroyer.new(
|
|
|
|
@user,
|
|
|
|
first_post,
|
|
|
|
context: I18n.t("topic_statuses.auto_deleted_by_merge"),
|
|
|
|
).destroy
|
|
|
|
|
|
|
|
@original_topic.trash!(Discourse.system_user)
|
|
|
|
end
|
|
|
|
elsif days_to_deleting > 0
|
2021-05-28 21:33:10 +08:00
|
|
|
@original_topic.set_or_create_timer(
|
|
|
|
TopicTimer.types[:delete],
|
|
|
|
days_to_deleting * 24,
|
|
|
|
by_user: @user,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
2013-05-26 08:40:33 +08:00
|
|
|
end
|