discourse/app/controllers/list_controller.rb
Sam 720e1965e3 FEATURE: add category suppress from latest
In the past we used suppress_from_homepage, it had mixed semantics
it would remove from category list if category list was on home and
unconditionally remove from latest.

New setting explicitly only removes from latest list but leaves the
category list alond
2018-02-22 09:56:35 +11:00

407 lines
14 KiB
Ruby

require_dependency 'topic_list_responder'
class ListController < ApplicationController
include TopicListResponder
skip_before_action :check_xhr
before_action :set_category, only: [
:category_default,
# filtered topics lists
Discourse.filters.map { |f| :"category_#{f}" },
Discourse.filters.map { |f| :"category_none_#{f}" },
Discourse.filters.map { |f| :"parent_category_category_#{f}" },
Discourse.filters.map { |f| :"parent_category_category_none_#{f}" },
# top summaries
:category_top,
:category_none_top,
:parent_category_category_top,
# top pages (ie. with a period)
TopTopic.periods.map { |p| :"category_top_#{p}" },
TopTopic.periods.map { |p| :"category_none_top_#{p}" },
TopTopic.periods.map { |p| :"parent_category_category_top_#{p}" },
# category feeds
:category_feed,
].flatten
before_action :ensure_logged_in, except: [
:topics_by,
# anonymous filters
Discourse.anonymous_filters,
Discourse.anonymous_filters.map { |f| "#{f}_feed" },
# anonymous categorized filters
:category_default,
Discourse.anonymous_filters.map { |f| :"category_#{f}" },
Discourse.anonymous_filters.map { |f| :"category_none_#{f}" },
Discourse.anonymous_filters.map { |f| :"parent_category_category_#{f}" },
Discourse.anonymous_filters.map { |f| :"parent_category_category_none_#{f}" },
# category feeds
:category_feed,
# user topics feed
:user_topics_feed,
# top summaries
:top,
:category_top,
:category_none_top,
:parent_category_category_top,
# top pages (ie. with a period)
TopTopic.periods.map { |p| :"top_#{p}" },
TopTopic.periods.map { |p| :"top_#{p}_feed" },
TopTopic.periods.map { |p| :"category_top_#{p}" },
TopTopic.periods.map { |p| :"category_none_top_#{p}" },
TopTopic.periods.map { |p| :"parent_category_category_top_#{p}" },
].flatten
# Create our filters
Discourse.filters.each do |filter|
define_method(filter) do |options = nil|
list_opts = build_topic_list_options
list_opts.merge!(options) if options
user = list_target_user
if params[:category].blank?
if filter == :latest
list_opts[:no_definitions] = true
end
if [:latest, :categories].include?(filter)
list_opts[:exclude_category_ids] = get_excluded_category_ids(list_opts[:category])
end
end
list = TopicQuery.new(user, list_opts).public_send("list_#{filter}")
list.more_topics_url = construct_url_with(:next, list_opts)
list.prev_topics_url = construct_url_with(:prev, list_opts)
if Discourse.anonymous_filters.include?(filter)
@description = SiteSetting.site_description
@rss = filter
# Note the first is the default and we don't add a title
if (filter.to_s != current_homepage) && use_crawler_layout?
filter_title = I18n.t("js.filters.#{filter.to_s}.title", count: 0)
if list_opts[:category] && @category
@title = I18n.t('js.filters.with_category', filter: filter_title, category: @category.name)
else
@title = I18n.t('js.filters.with_topics', filter: filter_title)
end
@title << " - #{SiteSetting.title}"
end
end
respond_with_list(list)
end
define_method("category_#{filter}") do
canonical_url "#{Discourse.base_url_no_prefix}#{@category.url}"
self.send(filter, category: @category.id)
end
define_method("category_none_#{filter}") do
self.send(filter, category: @category.id, no_subcategories: true)
end
define_method("parent_category_category_#{filter}") do
canonical_url "#{Discourse.base_url_no_prefix}#{@category.url}"
self.send(filter, category: @category.id)
end
define_method("parent_category_category_none_#{filter}") do
self.send(filter, category: @category.id)
end
end
def category_default
canonical_url "#{Discourse.base_url_no_prefix}#{@category.url}"
view_method = @category.default_view
view_method = 'latest' unless %w(latest top).include?(view_method)
if view_method == 'top'
top(category: @category.id)
else
self.send(view_method)
end
end
def topics_by
list_opts = build_topic_list_options
target_user = fetch_user_from_params({ include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts) }, [:user_stat, :user_option])
list = generate_list_for("topics_by", target_user, list_opts)
list.more_topics_url = url_for(construct_url_with(:next, list_opts))
list.prev_topics_url = url_for(construct_url_with(:prev, list_opts))
respond_with_list(list)
end
def self.generate_message_route(action)
define_method("#{action}") do
list_opts = build_topic_list_options
target_user = fetch_user_from_params({ include_inactive: current_user.try(:staff?) }, [:user_stat, :user_option])
guardian.ensure_can_see_private_messages!(target_user.id)
list = generate_list_for(action.to_s, target_user, list_opts)
url_prefix = "topics"
list.more_topics_url = url_for(construct_url_with(:next, list_opts, url_prefix))
list.prev_topics_url = url_for(construct_url_with(:prev, list_opts, url_prefix))
respond_with_list(list)
end
end
%i{
private_messages
private_messages_sent
private_messages_unread
private_messages_archive
private_messages_group
private_messages_group_archive
}.each do |action|
generate_message_route(action)
end
def latest_feed
discourse_expires_in 1.minute
@title = "#{SiteSetting.title} - #{I18n.t("rss_description.latest")}"
@link = "#{Discourse.base_url}/latest"
@atom_link = "#{Discourse.base_url}/latest.rss"
@description = I18n.t("rss_description.latest")
@topic_list = TopicQuery.new(nil, order: 'created').list_latest
render 'list', formats: [:rss]
end
def top_feed
discourse_expires_in 1.minute
@title = "#{SiteSetting.title} - #{I18n.t("rss_description.top")}"
@link = "#{Discourse.base_url}/top"
@atom_link = "#{Discourse.base_url}/top.rss"
@description = I18n.t("rss_description.top")
@topic_list = TopicQuery.new(nil).list_top_for(SiteSetting.top_page_default_timeframe.to_sym)
render 'list', formats: [:rss]
end
def category_feed
guardian.ensure_can_see!(@category)
discourse_expires_in 1.minute
@title = "#{@category.name} - #{SiteSetting.title}"
@link = "#{Discourse.base_url}#{@category.url}"
@atom_link = "#{Discourse.base_url}#{@category.url}.rss"
@description = "#{I18n.t('topics_in_category', category: @category.name)} #{@category.description}"
@topic_list = TopicQuery.new(current_user).list_new_in_category(@category)
render 'list', formats: [:rss]
end
def user_topics_feed
discourse_expires_in 1.minute
target_user = fetch_user_from_params
@title = "#{SiteSetting.title} - #{I18n.t("rss_description.user_topics", username: target_user.username)}"
@link = "#{Discourse.base_url}/u/#{target_user.username}/activity/topics"
@atom_link = "#{Discourse.base_url}/u/#{target_user.username}/activity/topics.rss"
@description = I18n.t("rss_description.user_topics", username: target_user.username)
@topic_list = TopicQuery.new(nil, order: 'created').send("list_topics_by", target_user)
render 'list', formats: [:rss]
end
def top(options = nil)
options ||= {}
period = ListController.best_period_for(current_user.try(:previous_visit_at), options[:category])
send("top_#{period}", options)
end
def category_top
top(category: @category.id)
end
def category_none_top
top(category: @category.id, no_subcategories: true)
end
def parent_category_category_top
top(category: @category.id)
end
TopTopic.periods.each do |period|
define_method("top_#{period}") do |options = nil|
top_options = build_topic_list_options
top_options.merge!(options) if options
top_options[:per_page] = SiteSetting.topics_per_period_in_top_page
if "top".freeze == current_homepage
top_options[:exclude_category_ids] = get_excluded_category_ids(top_options[:category])
end
user = list_target_user
list = TopicQuery.new(user, top_options).list_top_for(period)
list.for_period = period
list.more_topics_url = construct_url_with(:next, top_options)
list.prev_topics_url = construct_url_with(:prev, top_options)
@rss = "top_#{period}"
if use_crawler_layout?
@title = I18n.t("js.filters.top.#{period}.title") + " - #{SiteSetting.title}"
end
respond_with_list(list)
end
define_method("category_top_#{period}") do
self.send("top_#{period}", category: @category.id)
end
define_method("category_none_top_#{period}") do
self.send("top_#{period}", category: @category.id, no_subcategories: true)
end
define_method("parent_category_category_top_#{period}") do
self.send("top_#{period}", category: @category.id)
end
# rss feed
define_method("top_#{period}_feed") do |options = nil|
discourse_expires_in 1.minute
@description = I18n.t("rss_description.top_#{period}")
@title = "#{SiteSetting.title} - #{@description}"
@link = "#{Discourse.base_url}/top/#{period}"
@atom_link = "#{Discourse.base_url}/top/#{period}.rss"
@topic_list = TopicQuery.new(nil).list_top_for(period)
render 'list', formats: [:rss]
end
end
protected
def next_page_params(opts = nil)
page_params(opts).merge(page: params[:page].to_i + 1)
end
def prev_page_params(opts = nil)
pg = params[:page].to_i
if pg > 1
page_params(opts).merge(page: pg - 1)
else
page_params(opts).merge(page: nil)
end
end
private
def page_params(opts = nil)
opts ||= {}
route_params = { format: 'json' }
route_params[:category] = @category.slug_for_url if @category
route_params[:parent_category] = @category.parent_category.slug_for_url if @category && @category.parent_category
route_params[:order] = opts[:order] if opts[:order].present?
route_params[:ascending] = opts[:ascending] if opts[:ascending].present?
route_params[:username] = UrlHelper.escape_uri(params[:username]) if params[:username].present?
route_params
end
def set_category
slug_or_id = params.fetch(:category)
parent_slug_or_id = params[:parent_category]
id = params[:id].to_i
parent_category_id = nil
if parent_slug_or_id.present?
parent_category_id = Category.query_parent_category(parent_slug_or_id)
permalink_redirect_or_not_found && (return) if parent_category_id.blank? && !id
end
@category = Category.query_category(slug_or_id, parent_category_id)
# Redirect if we have `/c/:parent_category/:category/:id`
if id
category = Category.find_by_id(id)
(redirect_to category.url, status: 301) && return if category
end
permalink_redirect_or_not_found && (return) if !@category
@description_meta = @category.description_text
raise Discourse::NotFound unless guardian.can_see?(@category)
if use_crawler_layout?
@subcategories = @category.subcategories.select { |c| guardian.can_see?(c) }
end
end
def build_topic_list_options
options = {}
params[:page] = params[:page].to_i rescue 1
TopicQuery.public_valid_options.each do |key|
options[key] = params[key]
end
# hacky columns get special handling
options[:topic_ids] = param_to_integer_list(:topic_ids)
options[:no_subcategories] = options[:no_subcategories] == 'true'
options[:slow_platform] = slow_platform?
options
end
def list_target_user
if params[:user_id] && guardian.is_staff?
User.find(params[:user_id].to_i)
else
current_user
end
end
def generate_list_for(action, target_user, opts)
TopicQuery.new(current_user, opts).send("list_#{action}", target_user)
end
def construct_url_with(action, opts, url_prefix = nil)
method = url_prefix.blank? ? "#{action_name}_path" : "#{url_prefix}_#{action_name}_path"
url = if action == :prev
public_send(method, opts.merge(prev_page_params(opts)))
else # :next
public_send(method, opts.merge(next_page_params(opts)))
end
url.sub('.json?', '?')
end
def get_excluded_category_ids(current_category = nil)
exclude_category_ids = Category.where(suppress_from_latest: true)
exclude_category_ids = exclude_category_ids.where.not(id: current_category) if current_category
exclude_category_ids.pluck(:id)
end
def self.best_period_for(previous_visit_at, category_id = nil)
default_period = ((category_id && Category.where(id: category_id).pluck(:default_top_period).first) ||
SiteSetting.top_page_default_timeframe).to_sym
best_period_with_topics_for(previous_visit_at, category_id, default_period) || default_period
end
def self.best_period_with_topics_for(previous_visit_at, category_id = nil, default_period = SiteSetting.top_page_default_timeframe)
best_periods_for(previous_visit_at, default_period.to_sym).each do |period|
top_topics = TopTopic.where("#{period}_score > 0")
top_topics = top_topics.joins(:topic).where("topics.category_id = ?", category_id) if category_id
top_topics = top_topics.limit(SiteSetting.topics_per_period_in_top_page)
return period if top_topics.count == SiteSetting.topics_per_period_in_top_page
end
false
end
def self.best_periods_for(date, default_period = :all)
date ||= 1.year.ago
periods = []
periods << default_period if :all != default_period
periods << :daily if :daily != default_period && date > 8.days.ago
periods << :weekly if :weekly != default_period && date > 35.days.ago
periods << :monthly if :monthly != default_period && date > 180.days.ago
periods << :yearly if :yearly != default_period
periods
end
end