2013-02-25 19:42:20 +03:00
#
2015-09-02 20:25:18 +02:00
# Helps us find topics.
# Returns a TopicList object containing the topics found.
2013-02-05 14:16:51 -05:00
#
2015-09-02 20:25:18 +02:00
2013-02-05 14:16:51 -05:00
require_dependency 'topic_list'
2013-07-12 14:38:20 -04:00
require_dependency 'suggested_topics_builder'
2013-11-13 12:26:32 -05:00
require_dependency 'topic_query_sql'
2016-02-03 18:50:05 +11:00
require_dependency 'avatar_lookup'
2013-02-05 14:16:51 -05:00
class TopicQuery
2015-09-02 20:25:18 +02:00
VALID_OPTIONS = % i ( except_topic_ids
exclude_category_ids
2013-11-11 19:35:57 -05:00
limit
page
per_page
2014-06-05 15:30:24 +02:00
min_posts
max_posts
2013-11-11 19:35:57 -05:00
topic_ids
visible
category
2016-05-04 14:02:47 -04:00
tags
2016-07-20 16:21:43 -04:00
no_tags
2014-04-16 12:05:54 -04:00
order
ascending
2013-12-13 17:18:28 -05:00
no_subcategories
2014-02-07 17:01:31 -05:00
no_definitions
2014-05-16 00:31:45 +10:00
status
2014-07-17 09:29:09 +10:00
state
2014-05-16 00:31:45 +10:00
search
2014-12-15 11:54:26 -05:00
slow_platform
2015-01-07 18:20:10 +11:00
filter
2015-12-10 11:39:33 +11:00
group_name
2015-09-02 20:25:18 +02:00
q )
2013-02-05 14:16:51 -05:00
2014-04-16 12:05:54 -04:00
# Maps `order` to a columns in `topics`
2013-11-13 14:17:06 -05:00
SORTABLE_MAPPING = {
'likes' => 'like_count' ,
2014-10-03 13:16:53 +10:00
'op_likes' => 'op_likes' ,
2013-11-13 14:17:06 -05:00
'views' => 'views' ,
'posts' => 'posts_count' ,
2014-08-18 13:26:12 -04:00
'activity' => 'bumped_at' ,
2013-11-14 15:50:36 -05:00
'posters' => 'participant_count' ,
2014-08-27 12:42:54 -04:00
'category' => 'category_id' ,
'created' => 'created_at'
2013-11-13 14:17:06 -05:00
}
2015-12-21 11:43:17 -05:00
cattr_accessor :results_filter_callbacks
self . results_filter_callbacks = []
2013-07-16 21:20:18 +02:00
def initialize ( user = nil , options = {})
options . assert_valid_keys ( VALID_OPTIONS )
2015-09-21 15:14:05 -04:00
@options = options . dup
2013-07-16 21:20:18 +02:00
@user = user
2015-09-23 13:13:34 +10:00
@guardian = Guardian . new ( @user )
2013-02-05 14:16:51 -05:00
end
2014-02-21 14:17:45 -05:00
def joined_topic_user ( list = nil )
( list || Topic ) . joins ( "LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{ @user . id . to_i } )" )
end
2013-02-05 14:16:51 -05:00
# Return a list of suggested topics for a topic
2013-02-25 19:42:20 +03:00
def list_suggested_for ( topic )
2016-02-03 18:50:05 +11:00
return if topic . private_message? && ! @user
2013-07-12 14:38:20 -04:00
builder = SuggestedTopicsBuilder . new ( topic )
2013-02-05 14:16:51 -05:00
2016-02-03 18:50:05 +11:00
pm_params =
if topic . private_message?
group_ids = topic . topic_allowed_groups
. where ( 'group_id IN (SELECT group_id FROM group_users WHERE user_id = :user_id)' , user_id : @user . id )
. pluck ( :group_id )
{
topic : topic ,
my_group_ids : group_ids ,
target_group_ids : topic . topic_allowed_groups . pluck ( :group_id ),
target_user_ids : topic . topic_allowed_users . pluck ( :user_id ) - [ @user . id ]
}
end
2013-07-12 14:38:20 -04:00
# When logged in we start with different results
2013-07-16 21:20:18 +02:00
if @user
2016-02-03 18:50:05 +11:00
if topic . private_message?
builder . add_results ( new_messages (
pm_params . merge ( count : builder . results_left )
)) unless builder . full?
builder . add_results ( unread_messages (
pm_params . merge ( count : builder . results_left )
)) unless builder . full?
else
builder . add_results ( unread_results ( topic : topic , per_page : builder . results_left ), :high )
builder . add_results ( new_results ( topic : topic , per_page : builder . category_results_left )) unless builder . full?
end
2013-02-05 14:16:51 -05:00
end
2016-02-03 18:50:05 +11:00
if topic . private_message?
builder . add_results ( related_messages_group (
pm_params . merge ( count : [ 3 , builder . results_left ]. max ,
exclude : builder . excluded_topic_ids )
)) if pm_params [ :my_group_ids ]. present?
builder . add_results ( related_messages_user (
pm_params . merge ( count : [ 3 , builder . results_left ]. max ,
exclude : builder . excluded_topic_ids )
))
else
builder . add_results ( random_suggested ( topic , builder . results_left , builder . excluded_topic_ids )) unless builder . full?
end
params = { unordered : true }
if topic . private_message?
params [ :preload_posters ] = true
end
create_list ( :suggested , params , builder . results )
2013-02-05 14:16:51 -05:00
end
2013-03-27 16:17:49 -04:00
# The latest view of topics
def list_latest
2014-10-08 12:44:47 -04:00
create_list ( :latest , {}, latest_results )
2013-02-05 14:16:51 -05:00
end
def list_read
2013-04-02 16:52:51 -04:00
create_list ( :read , unordered : true ) do | topics |
topics . order ( 'COALESCE(tu.last_visited_at, topics.bumped_at) DESC' )
2013-02-05 14:16:51 -05:00
end
end
def list_new
2015-02-23 16:50:52 +11:00
create_list ( :new , { unordered : true }, new_results )
2013-02-05 14:16:51 -05:00
end
def list_unread
2015-02-23 16:50:52 +11:00
create_list ( :unread , { unordered : true }, unread_results )
2013-02-05 14:16:51 -05:00
end
def list_posted
2014-03-29 15:26:13 -07:00
create_list ( :posted ) { | l | l . where ( 'tu.posted' ) }
2013-02-05 14:16:51 -05:00
end
2015-01-25 15:53:11 +11:00
def list_bookmarks
create_list ( :bookmarks ) { | l | l . where ( 'tu.bookmarked' ) }
end
2013-12-27 18:10:35 +01:00
def list_top_for ( period )
score = " #{ period } _score"
2013-12-24 00:50:36 +01:00
create_list ( :top , unordered : true ) do | topics |
2014-01-18 19:03:09 +01:00
topics = topics . joins ( :top_topic ) . where ( "top_topics. #{ score } > 0" )
2014-09-05 15:20:39 +10:00
if period == :yearly && @user . try ( :trust_level ) == TrustLevel [ 0 ]
2014-01-18 19:03:09 +01:00
topics . order ( TopicQuerySQL . order_top_with_pinned_category_for ( score ))
else
topics . order ( TopicQuerySQL . order_top_for ( score ))
end
2013-12-24 00:50:36 +01:00
end
2014-01-14 01:02:14 +01:00
end
2013-07-24 17:15:21 -04:00
def list_topics_by ( user )
2015-03-23 18:12:37 -04:00
@options [ :filtered_to_user ] = user . id
2013-07-24 17:15:21 -04:00
create_list ( :user_topics ) do | topics |
topics . where ( user_id : user . id )
end
end
2015-12-23 11:09:17 +11:00
def not_archived ( list , user )
list . joins ( "LEFT JOIN user_archived_messages um
ON um.user_id = #{ user . id . to_i } AND um.topic_id = topics.id" )
. where ( 'um.user_id IS NULL' )
end
2013-08-24 16:58:16 -04:00
def list_private_messages ( user )
2015-12-07 18:37:03 +01:00
list = private_messages_for ( user , :user )
2015-12-23 11:09:17 +11:00
list = not_archived ( list , user )
. where ( 'NOT (topics.participant_count = 1 AND topics.user_id = ?)' , user . id )
create_list ( :private_messages , {}, list )
end
def list_private_messages_archive ( user )
list = private_messages_for ( user , :user )
list = list . joins ( :user_archived_messages ) . where ( 'user_archived_messages.user_id = ?' , user . id )
2014-10-08 12:44:47 -04:00
create_list ( :private_messages , {}, list )
2013-08-24 16:58:16 -04:00
end
def list_private_messages_sent ( user )
2015-12-07 18:37:03 +01:00
list = private_messages_for ( user , :user )
2015-12-23 11:09:17 +11:00
list = list . where ( 'EXISTS (
SELECT 1 FROM posts
WHERE posts.topic_id = topics.id AND
posts.user_id = ?
)' , user . id )
list = not_archived ( list , user )
2014-10-08 12:44:47 -04:00
create_list ( :private_messages , {}, list )
2013-08-24 16:58:16 -04:00
end
2013-08-30 12:32:05 -04:00
def list_private_messages_unread ( user )
2015-12-07 18:37:03 +01:00
list = private_messages_for ( user , :user )
2014-05-02 22:36:52 +02:00
list = list . where ( "tu.last_read_post_number IS NULL OR tu.last_read_post_number < topics.highest_post_number" )
2014-10-08 12:44:47 -04:00
create_list ( :private_messages , {}, list )
2013-08-30 12:32:05 -04:00
end
2013-07-24 17:15:21 -04:00
2015-12-10 11:39:33 +11:00
def list_private_messages_group ( user )
2015-12-07 18:37:03 +01:00
list = private_messages_for ( user , :group )
2015-12-23 11:09:17 +11:00
group_id = Group . where ( 'name ilike ?' , @options [ :group_name ] ) . pluck ( :id ) . first
list = list . joins ( "LEFT JOIN group_archived_messages gm ON gm.topic_id = topics.id AND
gm.group_id = #{ group_id . to_i } " )
list = list . where ( "gm.id IS NULL" )
create_list ( :private_messages , {}, list )
end
def list_private_messages_group_archive ( user )
list = private_messages_for ( user , :group )
group_id = Group . where ( 'name ilike ?' , @options [ :group_name ] ) . pluck ( :id ) . first
list = list . joins ( "JOIN group_archived_messages gm ON gm.topic_id = topics.id AND
gm.group_id = #{ group_id . to_i } " )
2015-12-07 18:37:03 +01:00
create_list ( :private_messages , {}, list )
end
2015-02-23 16:50:52 +11:00
def list_category_topic_ids ( category )
2015-02-25 14:24:25 +11:00
query = default_results ( category : category . id )
2015-03-12 10:42:26 +11:00
pinned_ids = query . where ( 'pinned_at IS NOT NULL AND category_id = ?' , category . id )
2015-06-10 14:36:47 -04:00
. limit ( nil )
2015-03-12 10:42:26 +11:00
. order ( 'pinned_at DESC' ) . pluck ( :id )
non_pinned_ids = query . where ( 'pinned_at IS NULL OR category_id <> ?' , category . id ) . pluck ( :id )
2015-06-10 14:36:47 -04:00
( pinned_ids + non_pinned_ids )
2013-02-05 14:16:51 -05:00
end
2013-02-27 19:36:12 -08:00
def list_new_in_category ( category )
2014-02-04 15:55:30 -05:00
create_list ( :new_in_category , unordered : true , category : category . id ) do | list |
list . by_newest . first ( 25 )
2014-01-17 23:52:06 +01:00
end
2013-02-27 19:36:12 -08:00
end
2013-07-16 21:20:18 +02:00
def self . new_filter ( list , treat_as_new_topic_start_date )
2013-05-23 15:21:07 +10:00
list . where ( "topics.created_at >= :created_at" , created_at : treat_as_new_topic_start_date )
. where ( "tu.last_read_post_number IS NULL" )
. where ( "COALESCE(tu.notification_level, :tracking) >= :tracking" , tracking : TopicUser . notification_levels [ :tracking ] )
end
def self . unread_filter ( list )
2014-08-18 16:46:16 -04:00
list . where ( "tu.last_read_post_number < topics.highest_post_number" )
2013-05-23 15:21:07 +10:00
. where ( "COALESCE(tu.notification_level, :regular) >= :tracking" , regular : TopicUser . notification_levels [ :regular ] , tracking : TopicUser . notification_levels [ :tracking ] )
2013-05-21 16:39:51 +10:00
end
2015-02-23 16:50:52 +11:00
def prioritize_pinned_topics ( topics , options )
2015-03-12 10:42:26 +11:00
pinned_clause = options [ :category_id ] ? "topics.category_id = #{ options [ :category_id ]. to_i } AND" : "pinned_globally AND "
2015-02-23 16:50:52 +11:00
pinned_clause << " pinned_at IS NOT NULL "
if @user
pinned_clause << " AND (topics.pinned_at > tu.cleared_pinned_at OR tu.cleared_pinned_at IS NULL)"
end
unpinned_topics = topics . where ( "NOT ( #{ pinned_clause } )" )
2015-06-25 16:25:50 -04:00
pinned_topics = topics . dup . offset ( nil ) . where ( pinned_clause )
2015-02-23 16:50:52 +11:00
per_page = options [ :per_page ] || per_page_setting
limit = per_page unless options [ :limit ] == false
page = options [ :page ]. to_i
if page == 0
( pinned_topics + unpinned_topics ) [ 0 ... limit ] if limit
else
2015-06-25 16:25:50 -04:00
offset = ( page * per_page ) - pinned_topics . count - 1
2015-02-26 14:48:56 +11:00
offset = 0 unless offset > 0
unpinned_topics . offset ( offset ) . to_a
2015-02-23 16:50:52 +11:00
end
end
2015-01-08 16:44:27 -05:00
def create_list ( filter , options = {}, topics = nil )
topics ||= default_results ( options )
topics = yield ( topics ) if block_given?
2015-02-23 16:50:52 +11:00
options = options . merge ( @options )
2015-12-23 11:09:17 +11:00
if [ "activity" , "default" ]. include? ( options [ :order ] || "activity" ) &&
! options [ :unordered ] &&
filter != :private_messages
2015-02-23 16:50:52 +11:00
topics = prioritize_pinned_topics ( topics , options )
end
2016-02-03 18:50:05 +11:00
topics = topics . to_a
if options [ :preload_posters ]
user_ids = []
topics . each do | ft |
user_ids << ft . user_id << ft . last_post_user_id << ft . featured_user_ids << ft . allowed_user_ids
end
avatar_lookup = AvatarLookup . new ( user_ids )
topics . each do | t |
t . posters = t . posters_summary ( avatar_lookup : avatar_lookup )
end
end
topics . each do | t |
2015-03-31 17:29:07 -04:00
t . allowed_user_ids = filter == :private_messages ? t . allowed_users . map { | u | u . id } : []
2015-02-23 16:50:52 +11:00
end
2016-02-03 18:50:05 +11:00
list = TopicList . new ( filter , @user , topics , options . merge ( @options ))
2015-01-08 16:44:27 -05:00
list . per_page = per_page_setting
list
end
def latest_results ( options = {})
result = default_results ( options )
2015-11-02 15:05:08 +11:00
result = remove_muted_topics ( result , @user ) unless options && options [ :state ] == "muted" . freeze
2015-01-08 16:44:27 -05:00
result = remove_muted_categories ( result , @user , exclude : options [ :category ] )
2016-04-25 15:55:15 -04:00
result = remove_muted_tags ( result , @user , options )
2015-12-21 11:43:17 -05:00
# plugins can remove topics here:
self . class . results_filter_callbacks . each do | filter_callback |
result = filter_callback . call ( :latest , result , @user , options )
end
2015-01-08 16:44:27 -05:00
result
end
def unread_results ( options = {})
result = TopicQuery . unread_filter ( default_results ( options . reverse_merge ( :unordered => true )))
. order ( 'CASE WHEN topics.user_id = tu.user_id THEN 1 ELSE 2 END' )
2015-12-21 11:43:17 -05:00
self . class . results_filter_callbacks . each do | filter_callback |
result = filter_callback . call ( :unread , result , @user , options )
end
2015-01-08 16:44:27 -05:00
suggested_ordering ( result , options )
end
def new_results ( options = {})
2015-02-23 16:50:52 +11:00
# TODO does this make sense or should it be ordered on created_at
# it is ordering on bumped_at now
2016-02-18 16:57:22 +11:00
result = TopicQuery . new_filter ( default_results ( options . reverse_merge ( :unordered => true )), @user . user_option . treat_as_new_topic_start_date )
2015-11-02 09:20:22 +11:00
result = remove_muted_topics ( result , @user )
2015-01-08 16:44:27 -05:00
result = remove_muted_categories ( result , @user , exclude : options [ :category ] )
2016-04-25 15:55:15 -04:00
result = remove_muted_tags ( result , @user , options )
2015-12-21 11:43:17 -05:00
self . class . results_filter_callbacks . each do | filter_callback |
result = filter_callback . call ( :new , result , @user , options )
end
2015-01-08 16:44:27 -05:00
suggested_ordering ( result , options )
end
2013-02-05 14:16:51 -05:00
protected
2014-12-15 11:54:26 -05:00
def per_page_setting
2014-12-19 13:18:26 -05:00
@options [ :slow_platform ] ? 15 : 30
2014-12-15 11:54:26 -05:00
end
2015-12-07 18:37:03 +01:00
def private_messages_for ( user , type )
2013-08-24 16:58:16 -04:00
options = @options
2014-12-15 11:54:26 -05:00
options . reverse_merge! ( per_page : per_page_setting )
2013-08-24 16:58:16 -04:00
2016-06-28 10:01:00 +08:00
result = Topic . includes ( :tags )
2015-12-07 18:37:03 +01:00
if type == :group
2016-06-28 10:01:00 +08:00
result = result . includes ( :allowed_users )
2015-12-10 11:39:33 +11:00
result = result . where ( "topics.id IN (SELECT topic_id FROM topic_allowed_groups
WHERE group_id IN (
SELECT group_id FROM group_users WHERE user_id = #{ user . id . to_i } ) AND
group_id IN (SELECT id FROM groups WHERE name ilike ?)
)" , @options [ :group_name ] )
2015-12-07 18:37:03 +01:00
elsif type == :user
result = result . includes ( :allowed_users )
result = result . where ( "topics.id IN (SELECT topic_id FROM topic_allowed_users WHERE user_id = #{ user . id . to_i } )" )
end
result = result . joins ( "LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{ user . id . to_i } )" )
. order ( "topics.bumped_at DESC" )
. private_messages
2013-08-24 16:58:16 -04:00
result = result . limit ( options [ :per_page ] ) unless options [ :limit ] == false
result = result . visible if options [ :visible ] || @user . nil? || @user . regular?
2016-05-09 16:33:55 -04:00
if options [ :page ]
offset = options [ :page ]. to_i * options [ :per_page ]
result = result . offset ( offset ) if offset > 0
end
2013-08-24 16:58:16 -04:00
result
end
2013-11-13 14:17:06 -05:00
def apply_ordering ( result , options )
2014-04-16 12:05:54 -04:00
sort_column = SORTABLE_MAPPING [ options [ :order ]] || 'default'
sort_dir = ( options [ :ascending ] == "true" ) ? "ASC" : "DESC"
2013-11-13 14:17:06 -05:00
# If we are sorting in the default order desc, we should consider including pinned
# topics. Otherwise, just use bumped_at.
if sort_column == 'default'
2013-11-14 15:50:36 -05:00
if sort_dir == 'DESC'
# If something requires a custom order, for example "unread" which sorts the least read
# to the top, do nothing
return result if options [ :unordered ]
end
2013-11-13 14:17:06 -05:00
sort_column = 'bumped_at'
end
2013-11-14 15:50:36 -05:00
# If we are sorting by category, actually use the name
if sort_column == 'category_id'
2015-02-23 16:50:52 +11:00
# TODO forces a table scan, slow
2013-11-14 15:50:36 -05:00
return result . references ( :categories ) . order ( TopicQuerySQL . order_by_category_sql ( sort_dir ))
end
2014-10-03 13:16:53 +10:00
if sort_column == 'op_likes'
2015-01-05 17:39:49 +11:00
return result . includes ( :first_post ) . order ( "(SELECT like_count FROM posts p3 WHERE p3.topic_id = topics.id AND p3.post_number = 1) #{ sort_dir } " )
2014-10-03 13:16:53 +10:00
end
2016-02-25 10:22:23 -06:00
if sort_column . start_with? ( 'custom_fields' )
field = sort_column . split ( '.' ) [ 1 ]
return result . order ( "(SELECT CASE WHEN EXISTS (SELECT true FROM topic_custom_fields tcf WHERE tcf.topic_id::integer = topics.id::integer AND tcf.name = ' #{ field } ') THEN (SELECT value::integer FROM topic_custom_fields tcf WHERE tcf.topic_id::integer = topics.id::integer AND tcf.name = ' #{ field } ') ELSE 0 END) #{ sort_dir } " )
end
2013-11-13 14:17:06 -05:00
result . order ( "topics. #{ sort_column } #{ sort_dir } " )
end
2014-06-18 11:23:31 +10:00
def get_category_id ( category_id_or_slug )
return nil unless category_id_or_slug
category_id = category_id_or_slug . to_i
category_id = Category . where ( slug : category_id_or_slug ) . pluck ( :id ) . first if category_id == 0
category_id
end
2014-02-21 14:17:45 -05:00
2013-07-16 21:20:18 +02:00
# Create results based on a bunch of default options
def default_results ( options = {})
options . reverse_merge! ( @options )
2014-12-15 11:54:26 -05:00
options . reverse_merge! ( per_page : per_page_setting )
2013-02-05 14:16:51 -05:00
2015-03-23 18:12:37 -04:00
# Whether to return visible topics
options [ :visible ] = true if @user . nil? || @user . regular?
options [ :visible ] = false if @user && @user . id == options [ :filtered_to_user ]
2013-03-06 15:17:07 -05:00
# Start with a list of all topics
2014-11-19 17:46:55 -05:00
result = Topic . unscoped
2013-03-06 15:17:07 -05:00
2013-07-16 21:20:18 +02:00
if @user
result = result . joins ( "LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{ @user . id . to_i } )" )
2014-02-26 11:09:02 -05:00
. references ( 'tu' )
2013-03-06 15:17:07 -05:00
end
2014-06-18 11:23:31 +10:00
category_id = get_category_id ( options [ :category ] )
2014-10-08 12:44:47 -04:00
@options [ :category_id ] = category_id
2014-06-18 11:23:31 +10:00
if category_id
if options [ :no_subcategories ]
result = result . where ( 'categories.id = ?' , category_id )
else
2015-09-02 20:25:18 +02:00
result = result . where ( 'categories.id = :category_id OR (categories.parent_category_id = :category_id AND categories.topic_id <> topics.id)' , category_id : category_id )
2013-11-08 15:05:14 -05:00
end
2014-06-18 11:23:31 +10:00
result = result . references ( :categories )
2013-11-08 15:05:14 -05:00
end
2016-05-04 14:02:47 -04:00
# ALL TAGS: something like this?
# Topic.joins(:tags).where('tags.name in (?)', @options[:tags]).group('topic_id').having('count(*)=?', @options[:tags].size).select('topic_id')
2016-05-26 18:03:36 -04:00
if SiteSetting . tagging_enabled
result = result . preload ( :tags )
if @options [ :tags ] && @options [ :tags ]. size > 0
result = result . joins ( :tags )
# ANY of the given tags:
if @options [ :tags ][ 0 ]. is_a? ( Integer )
result = result . where ( "tags.id in (?)" , @options [ :tags ] )
else
result = result . where ( "tags.name in (?)" , @options [ :tags ] )
end
2016-07-20 16:21:43 -04:00
elsif @options [ :no_tags ]
# the following will do: ("topics"."id" NOT IN (SELECT DISTINCT "topic_tags"."topic_id" FROM "topic_tags"))
result = result . where . not ( :id => TopicTag . select ( :topic_id ) . uniq )
2016-05-04 14:02:47 -04:00
end
end
2013-11-14 15:50:36 -05:00
result = apply_ordering ( result , options )
2014-09-10 19:55:10 -07:00
result = result . listable_topics . includes ( :category )
2015-09-02 20:25:18 +02:00
if options [ :exclude_category_ids ] && options [ :exclude_category_ids ]. is_a? ( Array ) && options [ :exclude_category_ids ]. size > 0
result = result . where ( "categories.id NOT IN (?)" , options [ :exclude_category_ids ] ) . references ( :categories )
end
2013-10-31 16:10:54 -04:00
2014-02-11 10:06:20 +11:00
# Don't include the category topics if excluded
if options [ :no_definitions ]
2014-02-04 15:55:30 -05:00
result = result . where ( 'COALESCE(categories.topic_id, 0) <> topics.id' )
end
2013-07-16 21:20:18 +02:00
result = result . limit ( options [ :per_page ] ) unless options [ :limit ] == false
2015-03-23 18:12:37 -04:00
result = result . visible if options [ :visible ]
2013-12-24 00:50:36 +01:00
result = result . where . not ( topics : { id : options [ :except_topic_ids ] }) . references ( :topics ) if options [ :except_topic_ids ]
2016-05-09 16:33:55 -04:00
if options [ :page ]
offset = options [ :page ]. to_i * options [ :per_page ]
2016-05-11 13:39:21 -04:00
result = result . offset ( offset ) if offset > 0
2016-05-09 16:33:55 -04:00
end
2013-07-16 21:20:18 +02:00
if options [ :topic_ids ]
2013-08-16 14:53:40 +02:00
result = result . where ( 'topics.id in (?)' , options [ :topic_ids ] ) . references ( :topics )
2013-05-28 17:52:52 +10:00
end
2014-05-16 00:31:45 +10:00
if search = options [ :search ]
result = result . where ( "topics.id in (select pp.topic_id from post_search_data pd join posts pp on pp.id = pd.post_id where pd.search_data @@ #{ Search . ts_query ( search . to_s ) } )" )
end
2014-07-17 09:29:09 +10:00
# NOTE protect against SYM attack can be removed with Ruby 2.2
#
state = options [ :state ]
if @user && state &&
TopicUser . notification_levels . keys . map ( & :to_s ) . include? ( state )
level = TopicUser . notification_levels [ state . to_sym ]
result = result . where ( 'topics.id IN (
SELECT topic_id
FROM topic_users
WHERE user_id = ? AND
notification_level = ?)' , @user . id , level )
end
2014-11-19 17:46:55 -05:00
require_deleted_clause = true
2014-01-13 14:40:21 +11:00
if status = options [ :status ]
case status
when 'open'
result = result . where ( 'NOT topics.closed AND NOT topics.archived' )
when 'closed'
result = result . where ( 'topics.closed' )
when 'archived'
result = result . where ( 'topics.archived' )
2015-01-12 01:00:45 -08:00
when 'listed'
2014-09-11 19:17:16 -04:00
result = result . where ( 'topics.visible' )
2015-01-12 01:00:45 -08:00
when 'unlisted'
2014-09-11 19:21:25 -04:00
result = result . where ( 'NOT topics.visible' )
2014-11-19 17:46:55 -05:00
when 'deleted'
2015-09-23 13:13:34 +10:00
guardian = @guardian
2014-11-19 17:46:55 -05:00
if guardian . is_staff?
result = result . where ( 'topics.deleted_at IS NOT NULL' )
require_deleted_clause = false
end
2014-01-13 14:40:21 +11:00
end
end
2015-01-07 18:20:10 +11:00
if ( filter = options [ :filter ] ) && @user
action =
if filter == "bookmarked"
PostActionType . types [ :bookmark ]
elsif filter == "liked"
PostActionType . types [ :like ]
end
if action
result = result . where ( 'topics.id IN (SELECT pp.topic_id
2015-01-07 13:58:34 +11:00
FROM post_actions pa
JOIN posts pp ON pp.id = pa.post_id
WHERE pa.user_id = :user_id AND
2015-01-07 18:20:10 +11:00
pa.post_action_type_id = :action AND
2015-01-07 13:58:34 +11:00
pa.deleted_at IS NULL
)' , user_id : @user . id ,
2015-01-07 18:20:10 +11:00
action : action
2015-01-07 13:58:34 +11:00
)
2015-01-07 18:20:10 +11:00
end
2015-01-07 13:58:34 +11:00
end
2014-11-19 17:46:55 -05:00
result = result . where ( 'topics.deleted_at IS NULL' ) if require_deleted_clause
2014-06-05 15:30:24 +02:00
result = result . where ( 'topics.posts_count <= ?' , options [ :max_posts ] ) if options [ :max_posts ]. present?
result = result . where ( 'topics.posts_count >= ?' , options [ :min_posts ] ) if options [ :min_posts ]. present?
2015-09-23 13:13:34 +10:00
@guardian . filter_allowed_categories ( result )
2013-02-05 14:16:51 -05:00
end
2015-11-02 09:20:22 +11:00
def remove_muted_topics ( list , user )
if user
2015-11-02 14:59:10 +11:00
list = list . where ( 'COALESCE(tu.notification_level,1) > :muted' , muted : TopicUser . notification_levels [ :muted ] )
2015-11-02 09:20:22 +11:00
end
list
end
2014-07-29 14:34:54 +10:00
def remove_muted_categories ( list , user , opts = nil )
2014-06-18 11:23:31 +10:00
category_id = get_category_id ( opts [ :exclude ] ) if opts
2015-09-02 20:25:18 +02:00
2014-02-03 16:05:49 +11:00
if user
2015-09-02 20:25:18 +02:00
list = list . references ( "cu" )
. where ( "
NOT EXISTS (
SELECT 1
FROM category_users cu
WHERE cu.user_id = :user_id
AND cu.category_id = topics.category_id
AND cu.notification_level = :muted
AND cu.category_id <> :category_id
2015-10-13 17:54:31 +11:00
AND (tu.notification_level IS NULL OR tu.notification_level < :tracking)
2015-09-02 20:25:18 +02:00
)" , user_id : user . id ,
muted : CategoryUser . notification_levels [ :muted ] ,
2015-10-13 17:54:31 +11:00
tracking : TopicUser . notification_levels [ :tracking ] ,
2015-09-02 20:25:18 +02:00
category_id : category_id || - 1 )
2014-02-03 16:05:49 +11:00
end
list
end
2016-04-25 15:55:15 -04:00
def remove_muted_tags ( list , user , opts = nil )
if user . nil? || ! SiteSetting . tagging_enabled || ! SiteSetting . remove_muted_tags_from_latest
list
else
2016-08-04 11:54:39 -04:00
if ! TagUser . lookup ( user , :muted ) . exists?
2016-04-25 15:55:15 -04:00
list
else
showing_tag = if opts [ :filter ]
f = opts [ :filter ]. split ( '/' )
f [ 0 ] == 'tags' ? f [ 1 ] : nil
else
nil
end
2016-08-04 11:54:39 -04:00
if TagUser . lookup ( user , :muted ) . joins ( :tag ) . where ( 'tags.name = ?' , showing_tag ) . exists?
2016-04-25 15:55:15 -04:00
list # if viewing the topic list for a muted tag, show all the topics
else
2016-08-04 11:54:39 -04:00
muted_tag_ids = TagUser . lookup ( user , :muted ) . pluck ( :tag_id )
list = list . where ( "
EXISTS (
SELECT 1
FROM topic_tags tt
WHERE tt.tag_id NOT IN (:tag_ids)
AND tt.topic_id = topics.id
) OR NOT EXISTS (SELECT 1 FROM topic_tags tt WHERE tt.topic_id = topics.id)" , tag_ids : muted_tag_ids )
2016-04-25 15:55:15 -04:00
end
end
end
end
2014-02-03 16:05:49 +11:00
2016-02-03 18:50:05 +11:00
def new_messages ( params )
TopicQuery . new_filter ( messages_for_groups_or_user ( params [ :my_group_ids ] ), 10 . years . ago )
. limit ( params [ :count ] )
end
def unread_messages ( params )
TopicQuery . unread_filter ( messages_for_groups_or_user ( params [ :my_group_ids ] ))
. limit ( params [ :count ] )
end
def related_messages_user ( params )
messages_for_user
. limit ( params [ :count ] )
. where ( 'topics.id IN (
SELECT ta.topic_id
FROM topic_allowed_users ta
WHERE ta.user_id IN (:user_ids)
) OR
topics.id IN (
SELECT tg.topic_id
FROM topic_allowed_groups tg
WHERE tg.group_id IN (:group_ids)
)
' , user_ids : ( params [ :target_user_ids ] || [] ) + [- 10 ] ,
group_ids : (( params [ :target_group_ids ] - params [ :my_group_ids ] ) || [] ) + [- 10 ] )
end
def related_messages_group ( params )
messages_for_groups_or_user ( params [ :my_group_ids ] )
. limit ( params [ :count ] )
. where ( 'topics.id IN (
SELECT ta.topic_id
FROM topic_allowed_users ta
WHERE ta.user_id IN (:user_ids)
) OR
topics.id IN (
SELECT tg.topic_id
FROM topic_allowed_groups tg
WHERE tg.group_id IN (:group_ids)
)
' , user_ids : ( params [ :target_user_ids ] || [] ) + [- 10 ] ,
group_ids : (( params [ :target_group_ids ] - params [ :my_group_ids ] ) || [] ) + [- 10 ] )
end
def messages_for_groups_or_user ( group_ids )
if group_ids . present?
base_messages
. where ( 'topics.id IN (
SELECT topic_id
FROM topic_allowed_groups tg
JOIN group_users gu ON gu.user_id = :user_id AND gu.group_id = tg.group_id
WHERE gu.group_id IN (:group_ids)
)' , user_id : @user . id , group_ids : group_ids )
else
messages_for_user
end
end
def messages_for_user
base_messages . where ( 'topics.id IN (
SELECT topic_id
FROM topic_allowed_users
WHERE user_id = :user_id
)' , user_id : @user . id )
end
def base_messages
Topic
. where ( 'topics.archetype = ?' , Archetype . private_message )
. joins ( "LEFT JOIN topic_users tu ON topics.id = tu.topic_id AND tu.user_id = #{ @user . id . to_i } " )
. order ( 'topics.bumped_at DESC' )
end
2014-02-03 16:05:49 +11:00
2014-01-28 18:15:36 -05:00
def random_suggested ( topic , count , excluded_topic_ids =[] )
2014-02-04 12:26:38 -05:00
result = default_results ( unordered : true , per_page : count ) . where ( closed : false , archived : false )
2016-07-19 12:34:54 +10:00
excluded_topic_ids += Category . topic_ids . to_a
2014-01-28 18:15:36 -05:00
result = result . where ( "topics.id NOT IN (?)" , excluded_topic_ids ) unless excluded_topic_ids . empty?
2013-02-05 14:16:51 -05:00
2014-07-29 14:34:54 +10:00
result = remove_muted_categories ( result , @user )
2013-07-12 14:38:20 -04:00
# If we are in a category, prefer it for the random results
2013-07-16 21:20:18 +02:00
if topic . category_id
2013-07-18 14:47:59 -04:00
result = result . order ( "CASE WHEN topics.category_id = #{ topic . category_id . to_i } THEN 0 ELSE 1 END" )
2013-02-27 18:30:14 -05:00
end
2015-02-25 17:19:12 +11:00
# Best effort, it over selects, however if you have a high number
# of muted categories there is tiny chance we will not select enough
# in particular this can happen if current category is empty and tons
# of muted, big edge case
#
# we over select in case cache is stale
max = ( count * 1 . 3 ) . to_i
ids = RandomTopicSelector . next ( max ) + RandomTopicSelector . next ( max , topic . category )
2015-03-03 10:20:42 +11:00
result . where ( id : ids . uniq )
2013-02-05 14:16:51 -05:00
end
2013-08-08 13:18:52 -04:00
def suggested_ordering ( result , options )
# Prefer unread in the same category
if options [ :topic ] && options [ :topic ]. category_id
result = result . order ( "CASE WHEN topics.category_id = #{ options [ :topic ]. category_id . to_i } THEN 0 ELSE 1 END" )
end
2015-02-23 16:50:52 +11:00
result . order ( 'topics.bumped_at DESC' )
2013-08-08 13:18:52 -04:00
end
2013-02-05 14:16:51 -05:00
end