2019-05-02 17:17:27 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-02-05 13:16:51 -06:00
|
|
|
class TopicView
|
2018-07-12 16:00:53 -05:00
|
|
|
MEGA_TOPIC_POSTS_COUNT = 10000
|
2019-07-19 10:15:38 -05:00
|
|
|
MIN_POST_READ_TIME = 4.0
|
2013-02-05 13:16:51 -06:00
|
|
|
|
2019-04-12 08:55:27 -05:00
|
|
|
attr_reader(
|
|
|
|
:topic,
|
|
|
|
:posts,
|
|
|
|
:guardian,
|
|
|
|
:filtered_posts,
|
|
|
|
:chunk_size,
|
|
|
|
:print,
|
|
|
|
:message_bus_last_id,
|
|
|
|
:queued_posts_enabled,
|
2019-05-03 13:26:37 -05:00
|
|
|
:personal_message,
|
|
|
|
:can_review_topic
|
2019-04-12 08:55:27 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
attr_accessor(
|
|
|
|
:draft,
|
|
|
|
:draft_key,
|
|
|
|
:draft_sequence,
|
|
|
|
:user_custom_fields,
|
|
|
|
:post_custom_fields,
|
|
|
|
:post_number
|
|
|
|
)
|
2013-02-05 13:16:51 -06:00
|
|
|
|
2016-07-31 23:45:05 -05:00
|
|
|
def self.print_chunk_size
|
|
|
|
1000
|
|
|
|
end
|
|
|
|
|
2014-12-14 17:57:34 -06:00
|
|
|
def self.chunk_size
|
|
|
|
20
|
|
|
|
end
|
|
|
|
|
2016-01-11 05:42:06 -06:00
|
|
|
def self.default_post_custom_fields
|
2020-03-24 04:12:52 -05:00
|
|
|
@default_post_custom_fields ||= [Post::NOTICE_TYPE, Post::NOTICE_ARGS, "action_code_who"]
|
2016-01-11 05:42:06 -06:00
|
|
|
end
|
|
|
|
|
2017-09-29 10:04:05 -05:00
|
|
|
def self.post_custom_fields_whitelisters
|
|
|
|
@post_custom_fields_whitelisters ||= Set.new
|
|
|
|
end
|
2015-04-23 12:33:29 -05:00
|
|
|
|
|
|
|
def self.add_post_custom_fields_whitelister(&block)
|
|
|
|
post_custom_fields_whitelisters << block
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.whitelisted_post_custom_fields(user)
|
2016-01-11 05:42:06 -06:00
|
|
|
wpcf = default_post_custom_fields + post_custom_fields_whitelisters.map { |w| w.call(user) }
|
|
|
|
wpcf.flatten.uniq
|
2015-04-23 12:33:29 -05:00
|
|
|
end
|
|
|
|
|
2018-05-28 04:06:47 -05:00
|
|
|
def initialize(topic_or_topic_id, user = nil, options = {})
|
|
|
|
@topic = find_topic(topic_or_topic_id)
|
2013-07-11 15:38:46 -05:00
|
|
|
@user = user
|
|
|
|
@guardian = Guardian.new(@user)
|
2018-03-23 20:44:39 -05:00
|
|
|
|
2013-07-12 03:33:45 -05:00
|
|
|
check_and_raise_exceptions
|
2013-06-20 16:20:08 -05:00
|
|
|
|
2018-05-28 04:06:47 -05:00
|
|
|
@message_bus_last_id = MessageBus.last_id("/topic/#{@topic.id}")
|
|
|
|
@print = options[:print].present?
|
|
|
|
|
2013-08-13 09:29:25 -05:00
|
|
|
options.each do |key, value|
|
2013-10-28 01:12:07 -05:00
|
|
|
self.instance_variable_set("@#{key}".to_sym, value)
|
2013-08-13 09:29:25 -05:00
|
|
|
end
|
|
|
|
|
2018-03-23 20:44:39 -05:00
|
|
|
@post_number = [@post_number.to_i, 1].max
|
|
|
|
@page = [@page.to_i, 1].max
|
|
|
|
|
2019-02-21 17:37:18 -06:00
|
|
|
@include_suggested = options.fetch(:include_suggested) { true }
|
|
|
|
@include_related = options.fetch(:include_related) { true }
|
|
|
|
|
2016-07-31 23:45:05 -05:00
|
|
|
@chunk_size =
|
|
|
|
case
|
2016-08-05 00:12:35 -05:00
|
|
|
when @print then TopicView.print_chunk_size
|
2016-07-31 23:45:05 -05:00
|
|
|
else TopicView.chunk_size
|
|
|
|
end
|
2017-07-27 20:20:09 -05:00
|
|
|
|
2014-12-12 10:47:20 -06:00
|
|
|
@limit ||= @chunk_size
|
2013-02-05 13:16:51 -06:00
|
|
|
|
2013-07-12 03:33:45 -05:00
|
|
|
setup_filtered_posts
|
2013-02-05 13:16:51 -06:00
|
|
|
|
|
|
|
@initial_load = true
|
2013-03-26 13:18:35 -05:00
|
|
|
@index_reverse = false
|
2013-02-05 13:16:51 -06:00
|
|
|
|
2013-02-10 12:50:26 -06:00
|
|
|
filter_posts(options)
|
|
|
|
|
2018-06-28 01:54:54 -05:00
|
|
|
if @posts && !@skip_custom_fields
|
2017-08-11 21:18:04 -05:00
|
|
|
if (added_fields = User.whitelisted_user_custom_fields(@guardian)).present?
|
2017-09-07 04:22:39 -05:00
|
|
|
@user_custom_fields = User.custom_fields_for_ids(@posts.pluck(:user_id), added_fields)
|
2016-03-11 14:52:18 -06:00
|
|
|
end
|
2015-03-03 00:51:01 -06:00
|
|
|
|
2017-08-11 21:18:04 -05:00
|
|
|
if (whitelisted_fields = TopicView.whitelisted_post_custom_fields(@user)).present?
|
2017-09-07 04:22:39 -05:00
|
|
|
@post_custom_fields = Post.custom_fields_for_ids(@posts.pluck(:id), whitelisted_fields)
|
2017-08-11 21:18:04 -05:00
|
|
|
end
|
2015-04-23 12:33:29 -05:00
|
|
|
end
|
|
|
|
|
2013-02-10 12:50:26 -06:00
|
|
|
@draft_key = @topic.draft_key
|
2013-07-11 15:38:46 -05:00
|
|
|
@draft_sequence = DraftSequence.current(@user, @draft_key)
|
2019-04-12 08:55:27 -05:00
|
|
|
|
2019-05-03 13:26:37 -05:00
|
|
|
@can_review_topic = @guardian.can_review_topic?(@topic)
|
2019-04-12 08:55:27 -05:00
|
|
|
@queued_posts_enabled = NewPostManager.queue_enabled?
|
|
|
|
@personal_message = @topic.private_message?
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
2019-08-27 07:09:00 -05:00
|
|
|
def show_read_indicator?
|
|
|
|
return false unless @user || topic.private_message?
|
|
|
|
|
|
|
|
topic.allowed_groups.any? do |group|
|
|
|
|
group.publish_read_state? && group.users.include?(@user)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-02-10 12:50:26 -06:00
|
|
|
def canonical_path
|
2020-03-09 09:31:24 -05:00
|
|
|
if SiteSetting.embed_set_canonical_url
|
|
|
|
topic_embed = topic.topic_embed
|
|
|
|
return topic_embed.embed_url if topic_embed
|
|
|
|
end
|
2019-05-02 17:17:27 -05:00
|
|
|
path = relative_url.dup
|
2013-02-10 12:50:26 -06:00
|
|
|
path <<
|
2018-03-23 20:44:39 -05:00
|
|
|
if @page > 1
|
|
|
|
"?page=#{@page}"
|
2013-02-10 12:50:26 -06:00
|
|
|
else
|
2020-07-06 00:01:19 -05:00
|
|
|
posts_count = is_mega_topic? ? @post_number : unfiltered_posts.where("post_number <= ?", @post_number).count
|
2020-07-05 03:34:31 -05:00
|
|
|
page = ((posts_count - 1) / @limit) + 1
|
2018-03-23 20:44:39 -05:00
|
|
|
page > 1 ? "?page=#{page}" : ""
|
2013-02-10 12:50:26 -06:00
|
|
|
end
|
2017-07-27 20:20:09 -05:00
|
|
|
|
2013-02-10 12:50:26 -06:00
|
|
|
path
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
2018-06-25 23:54:14 -05:00
|
|
|
def contains_gaps?
|
|
|
|
@contains_gaps
|
|
|
|
end
|
|
|
|
|
2013-12-04 14:56:09 -06:00
|
|
|
def gaps
|
|
|
|
return unless @contains_gaps
|
2018-07-10 03:23:53 -05:00
|
|
|
|
|
|
|
@gaps ||= begin
|
|
|
|
if is_mega_topic?
|
|
|
|
nil
|
|
|
|
else
|
|
|
|
Gaps.new(filtered_post_ids, unfiltered_posts.order(:sort_order).pluck(:id))
|
|
|
|
end
|
|
|
|
end
|
2013-12-04 14:56:09 -06:00
|
|
|
end
|
|
|
|
|
2013-07-05 13:45:54 -05:00
|
|
|
def last_post
|
|
|
|
return nil if @posts.blank?
|
|
|
|
@last_post ||= @posts.last
|
|
|
|
end
|
|
|
|
|
2014-03-03 11:56:37 -06:00
|
|
|
def prev_page
|
2018-03-23 20:44:39 -05:00
|
|
|
@page > 1 && posts.size > 0 ? @page - 1 : nil
|
2014-03-03 11:56:37 -06:00
|
|
|
end
|
|
|
|
|
2013-02-10 12:50:26 -06:00
|
|
|
def next_page
|
2013-07-05 13:45:54 -05:00
|
|
|
@next_page ||= begin
|
2019-12-04 06:52:24 -06:00
|
|
|
if last_post && highest_post_number && (highest_post_number > last_post.post_number)
|
2013-07-05 13:45:54 -05:00
|
|
|
@page + 1
|
|
|
|
end
|
2013-02-10 12:50:26 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-03-03 11:56:37 -06:00
|
|
|
def prev_page_path
|
|
|
|
if prev_page > 1
|
2016-08-03 23:15:22 -05:00
|
|
|
"#{relative_url}?page=#{prev_page}"
|
2014-03-03 11:56:37 -06:00
|
|
|
else
|
2016-08-03 23:15:22 -05:00
|
|
|
relative_url
|
2014-03-03 11:56:37 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-02-10 12:50:26 -06:00
|
|
|
def next_page_path
|
2016-08-03 23:15:22 -05:00
|
|
|
"#{relative_url}?page=#{next_page}"
|
2013-02-10 12:50:26 -06:00
|
|
|
end
|
|
|
|
|
2013-03-07 16:31:06 -06:00
|
|
|
def absolute_url
|
2018-08-27 17:05:08 -05:00
|
|
|
"#{Discourse.base_url_no_prefix}#{relative_url}"
|
2013-03-07 16:31:06 -06:00
|
|
|
end
|
|
|
|
|
2013-02-10 12:50:26 -06:00
|
|
|
def relative_url
|
2016-08-08 22:53:08 -05:00
|
|
|
"#{@topic.relative_url}#{@print ? '/print' : ''}"
|
2013-02-10 12:50:26 -06:00
|
|
|
end
|
|
|
|
|
2015-07-21 19:26:58 -05:00
|
|
|
def page_title
|
|
|
|
title = @topic.title
|
2017-02-07 15:55:42 -06:00
|
|
|
if SiteSetting.topic_page_title_includes_category
|
|
|
|
if @topic.category_id != SiteSetting.uncategorized_category_id && @topic.category_id && @topic.category
|
|
|
|
title += " - #{@topic.category.name}"
|
|
|
|
elsif SiteSetting.tagging_enabled && @topic.tags.exists?
|
|
|
|
title += " - #{@topic.tags.order('tags.topic_count DESC').first.name}"
|
|
|
|
end
|
2015-07-21 19:26:58 -05:00
|
|
|
end
|
|
|
|
title
|
|
|
|
end
|
|
|
|
|
2013-02-10 12:50:26 -06:00
|
|
|
def title
|
|
|
|
@topic.title
|
|
|
|
end
|
|
|
|
|
2013-07-08 11:21:39 -05:00
|
|
|
def desired_post
|
|
|
|
return @desired_post if @desired_post.present?
|
2013-03-07 16:31:06 -06:00
|
|
|
return nil if posts.blank?
|
2013-07-08 11:21:39 -05:00
|
|
|
|
2018-03-23 20:44:39 -05:00
|
|
|
@desired_post = posts.detect { |p| p.post_number == @post_number }
|
2013-07-08 11:21:39 -05:00
|
|
|
@desired_post ||= posts.first
|
|
|
|
@desired_post
|
|
|
|
end
|
|
|
|
|
2017-11-28 05:27:43 -06:00
|
|
|
def summary(opts = {})
|
2013-07-08 11:21:39 -05:00
|
|
|
return nil if desired_post.blank?
|
2013-09-04 18:33:30 -05:00
|
|
|
# TODO, this is actually quite slow, should be cached in the post table
|
2017-11-28 05:27:43 -06:00
|
|
|
excerpt = desired_post.excerpt(500, opts.merge(strip_links: true, text_entities: true))
|
2014-12-07 17:23:53 -06:00
|
|
|
(excerpt || "").gsub(/\n/, ' ').strip
|
2013-03-07 16:31:06 -06:00
|
|
|
end
|
|
|
|
|
2015-12-28 06:52:31 -06:00
|
|
|
def read_time
|
2018-03-23 20:44:39 -05:00
|
|
|
return nil if @post_number > 1 # only show for topic URLs
|
2019-07-19 10:15:38 -05:00
|
|
|
|
|
|
|
if @topic.word_count && SiteSetting.read_time_word_count > 0
|
|
|
|
[
|
|
|
|
@topic.word_count / SiteSetting.read_time_word_count,
|
|
|
|
@topic.posts_count * MIN_POST_READ_TIME / 60
|
|
|
|
].max.ceil
|
|
|
|
end
|
2015-12-28 06:52:31 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
def like_count
|
2018-03-23 20:44:39 -05:00
|
|
|
return nil if @post_number > 1 # only show for topic URLs
|
2015-12-28 06:52:31 -06:00
|
|
|
@topic.like_count
|
|
|
|
end
|
|
|
|
|
2018-07-30 05:52:51 -05:00
|
|
|
def published_time
|
|
|
|
return nil if desired_post.blank?
|
2018-07-30 09:56:40 -05:00
|
|
|
if desired_post.wiki && desired_post.post_number == 1 && desired_post.revisions.size > 0
|
|
|
|
desired_post.revisions.last.updated_at.strftime('%FT%T%:z')
|
|
|
|
else
|
|
|
|
desired_post.created_at.strftime('%FT%T%:z')
|
|
|
|
end
|
2018-07-30 05:52:51 -05:00
|
|
|
end
|
|
|
|
|
2013-03-08 14:58:37 -06:00
|
|
|
def image_url
|
2019-12-20 07:17:14 -06:00
|
|
|
url = desired_post&.image_url if @post_number > 1
|
|
|
|
url || @topic.image_url
|
2013-03-08 14:58:37 -06:00
|
|
|
end
|
|
|
|
|
2013-02-10 12:50:26 -06:00
|
|
|
def filter_posts(opts = {})
|
2013-03-26 10:58:49 -05:00
|
|
|
return filter_posts_near(opts[:post_number].to_i) if opts[:post_number].present?
|
2013-06-20 16:20:08 -05:00
|
|
|
return filter_posts_by_ids(opts[:post_ids]) if opts[:post_ids].present?
|
2018-07-11 02:41:26 -05:00
|
|
|
|
|
|
|
if opts[:filter_post_number].present?
|
|
|
|
return filter_posts_by_post_number(opts[:filter_post_number], opts[:asc])
|
|
|
|
end
|
|
|
|
|
2013-06-20 16:20:08 -05:00
|
|
|
return filter_best(opts[:best], opts) if opts[:best].present?
|
2013-07-01 06:29:45 -05:00
|
|
|
|
2016-02-24 21:32:16 -06:00
|
|
|
filter_posts_paged(@page)
|
2013-03-26 10:58:49 -05:00
|
|
|
end
|
|
|
|
|
2014-02-10 15:59:36 -06:00
|
|
|
def primary_group_names
|
|
|
|
return @group_names if @group_names
|
|
|
|
|
|
|
|
primary_group_ids = Set.new
|
|
|
|
@posts.each do |p|
|
|
|
|
primary_group_ids << p.user.primary_group_id if p.user.try(:primary_group_id)
|
|
|
|
end
|
|
|
|
|
|
|
|
result = {}
|
|
|
|
unless primary_group_ids.empty?
|
|
|
|
Group.where(id: primary_group_ids.to_a).pluck(:id, :name).each do |g|
|
|
|
|
result[g[0]] = g[1]
|
|
|
|
end
|
|
|
|
end
|
2015-09-28 01:38:34 -05:00
|
|
|
|
|
|
|
@group_names = result
|
2014-02-10 15:59:36 -06:00
|
|
|
end
|
2013-03-28 00:53:11 -05:00
|
|
|
|
2013-03-26 10:58:49 -05:00
|
|
|
# Find the sort order for a post in the topic
|
|
|
|
def sort_order_for_post_number(post_number)
|
2015-09-10 15:01:23 -05:00
|
|
|
posts = Post.where(topic_id: @topic.id, post_number: post_number).with_deleted
|
|
|
|
posts = filter_post_types(posts)
|
|
|
|
posts.select(:sort_order).first.try(:sort_order)
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
2013-02-10 12:50:26 -06:00
|
|
|
# Filter to all posts near a particular post number
|
|
|
|
def filter_posts_near(post_number)
|
2018-07-10 02:42:48 -05:00
|
|
|
posts_before = (@limit.to_f / 4).floor
|
|
|
|
posts_before = 1 if posts_before.zero?
|
2018-07-13 01:25:12 -05:00
|
|
|
sort_order = get_sort_order(post_number)
|
2018-07-10 02:42:48 -05:00
|
|
|
|
|
|
|
before_post_ids = @filtered_posts.order(sort_order: :desc)
|
2018-07-13 01:25:12 -05:00
|
|
|
.where("posts.sort_order < ?", sort_order)
|
2018-07-10 02:42:48 -05:00
|
|
|
.limit(posts_before)
|
|
|
|
.pluck(:id)
|
|
|
|
|
|
|
|
post_ids = before_post_ids + @filtered_posts.order(sort_order: :asc)
|
2018-07-13 01:25:12 -05:00
|
|
|
.where("posts.sort_order >= ?", sort_order)
|
2018-07-10 02:42:48 -05:00
|
|
|
.limit(@limit - before_post_ids.length)
|
|
|
|
.pluck(:id)
|
|
|
|
|
|
|
|
if post_ids.length < @limit
|
|
|
|
post_ids = post_ids + @filtered_posts.order(sort_order: :desc)
|
2018-07-13 01:25:12 -05:00
|
|
|
.where("posts.sort_order < ?", sort_order)
|
2018-07-10 02:42:48 -05:00
|
|
|
.offset(before_post_ids.length)
|
|
|
|
.limit(@limit - post_ids.length)
|
|
|
|
.pluck(:id)
|
|
|
|
end
|
|
|
|
|
|
|
|
filter_posts_by_ids(post_ids)
|
2013-02-10 12:50:26 -06:00
|
|
|
end
|
2013-02-05 13:16:51 -06:00
|
|
|
|
|
|
|
def filter_posts_paged(page)
|
2013-04-03 17:12:27 -05:00
|
|
|
page = [page, 1].max
|
2014-01-03 11:52:24 -06:00
|
|
|
min = @limit * (page - 1)
|
|
|
|
|
|
|
|
# Sometimes we don't care about the OP, for example when embedding comments
|
|
|
|
min = 1 if min == 0 && @exclude_first
|
|
|
|
|
2018-07-10 02:42:48 -05:00
|
|
|
@posts = filter_posts_by_ids(
|
|
|
|
@filtered_posts.order(:sort_order)
|
|
|
|
.offset(min)
|
|
|
|
.limit(@limit)
|
|
|
|
.pluck(:id)
|
|
|
|
)
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
2013-07-01 06:29:45 -05:00
|
|
|
def filter_best(max, opts = {})
|
2013-07-12 03:33:45 -05:00
|
|
|
filter = FilterBestPosts.new(@topic, @filtered_posts, max, opts)
|
|
|
|
@posts = filter.posts
|
|
|
|
@filtered_posts = filter.filtered_posts
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
def read?(post_number)
|
2014-06-02 20:48:52 -05:00
|
|
|
return true unless @user
|
2013-02-05 13:16:51 -06:00
|
|
|
read_posts_set.include?(post_number)
|
|
|
|
end
|
|
|
|
|
2014-07-15 16:02:43 -05:00
|
|
|
def has_deleted?
|
2014-08-04 10:29:01 -05:00
|
|
|
@predelete_filtered_posts.with_deleted
|
|
|
|
.where("posts.deleted_at IS NOT NULL")
|
2014-08-07 12:12:35 -05:00
|
|
|
.where("posts.post_number > 1")
|
2014-08-04 10:29:01 -05:00
|
|
|
.exists?
|
2014-07-15 16:02:43 -05:00
|
|
|
end
|
|
|
|
|
2013-02-05 13:16:51 -06:00
|
|
|
def topic_user
|
|
|
|
@topic_user ||= begin
|
|
|
|
return nil if @user.blank?
|
2014-05-06 08:41:59 -05:00
|
|
|
@topic.topic_users.find_by(user_id: @user.id)
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-03-12 00:20:56 -05:00
|
|
|
def has_bookmarks?
|
|
|
|
return false if @user.blank?
|
|
|
|
@topic.bookmarks.exists?(user_id: @user.id)
|
|
|
|
end
|
|
|
|
|
2020-04-15 18:20:44 -05:00
|
|
|
def first_post_bookmark_reminder_at
|
|
|
|
@topic.first_post.bookmarks.where(user: @user).pluck_first(:reminder_at)
|
|
|
|
end
|
|
|
|
|
2017-12-13 00:19:42 -06:00
|
|
|
MAX_PARTICIPANTS = 24
|
|
|
|
|
2013-03-26 13:06:24 -05:00
|
|
|
def post_counts_by_user
|
2017-09-13 10:14:03 -05:00
|
|
|
@post_counts_by_user ||= begin
|
2018-06-20 20:09:45 -05:00
|
|
|
if is_mega_topic?
|
|
|
|
{}
|
|
|
|
else
|
|
|
|
post_ids = unfiltered_post_ids
|
2017-10-02 10:47:04 -05:00
|
|
|
|
2018-06-20 20:09:45 -05:00
|
|
|
return {} if post_ids.blank?
|
2017-09-12 01:34:43 -05:00
|
|
|
|
2018-06-20 20:09:45 -05:00
|
|
|
sql = <<~SQL
|
2019-02-27 07:49:07 -06:00
|
|
|
SELECT user_id, count(*) AS count_all
|
|
|
|
FROM posts
|
|
|
|
WHERE id in (:post_ids)
|
|
|
|
AND user_id IS NOT NULL
|
|
|
|
GROUP BY user_id
|
|
|
|
ORDER BY count_all DESC
|
|
|
|
LIMIT #{MAX_PARTICIPANTS}
|
2018-06-20 20:09:45 -05:00
|
|
|
SQL
|
2017-09-12 01:34:43 -05:00
|
|
|
|
2018-06-20 20:09:45 -05:00
|
|
|
Hash[*DB.query_single(sql, post_ids: post_ids)]
|
|
|
|
end
|
2017-09-13 10:14:03 -05:00
|
|
|
end
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
2018-06-20 03:11:39 -05:00
|
|
|
# if a topic has more that N posts no longer attempt to
|
|
|
|
# get accurate participant count, instead grab cached count
|
|
|
|
# from topic
|
|
|
|
MAX_POSTS_COUNT_PARTICIPANTS = 500
|
|
|
|
|
2017-12-13 00:19:42 -06:00
|
|
|
def participant_count
|
|
|
|
@participant_count ||=
|
|
|
|
begin
|
|
|
|
if participants.size == MAX_PARTICIPANTS
|
2018-06-27 04:18:47 -05:00
|
|
|
if @topic.posts_count > MAX_POSTS_COUNT_PARTICIPANTS
|
2018-06-20 03:11:39 -05:00
|
|
|
@topic.participant_count
|
|
|
|
else
|
|
|
|
sql = <<~SQL
|
|
|
|
SELECT COUNT(DISTINCT user_id)
|
|
|
|
FROM posts
|
|
|
|
WHERE id IN (:post_ids)
|
|
|
|
AND user_id IS NOT NULL
|
|
|
|
SQL
|
|
|
|
DB.query_single(sql, post_ids: unfiltered_post_ids).first.to_i
|
|
|
|
end
|
2017-12-13 00:19:42 -06:00
|
|
|
else
|
|
|
|
participants.size
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-02-05 13:16:51 -06:00
|
|
|
def participants
|
|
|
|
@participants ||= begin
|
|
|
|
participants = {}
|
2017-09-13 10:14:03 -05:00
|
|
|
User.where(id: post_counts_by_user.keys).includes(:primary_group).each { |u| participants[u.id] = u }
|
2013-02-05 13:16:51 -06:00
|
|
|
participants
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-04-12 08:55:27 -05:00
|
|
|
def group_allowed_user_ids
|
|
|
|
return @group_allowed_user_ids unless @group_allowed_user_ids.nil?
|
|
|
|
|
|
|
|
group_ids = @topic.allowed_groups.map(&:id)
|
|
|
|
@group_allowed_user_ids = Set.new(GroupUser.where(group_id: group_ids).pluck('distinct user_id'))
|
|
|
|
end
|
|
|
|
|
2013-02-05 13:16:51 -06:00
|
|
|
def all_post_actions
|
2014-06-04 10:41:11 -05:00
|
|
|
@all_post_actions ||= PostAction.counts_for(@posts, @user)
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
def links
|
2017-09-14 13:08:16 -05:00
|
|
|
@links ||= TopicLink.topic_map(@guardian, @topic.id)
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
2019-12-10 22:04:02 -06:00
|
|
|
def user_post_bookmarks
|
2020-07-09 00:46:52 -05:00
|
|
|
@user_post_bookmarks ||= @topic.bookmarks.where(user: @user)
|
2019-12-10 22:04:02 -06:00
|
|
|
end
|
|
|
|
|
2019-05-03 13:26:37 -05:00
|
|
|
def reviewable_counts
|
2019-06-07 21:30:21 -05:00
|
|
|
if @reviewable_counts.nil?
|
2019-05-03 13:26:37 -05:00
|
|
|
|
2019-06-07 03:12:30 -05:00
|
|
|
post_ids = @posts.map(&:id)
|
|
|
|
|
|
|
|
sql = <<~SQL
|
|
|
|
SELECT target_id,
|
|
|
|
MAX(r.id) reviewable_id,
|
|
|
|
COUNT(*) total,
|
|
|
|
SUM(CASE WHEN s.status = :pending THEN 1 ELSE 0 END) pending
|
|
|
|
FROM reviewables r
|
|
|
|
JOIN reviewable_scores s ON reviewable_id = r.id
|
|
|
|
WHERE r.target_id IN (:post_ids) AND
|
|
|
|
r.target_type = 'Post'
|
|
|
|
GROUP BY target_id
|
|
|
|
SQL
|
|
|
|
|
2019-05-03 13:26:37 -05:00
|
|
|
@reviewable_counts = {}
|
2019-06-07 03:12:30 -05:00
|
|
|
|
|
|
|
DB.query(
|
|
|
|
sql,
|
|
|
|
pending: ReviewableScore.statuses[:pending],
|
|
|
|
post_ids: post_ids
|
|
|
|
).each do |row|
|
|
|
|
@reviewable_counts[row.target_id] = {
|
|
|
|
total: row.total,
|
|
|
|
pending: row.pending,
|
|
|
|
reviewable_id: row.reviewable_id
|
|
|
|
}
|
2019-05-03 13:26:37 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
@reviewable_counts
|
|
|
|
end
|
|
|
|
|
2019-04-12 08:55:27 -05:00
|
|
|
def pending_posts
|
2019-05-03 13:26:37 -05:00
|
|
|
@pending_posts ||= ReviewableQueuedPost.pending.where(created_by: @user, topic: @topic).order(:created_at)
|
2019-04-12 08:55:27 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def actions_summary
|
|
|
|
return @actions_summary unless @actions_summary.nil?
|
|
|
|
|
|
|
|
@actions_summary = []
|
|
|
|
return @actions_summary unless post = posts&.first
|
|
|
|
PostActionType.topic_flag_types.each do |sym, id|
|
|
|
|
@actions_summary << {
|
|
|
|
id: id,
|
|
|
|
count: 0,
|
|
|
|
hidden: false,
|
|
|
|
can_act: @guardian.post_can_act?(post, sym)
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
@actions_summary
|
|
|
|
end
|
|
|
|
|
2013-02-05 13:16:51 -06:00
|
|
|
def link_counts
|
2017-09-14 13:08:16 -05:00
|
|
|
@link_counts ||= TopicLink.counts_for(@guardian, @topic, posts)
|
2013-02-25 10:42:20 -06:00
|
|
|
end
|
2013-02-05 13:16:51 -06:00
|
|
|
|
|
|
|
# Are we the initial page load? If so, we can return extra information like
|
|
|
|
# user post counts, etc.
|
|
|
|
def initial_load?
|
|
|
|
@initial_load
|
|
|
|
end
|
|
|
|
|
2018-11-11 20:04:30 -06:00
|
|
|
def pm_params
|
|
|
|
@pm_params ||= TopicQuery.new(@user).get_pm_params(topic)
|
|
|
|
end
|
|
|
|
|
2013-02-05 13:16:51 -06:00
|
|
|
def suggested_topics
|
2019-02-21 17:37:18 -06:00
|
|
|
if @include_suggested
|
|
|
|
@suggested_topics ||= TopicQuery.new(@user).list_suggested_for(topic, pm_params: pm_params)
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
2018-11-11 20:04:30 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
def related_messages
|
2019-02-21 17:37:18 -06:00
|
|
|
if @include_related
|
|
|
|
@related_messages ||= TopicQuery.new(@user).list_related_for(topic, pm_params: pm_params)
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
|
|
|
|
2013-02-14 19:58:14 -06:00
|
|
|
# This is pending a larger refactor, that allows custom orders
|
2019-01-03 11:03:01 -06:00
|
|
|
# for now we need to look for the highest_post_number in the stream
|
|
|
|
# the cache on topics is not correct if there are deleted posts at
|
|
|
|
# the end of the stream (for mods), nor is it correct for filtered
|
|
|
|
# streams
|
2013-02-14 19:58:14 -06:00
|
|
|
def highest_post_number
|
2013-03-26 10:58:49 -05:00
|
|
|
@highest_post_number ||= @filtered_posts.maximum(:post_number)
|
2013-02-14 19:58:14 -06:00
|
|
|
end
|
|
|
|
|
2013-02-21 12:20:00 -06:00
|
|
|
def recent_posts
|
2013-03-26 10:58:49 -05:00
|
|
|
@filtered_posts.by_newest.with_user.first(25)
|
2013-02-21 12:20:00 -06:00
|
|
|
end
|
|
|
|
|
2018-06-26 22:11:22 -05:00
|
|
|
# Returns an array of [id, days_ago] tuples.
|
2017-08-04 10:28:25 -05:00
|
|
|
# `days_ago` is there for the timeline calculations.
|
2016-05-17 12:03:08 -05:00
|
|
|
def filtered_post_stream
|
2018-06-20 03:24:09 -05:00
|
|
|
@filtered_post_stream ||= begin
|
|
|
|
posts = @filtered_posts
|
|
|
|
.order(:sort_order)
|
|
|
|
|
2018-06-26 22:11:22 -05:00
|
|
|
columns = [:id]
|
2018-06-20 03:24:09 -05:00
|
|
|
|
2018-06-20 03:58:52 -05:00
|
|
|
if !is_mega_topic?
|
2020-06-16 19:40:01 -05:00
|
|
|
columns << 'EXTRACT(DAYS FROM CURRENT_TIMESTAMP - posts.created_at)::INT AS days_ago'
|
2018-06-20 03:24:09 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
posts.pluck(*columns)
|
|
|
|
end
|
2016-05-17 12:03:08 -05:00
|
|
|
end
|
|
|
|
|
2013-08-13 09:29:25 -05:00
|
|
|
def filtered_post_ids
|
2018-06-26 22:11:22 -05:00
|
|
|
@filtered_post_ids ||= filtered_post_stream.map do |tuple|
|
|
|
|
if is_mega_topic?
|
|
|
|
tuple
|
|
|
|
else
|
|
|
|
tuple[0]
|
|
|
|
end
|
|
|
|
end
|
2013-08-13 09:29:25 -05:00
|
|
|
end
|
|
|
|
|
2017-12-13 00:19:42 -06:00
|
|
|
def unfiltered_post_ids
|
|
|
|
@unfiltered_post_ids ||=
|
|
|
|
begin
|
|
|
|
if @contains_gaps
|
2017-12-13 00:36:36 -06:00
|
|
|
unfiltered_posts.pluck(:id)
|
2017-12-13 00:19:42 -06:00
|
|
|
else
|
|
|
|
filtered_post_ids
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-06-26 23:33:57 -05:00
|
|
|
def filtered_post_id(post_number)
|
2019-10-21 05:32:27 -05:00
|
|
|
@filtered_posts.where(post_number: post_number).pluck_first(:id)
|
2018-06-26 22:11:22 -05:00
|
|
|
end
|
|
|
|
|
2018-07-11 02:41:26 -05:00
|
|
|
def is_mega_topic?
|
|
|
|
@is_mega_topic ||= (@topic.posts_count >= MEGA_TOPIC_POSTS_COUNT)
|
|
|
|
end
|
|
|
|
|
|
|
|
def first_post_id
|
2019-10-21 05:32:27 -05:00
|
|
|
@filtered_posts.order(sort_order: :asc).pluck_first(:id)
|
2018-07-11 02:41:26 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def last_post_id
|
2019-10-21 05:32:27 -05:00
|
|
|
@filtered_posts.order(sort_order: :desc).pluck_first(:id)
|
2018-07-11 02:41:26 -05:00
|
|
|
end
|
|
|
|
|
2018-11-20 18:58:47 -06:00
|
|
|
def current_post_number
|
|
|
|
if highest_post_number.present?
|
|
|
|
post_number > highest_post_number ? highest_post_number : post_number
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-04-12 08:55:27 -05:00
|
|
|
def queued_posts_count
|
|
|
|
ReviewableQueuedPost.viewable_by(@user).where(topic_id: @topic.id).pending.count
|
|
|
|
end
|
|
|
|
|
2020-04-08 11:52:36 -05:00
|
|
|
def published_page
|
|
|
|
@topic.published_page
|
|
|
|
end
|
|
|
|
|
2013-02-05 13:16:51 -06:00
|
|
|
protected
|
|
|
|
|
2013-02-10 12:50:26 -06:00
|
|
|
def read_posts_set
|
|
|
|
@read_posts_set ||= begin
|
|
|
|
result = Set.new
|
|
|
|
return result unless @user.present?
|
|
|
|
return result unless topic_user.present?
|
2013-02-05 13:16:51 -06:00
|
|
|
|
2013-10-04 03:06:32 -05:00
|
|
|
post_numbers = PostTiming
|
2013-03-26 10:58:49 -05:00
|
|
|
.where(topic_id: @topic.id, user_id: @user.id)
|
2017-09-07 05:41:44 -05:00
|
|
|
.where(post_number: @posts.pluck(:post_number))
|
2013-03-26 10:58:49 -05:00
|
|
|
.pluck(:post_number)
|
2013-02-05 13:16:51 -06:00
|
|
|
|
2013-03-26 10:58:49 -05:00
|
|
|
post_numbers.each { |pn| result << pn }
|
2013-02-10 12:50:26 -06:00
|
|
|
result
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|
2013-02-10 12:50:26 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2018-07-13 01:25:12 -05:00
|
|
|
def get_sort_order(post_number)
|
|
|
|
sql = <<~SQL
|
2019-02-27 07:49:07 -06:00
|
|
|
SELECT posts.sort_order
|
|
|
|
FROM posts
|
|
|
|
WHERE posts.post_number = #{post_number.to_i}
|
|
|
|
AND posts.topic_id = #{@topic.id.to_i}
|
|
|
|
LIMIT 1
|
2018-07-13 01:25:12 -05:00
|
|
|
SQL
|
|
|
|
|
|
|
|
sort_order = DB.query_single(sql).first
|
|
|
|
|
|
|
|
if !sort_order
|
|
|
|
sql = <<~SQL
|
2019-02-27 07:49:07 -06:00
|
|
|
SELECT posts.sort_order
|
|
|
|
FROM posts
|
|
|
|
WHERE posts.topic_id = #{@topic.id.to_i}
|
|
|
|
ORDER BY @(post_number - #{post_number.to_i})
|
|
|
|
LIMIT 1
|
2018-07-13 01:25:12 -05:00
|
|
|
SQL
|
|
|
|
|
|
|
|
sort_order = DB.query_single(sql).first
|
|
|
|
end
|
|
|
|
|
|
|
|
sort_order
|
2018-07-11 02:41:26 -05:00
|
|
|
end
|
|
|
|
|
2015-09-10 15:01:23 -05:00
|
|
|
def filter_post_types(posts)
|
2015-09-21 17:50:52 -05:00
|
|
|
visible_types = Topic.visible_post_types(@user)
|
2015-09-10 15:01:23 -05:00
|
|
|
|
|
|
|
if @user.present?
|
2016-04-20 14:29:27 -05:00
|
|
|
posts.where("posts.user_id = ? OR post_type IN (?)", @user.id, visible_types)
|
2015-09-10 15:01:23 -05:00
|
|
|
else
|
|
|
|
posts.where(post_type: visible_types)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-07-11 02:41:26 -05:00
|
|
|
def filter_posts_by_post_number(post_number, asc)
|
2018-07-13 01:25:12 -05:00
|
|
|
sort_order = get_sort_order(post_number)
|
|
|
|
|
2018-07-11 02:41:26 -05:00
|
|
|
posts =
|
|
|
|
if asc
|
|
|
|
@filtered_posts
|
2018-07-13 01:25:12 -05:00
|
|
|
.where("sort_order > ?", sort_order)
|
2018-07-11 02:41:26 -05:00
|
|
|
.order(sort_order: :asc)
|
|
|
|
else
|
|
|
|
@filtered_posts
|
2018-07-13 01:25:12 -05:00
|
|
|
.where("sort_order < ?", sort_order)
|
2018-07-11 02:41:26 -05:00
|
|
|
.order(sort_order: :desc)
|
|
|
|
end
|
|
|
|
|
2018-06-28 01:54:54 -05:00
|
|
|
posts = posts.limit(@limit) if !@skip_limit
|
2018-07-11 02:41:26 -05:00
|
|
|
filter_posts_by_ids(posts.pluck(:id))
|
|
|
|
|
|
|
|
@posts = @posts.unscope(:order).order(sort_order: :desc) if !asc
|
|
|
|
end
|
|
|
|
|
2013-06-20 16:20:08 -05:00
|
|
|
def filter_posts_by_ids(post_ids)
|
|
|
|
# TODO: Sort might be off
|
2014-05-29 06:55:55 -05:00
|
|
|
@posts = Post.where(id: post_ids, topic_id: @topic.id)
|
2017-08-04 10:23:53 -05:00
|
|
|
.includes({ user: :primary_group }, :reply_to_user, :deleted_by, :incoming_email, :topic)
|
2013-06-20 16:20:08 -05:00
|
|
|
.order('sort_order')
|
2015-09-10 15:01:23 -05:00
|
|
|
@posts = filter_post_types(@posts)
|
2014-07-15 16:02:43 -05:00
|
|
|
@posts = @posts.with_deleted if @guardian.can_see_deleted_posts?
|
2013-06-20 16:20:08 -05:00
|
|
|
@posts
|
|
|
|
end
|
|
|
|
|
2018-05-28 04:06:47 -05:00
|
|
|
def find_topic(topic_or_topic_id)
|
|
|
|
if topic_or_topic_id.is_a?(Topic)
|
|
|
|
topic_or_topic_id
|
|
|
|
else
|
|
|
|
# with_deleted covered in #check_and_raise_exceptions
|
|
|
|
finder = Topic.with_deleted.where(id: topic_or_topic_id).includes(:category)
|
|
|
|
finder.first
|
|
|
|
end
|
2013-02-10 12:50:26 -06:00
|
|
|
end
|
2013-07-12 03:33:45 -05:00
|
|
|
|
2013-12-04 14:56:09 -06:00
|
|
|
def unfiltered_posts
|
2015-09-10 15:01:23 -05:00
|
|
|
result = filter_post_types(@topic.posts)
|
2014-07-15 16:02:43 -05:00
|
|
|
result = result.with_deleted if @guardian.can_see_deleted_posts?
|
2016-05-03 14:19:59 -05:00
|
|
|
result = result.where("user_id IS NOT NULL") if @exclude_deleted_users
|
|
|
|
result = result.where(hidden: false) if @exclude_hidden
|
2013-12-04 14:56:09 -06:00
|
|
|
result
|
|
|
|
end
|
|
|
|
|
2013-07-12 03:33:45 -05:00
|
|
|
def setup_filtered_posts
|
2013-12-04 14:56:09 -06:00
|
|
|
# Certain filters might leave gaps between posts. If that's true, we can return a gap structure
|
|
|
|
@contains_gaps = false
|
|
|
|
@filtered_posts = unfiltered_posts
|
|
|
|
|
2019-04-10 05:54:59 -05:00
|
|
|
sql = <<~SQL
|
|
|
|
SELECT ignored_user_id
|
|
|
|
FROM ignored_users as ig
|
|
|
|
JOIN users as u ON u.id = ig.ignored_user_id
|
|
|
|
WHERE ig.user_id = :current_user_id
|
|
|
|
AND ig.ignored_user_id <> :current_user_id
|
|
|
|
AND NOT u.admin
|
|
|
|
AND NOT u.moderator
|
|
|
|
SQL
|
2019-03-20 05:18:46 -05:00
|
|
|
|
2019-04-10 05:54:59 -05:00
|
|
|
ignored_user_ids = DB.query_single(sql, current_user_id: @user&.id)
|
2019-03-04 08:29:05 -06:00
|
|
|
|
2019-04-10 05:54:59 -05:00
|
|
|
if ignored_user_ids.present?
|
|
|
|
@filtered_posts = @filtered_posts.where.not("user_id IN (?) AND id <> ?", ignored_user_ids, first_post_id)
|
|
|
|
@contains_gaps = true
|
2019-02-27 07:49:07 -06:00
|
|
|
end
|
|
|
|
|
2013-12-04 14:56:09 -06:00
|
|
|
# Filters
|
2018-06-25 22:05:25 -05:00
|
|
|
if @filter == 'summary'
|
2015-01-30 00:19:42 -06:00
|
|
|
@filtered_posts = @filtered_posts.summary(@topic.id)
|
2018-06-25 22:05:25 -05:00
|
|
|
@contains_gaps = true
|
2013-12-04 14:56:09 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
if @best.present?
|
2015-07-24 15:39:03 -05:00
|
|
|
@filtered_posts = @filtered_posts.where('posts.post_type = ?', Post.types[:regular])
|
2013-12-04 14:56:09 -06:00
|
|
|
@contains_gaps = true
|
|
|
|
end
|
|
|
|
|
2014-07-15 16:02:43 -05:00
|
|
|
# Username filters
|
2013-12-04 14:56:09 -06:00
|
|
|
if @username_filters.present?
|
|
|
|
usernames = @username_filters.map { |u| u.downcase }
|
2018-06-28 21:33:08 -05:00
|
|
|
|
|
|
|
@filtered_posts = @filtered_posts.where('
|
|
|
|
posts.post_number = 1
|
|
|
|
OR posts.user_id IN (SELECT u.id FROM users u WHERE u.username_lower IN (?))
|
|
|
|
', usernames)
|
|
|
|
|
2013-12-04 14:56:09 -06:00
|
|
|
@contains_gaps = true
|
|
|
|
end
|
|
|
|
|
2014-07-15 16:02:43 -05:00
|
|
|
# Deleted
|
|
|
|
# This should be last - don't want to tell the admin about deleted posts that clicking the button won't show
|
|
|
|
# copy the filter for has_deleted? method
|
|
|
|
@predelete_filtered_posts = @filtered_posts.spawn
|
2018-06-28 21:33:08 -05:00
|
|
|
|
2014-07-15 16:02:43 -05:00
|
|
|
if @guardian.can_see_deleted_posts? && !@show_deleted && has_deleted?
|
2018-06-28 21:33:08 -05:00
|
|
|
@filtered_posts = @filtered_posts.where(
|
|
|
|
"posts.deleted_at IS NULL OR posts.post_number = 1"
|
|
|
|
)
|
|
|
|
|
2014-07-15 16:02:43 -05:00
|
|
|
@contains_gaps = true
|
|
|
|
end
|
|
|
|
|
2013-07-12 03:33:45 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def check_and_raise_exceptions
|
|
|
|
raise Discourse::NotFound if @topic.blank?
|
|
|
|
# Special case: If the topic is private and the user isn't logged in, ask them
|
|
|
|
# to log in!
|
2018-02-25 10:31:51 -06:00
|
|
|
if @topic.present? && @topic.private_message? && @user.blank?
|
|
|
|
raise Discourse::NotLoggedIn.new
|
2013-07-12 03:33:45 -05:00
|
|
|
end
|
2018-02-25 10:31:51 -06:00
|
|
|
# can user see this topic?
|
2017-09-14 13:08:16 -05:00
|
|
|
raise Discourse::InvalidAccess.new("can't see #{@topic}", @topic) unless @guardian.can_see?(@topic)
|
2018-02-25 10:31:51 -06:00
|
|
|
# log personal message views
|
|
|
|
if SiteSetting.log_personal_messages_views && @topic.present? && @topic.private_message? && @topic.all_allowed_users.where(id: @user.id).blank?
|
2018-03-12 06:10:17 -05:00
|
|
|
unless UserHistory.where(acting_user_id: @user.id, action: UserHistory.actions[:check_personal_message], topic_id: @topic.id).where("created_at > ?", 1.hour.ago).exists?
|
2018-03-10 21:51:46 -06:00
|
|
|
StaffActionLogger.new(@user).log_check_personal_message(@topic)
|
|
|
|
end
|
2018-02-25 10:31:51 -06:00
|
|
|
end
|
2013-07-12 03:33:45 -05:00
|
|
|
end
|
2013-02-05 13:16:51 -06:00
|
|
|
end
|