2013-02-05 14:16:51 -05:00
require_dependency 'guardian'
require_dependency 'topic_query'
2013-07-12 10:33:45 +02:00
require_dependency 'filter_best_posts'
2013-03-08 15:04:37 -05:00
require_dependency 'summarize'
2013-12-04 15:56:09 -05:00
require_dependency 'gaps'
2013-02-05 14:16:51 -05:00
class TopicView
2013-06-20 17:20:08 -04:00
attr_reader :topic , :posts , :guardian , :filtered_posts
2013-03-26 14:18:35 -04:00
attr_accessor :draft , :draft_key , :draft_sequence
2013-02-05 14:16:51 -05:00
def initialize ( topic_id , user = nil , options = { } )
2013-07-11 16:38:46 -04:00
@user = user
2013-02-10 13:50:26 -05:00
@topic = find_topic ( topic_id )
2013-07-11 16:38:46 -04:00
@guardian = Guardian . new ( @user )
2013-07-12 10:33:45 +02:00
check_and_raise_exceptions
2013-06-20 17:20:08 -04:00
2013-08-13 16:29:25 +02:00
options . each do | key , value |
2013-10-28 11:42:07 +05:30
self . instance_variable_set ( " @ #{ key } " . to_sym , value )
2013-08-13 16:29:25 +02:00
end
@page = @page . to_i
@page = 1 if @page . zero?
@limit || = SiteSetting . posts_per_page
2013-02-05 14:16:51 -05:00
2013-07-12 10:33:45 +02:00
setup_filtered_posts
2013-02-05 14:16:51 -05:00
@initial_load = true
2013-03-26 14:18:35 -04:00
@index_reverse = false
2013-02-05 14:16:51 -05:00
2013-02-10 13:50:26 -05:00
filter_posts ( options )
@draft_key = @topic . draft_key
2013-07-11 16:38:46 -04:00
@draft_sequence = DraftSequence . current ( @user , @draft_key )
2013-02-05 14:16:51 -05:00
end
2013-02-10 13:50:26 -05:00
def canonical_path
path = @topic . relative_url
path << if @post_number
2014-01-03 12:52:24 -05:00
page = ( ( @post_number . to_i - 1 ) / @limit ) + 1
2013-02-10 13:50:26 -05:00
( page > 1 ) ? " ?page= #{ page } " : " "
else
( @page && @page . to_i > 1 ) ? " ?page= #{ @page } " : " "
end
path
2013-02-05 14:16:51 -05:00
end
2013-12-04 15:56:09 -05:00
def contains_gaps?
@contains_gaps
end
def gaps
return unless @contains_gaps
Gaps . new ( filtered_post_ids , unfiltered_posts . order ( :sort_order ) . pluck ( :id ) )
end
2013-07-05 14:45:54 -04:00
def last_post
return nil if @posts . blank?
@last_post || = @posts . last
end
2014-03-03 12:56:37 -05:00
def prev_page
if @page && @page > 1
@page - 1
else
nil
end
end
2013-02-10 13:50:26 -05:00
def next_page
2013-07-05 14:45:54 -04:00
@next_page || = begin
if last_post && ( @topic . highest_post_number > last_post . post_number )
@page + 1
end
2013-02-10 13:50:26 -05:00
end
end
2014-03-03 12:56:37 -05:00
def prev_page_path
if prev_page > 1
" #{ @topic . relative_url } ?page= #{ prev_page } "
else
@topic . relative_url
end
end
2013-02-10 13:50:26 -05:00
def next_page_path
" #{ @topic . relative_url } ?page= #{ next_page } "
end
2013-03-07 17:31:06 -05:00
def absolute_url
" #{ Discourse . base_url } #{ @topic . relative_url } "
end
2013-02-10 13:50:26 -05:00
def relative_url
@topic . relative_url
end
def title
@topic . title
end
2013-07-08 12:21:39 -04:00
def desired_post
return @desired_post if @desired_post . present?
2013-03-07 17:31:06 -05:00
return nil if posts . blank?
2013-07-08 12:21:39 -04:00
@desired_post = posts . detect { | p | p . post_number == @post_number . to_i }
@desired_post || = posts . first
@desired_post
end
def summary
return nil if desired_post . blank?
2013-09-05 09:33:30 +10:00
# TODO, this is actually quite slow, should be cached in the post table
2013-07-08 12:21:39 -04:00
Summarize . new ( desired_post . cooked ) . summary
2013-03-07 17:31:06 -05:00
end
2013-03-08 15:58:37 -05:00
def image_url
2013-07-08 12:21:39 -04:00
return nil if desired_post . blank?
2013-09-03 17:19:29 -04:00
desired_post . user . try ( :small_avatar_url )
2013-03-08 15:58:37 -05:00
end
2013-02-10 13:50:26 -05:00
def filter_posts ( opts = { } )
2013-03-26 11:58:49 -04:00
return filter_posts_near ( opts [ :post_number ] . to_i ) if opts [ :post_number ] . present?
2013-06-20 17:20:08 -04:00
return filter_posts_by_ids ( opts [ :post_ids ] ) if opts [ :post_ids ] . present?
return filter_best ( opts [ :best ] , opts ) if opts [ :best ] . present?
2013-07-01 21:29:45 +10:00
2013-03-26 11:58:49 -04:00
filter_posts_paged ( opts [ :page ] . to_i )
end
2014-02-10 16:59:36 -05: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
result
end
2013-03-27 22:53:11 -07:00
2013-03-26 11:58:49 -04:00
# Find the sort order for a post in the topic
def sort_order_for_post_number ( post_number )
Post . where ( topic_id : @topic . id , post_number : post_number )
. with_deleted
2013-03-27 22:53:11 -07:00
. select ( :sort_order )
2013-03-26 11:58:49 -04:00
. first
. try ( :sort_order )
2013-02-05 14:16:51 -05:00
end
2013-02-10 13:50:26 -05:00
# Filter to all posts near a particular post number
def filter_posts_near ( post_number )
2013-08-13 16:29:25 +02:00
min_idx , max_idx = get_minmax_ids ( post_number )
2013-03-26 11:58:49 -04:00
filter_posts_in_range ( min_idx , max_idx )
2013-02-10 13:50:26 -05:00
end
2013-02-05 14:16:51 -05:00
def filter_posts_paged ( page )
2013-04-04 00:12:27 +02:00
page = [ page , 1 ] . max
2014-01-03 12:52:24 -05: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
max = ( min + @limit ) - 1
2013-07-05 14:45:54 -04:00
2013-03-26 11:58:49 -04:00
filter_posts_in_range ( min , max )
2013-02-05 14:16:51 -05:00
end
2013-07-01 21:29:45 +10:00
def filter_best ( max , opts = { } )
2013-07-12 10:33:45 +02:00
filter = FilterBestPosts . new ( @topic , @filtered_posts , max , opts )
@posts = filter . posts
@filtered_posts = filter . filtered_posts
2013-02-05 14:16:51 -05:00
end
def read? ( post_number )
read_posts_set . include? ( post_number )
end
def topic_user
@topic_user || = begin
return nil if @user . blank?
2014-05-06 14:41:59 +01:00
@topic . topic_users . find_by ( user_id : @user . id )
2013-02-05 14:16:51 -05:00
end
end
2013-03-26 14:06:24 -04:00
def post_counts_by_user
@post_counts_by_user || = Post . where ( topic_id : @topic . id ) . group ( :user_id ) . order ( 'count_all desc' ) . limit ( 24 ) . count
2013-02-05 14:16:51 -05:00
end
def participants
@participants || = begin
participants = { }
2013-03-26 14:06:24 -04:00
User . where ( id : post_counts_by_user . map { | k , v | k } ) . each { | u | participants [ u . id ] = u }
2013-02-05 14:16:51 -05:00
participants
end
end
def all_post_actions
2013-02-25 19:42:20 +03:00
@all_post_actions || = PostAction . counts_for ( posts , @user )
2013-02-05 14:16:51 -05:00
end
def links
2013-11-15 12:15:46 -05:00
@links || = TopicLink . topic_map ( guardian , @topic . id )
2013-02-05 14:16:51 -05:00
end
def link_counts
2013-06-05 16:10:26 +10:00
@link_counts || = TopicLink . counts_for ( guardian , @topic , posts )
2013-02-25 19:42:20 +03:00
end
2013-02-05 14:16:51 -05: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
def suggested_topics
return nil if topic . private_message?
@suggested_topics || = TopicQuery . new ( @user ) . list_suggested_for ( topic )
end
2013-02-15 12:58:14 +11:00
# This is pending a larger refactor, that allows custom orders
2013-02-25 19:42:20 +03: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
2013-02-15 12:58:14 +11:00
# streams
def highest_post_number
2013-03-26 11:58:49 -04:00
@highest_post_number || = @filtered_posts . maximum ( :post_number )
2013-02-15 12:58:14 +11:00
end
2013-02-21 10:20:00 -08:00
def recent_posts
2013-03-26 11:58:49 -04:00
@filtered_posts . by_newest . with_user . first ( 25 )
2013-02-21 10:20:00 -08:00
end
2013-06-28 12:20:06 -04:00
def current_post_ids
@current_post_ids || = if @posts . is_a? ( Array )
@posts . map { | p | p . id }
else
2013-10-04 18:06:32 +10:00
@posts . pluck ( :post_number )
2013-06-28 12:20:06 -04:00
end
end
2013-08-13 16:29:25 +02:00
def filtered_post_ids
@filtered_post_ids || = filter_post_ids_by ( :sort_order )
end
2013-02-05 14:16:51 -05:00
protected
2013-02-10 13:50:26 -05: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 14:16:51 -05:00
2013-10-04 18:06:32 +10:00
post_numbers = PostTiming
2013-03-26 11:58:49 -04:00
. where ( topic_id : @topic . id , user_id : @user . id )
2013-06-28 12:20:06 -04:00
. where ( post_number : current_post_ids )
2013-03-26 11:58:49 -04:00
. pluck ( :post_number )
2013-02-05 14:16:51 -05:00
2013-03-26 11:58:49 -04:00
post_numbers . each { | pn | result << pn }
2013-02-10 13:50:26 -05:00
result
2013-02-05 14:16:51 -05:00
end
2013-02-10 13:50:26 -05:00
end
private
2013-06-20 17:20:08 -04:00
def filter_posts_by_ids ( post_ids )
# TODO: Sort might be off
@posts = Post . where ( id : post_ids )
. includes ( :user )
. includes ( :reply_to_user )
. order ( 'sort_order' )
@posts = @posts . with_deleted if @user . try ( :staff? )
@posts
end
2013-02-10 13:50:26 -05:00
def filter_posts_in_range ( min , max )
2013-05-18 13:11:01 -07:00
post_count = ( filtered_post_ids . length - 1 )
2013-03-26 11:58:49 -04:00
2013-05-18 13:11:01 -07:00
max = [ max , post_count ] . min
2013-05-20 10:29:49 +10:00
return @posts = [ ] if min > max
2013-05-18 13:11:01 -07:00
min = [ [ min , max ] . min , 0 ] . max
2013-03-26 11:58:49 -04:00
2013-06-20 17:20:08 -04:00
@posts = filter_posts_by_ids ( filtered_post_ids [ min .. max ] )
2013-03-26 11:58:49 -04:00
@posts
2013-02-10 13:50:26 -05:00
end
2013-02-05 14:16:51 -05:00
2013-02-10 13:50:26 -05:00
def find_topic ( topic_id )
2013-07-11 16:38:46 -04:00
finder = Topic . where ( id : topic_id ) . includes ( :category )
finder = finder . with_deleted if @user . try ( :staff? )
finder . first
2013-02-10 13:50:26 -05:00
end
2013-07-12 10:33:45 +02:00
2013-12-04 15:56:09 -05:00
def unfiltered_posts
2013-12-09 14:28:32 -05:00
result = @topic . posts
2013-12-04 15:56:09 -05:00
result = result . with_deleted if @user . try ( :staff? )
result
end
2013-07-12 10:33:45 +02:00
def setup_filtered_posts
2013-12-04 15:56:09 -05: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
2013-09-03 17:19:29 -04:00
@filtered_posts = @filtered_posts . with_deleted if @user . try ( :staff? )
2013-12-04 15:56:09 -05:00
# Filters
if @filter == 'summary'
@filtered_posts = @filtered_posts . summary
@contains_gaps = true
end
if @best . present?
@filtered_posts = @filtered_posts . where ( 'posts.post_type <> ?' , Post . types [ :moderator_action ] )
@contains_gaps = true
end
if @username_filters . present?
usernames = @username_filters . map { | u | u . downcase }
@filtered_posts = @filtered_posts . where ( 'post_number = 1 or user_id in (select u.id from users u where username_lower in (?))' , usernames )
@contains_gaps = true
end
2013-07-12 10:33:45 +02: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!
if @topic . present? && @topic . private_message? && @user . blank?
raise Discourse :: NotLoggedIn . new
end
guardian . ensure_can_see! ( @topic )
end
2013-08-13 16:29:25 +02:00
def filter_post_ids_by ( sort_order )
@filtered_posts . order ( sort_order ) . pluck ( :id )
end
def get_minmax_ids ( post_number )
# Find the closest number we have
closest_index = closest_post_to ( post_number )
return nil if closest_index . nil?
# Make sure to get at least one post before, even with rounding
2014-01-03 12:52:24 -05:00
posts_before = ( @limit . to_f / 4 ) . floor
2013-08-13 16:29:25 +02:00
posts_before = 1 if posts_before . zero?
min_idx = closest_index - posts_before
min_idx = 0 if min_idx < 0
2014-01-03 12:52:24 -05:00
max_idx = min_idx + ( @limit - 1 )
2013-08-13 16:29:25 +02:00
# Get a full page even if at the end
ensure_full_page ( min_idx , max_idx )
end
def ensure_full_page ( min , max )
upper_limit = ( filtered_post_ids . length - 1 )
if max > = upper_limit
2014-01-03 12:52:24 -05:00
return ( upper_limit - @limit ) + 1 , upper_limit
2013-08-13 16:29:25 +02:00
else
return min , max
end
end
def closest_post_to ( post_number )
closest_posts = filter_post_ids_by ( " @(post_number - #{ post_number } ) " )
return nil if closest_posts . empty?
filtered_post_ids . index ( closest_posts . first )
end
2013-02-05 14:16:51 -05:00
end