discourse/app/controllers/list_controller.rb

458 lines
16 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
2015-01-09 05:44:27 +08:00
require_dependency 'topic_list_responder'
2013-02-06 03:16:51 +08:00
class ListController < ApplicationController
2015-01-09 05:44:27 +08:00
include TopicListResponder
2013-02-06 03:16:51 +08:00
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: [
2014-01-14 08:02:14 +08:00
: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}" },
:group_topics
2014-01-14 08:02:14 +08:00
].flatten
2013-02-06 03:16:51 +08:00
# 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].blank?
list_opts[:exclude_category_ids] = get_excluded_category_ids(list_opts[:category])
end
end
list = TopicQuery.new(user, list_opts).public_send("list_#{filter}")
if guardian.can_create_shared_draft? && @category.present?
if @category.id == SiteSetting.shared_drafts_category.to_i
# On shared drafts, show the destination category
list.topics.each do |t|
t.includes_destination_category = true
end
else
# When viewing a non-shared draft category, find topics whose
# destination are this category
shared_drafts = TopicQuery.new(
user,
category: SiteSetting.shared_drafts_category,
destination_category_id: list_opts[:category]
).list_latest
if shared_drafts.present? && shared_drafts.topics.present?
list.shared_drafts = shared_drafts.topics
end
end
end
list.more_topics_url = construct_url_with(:next, list_opts)
list.prev_topics_url = construct_url_with(:prev, list_opts)
2013-12-24 07:50:36 +08:00
if Discourse.anonymous_filters.include?(filter)
2013-10-12 00:35:12 +08:00
@description = SiteSetting.site_description
@rss = filter
2014-10-31 02:26:35 +08:00
# Note the first is the default and we don't add a title
if (filter.to_s != current_homepage) && use_crawler_layout?
2015-07-29 21:39:20 +08:00
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)
2014-10-31 02:26:35 +08:00
else
@title = I18n.t('js.filters.with_topics', filter: filter_title)
end
@title << " - #{SiteSetting.title}"
elsif (filter.to_s == current_homepage) && SiteSetting.short_site_description.present?
@title = "#{SiteSetting.title} - #{SiteSetting.short_site_description}"
2014-10-31 02:26:35 +08:00
end
2013-10-12 00:35:12 +08:00
end
2014-10-31 02:26:35 +08:00
2015-01-09 05:44:27 +08:00
respond_with_list(list)
2013-02-07 23:45:24 +08:00
end
define_method("category_#{filter}") do
canonical_url "#{Discourse.base_url_no_prefix}#{@category.url}"
2019-05-07 09:27:05 +08:00
self.public_send(filter, category: @category.id)
end
define_method("category_none_#{filter}") do
2019-05-07 09:27:05 +08:00
self.public_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}"
2019-05-07 09:27:05 +08:00
self.public_send(filter, category: @category.id)
end
define_method("parent_category_category_none_#{filter}") do
2019-05-07 09:27:05 +08:00
self.public_send(filter, category: @category.id)
2013-07-06 04:49:06 +08:00
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
2019-05-07 09:27:05 +08:00
self.public_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])
2017-03-02 07:26:26 +08:00
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 group_topics
group = Group.find_by(name: params[:group_name])
raise Discourse::NotFound unless group
guardian.ensure_can_see_group!(group)
list_opts = build_topic_list_options
list = generate_list_for("group_topics", group, 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))
2015-01-09 05:44:27 +08:00
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
2018-02-14 04:46:25 +08:00
private_messages_tag
}.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
2016-03-01 01:58:51 +08:00
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)
2016-03-01 01:58:51 +08:00
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_no_prefix}#{@category.url}"
@atom_link = "#{Discourse.base_url_no_prefix}#{@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)}"
2017-03-29 02:27:54 +08:00
@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)
2019-05-07 09:27:05 +08:00
@topic_list = TopicQuery
.new(nil, order: 'created')
.public_send("list_topics_by", target_user)
render 'list', formats: [:rss]
end
2017-07-28 09:20:09 +08:00
def top(options = nil)
options ||= {}
period = ListController.best_period_for(current_user.try(:previous_visit_at), options[:category])
public_send("top_#{period}", options)
2013-12-24 07:50:36 +08:00
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
2014-01-14 08:02:14 +08:00
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].blank?
top_options[:exclude_category_ids] = get_excluded_category_ids(top_options[:category])
end
2014-01-14 08:02:14 +08:00
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}"
2014-10-31 02:26:35 +08:00
if use_crawler_layout?
@title = I18n.t("js.filters.top.#{period}.title") + " - #{SiteSetting.title}"
2014-10-31 02:26:35 +08:00
end
2015-01-09 05:44:27 +08:00
respond_with_list(list)
2014-01-14 08:02:14 +08:00
end
define_method("category_top_#{period}") do
2019-05-07 09:27:05 +08:00
self.public_send("top_#{period}", category: @category.id)
end
2013-02-06 03:16:51 +08:00
define_method("category_none_top_#{period}") do
2019-05-07 09:27:05 +08:00
self.public_send("top_#{period}",
category: @category.id,
no_subcategories: true
)
end
define_method("parent_category_category_top_#{period}") do
2019-05-07 09:27:05 +08:00
self.public_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
2013-12-14 06:18:28 +08:00
end
protected
def next_page_params(opts = nil)
2014-02-26 01:15:20 +08:00
page_params(opts).merge(page: params[:page].to_i + 1)
end
def prev_page_params(opts = nil)
2014-03-04 01:56:37 +08:00
pg = params[:page].to_i
if pg > 1
page_params(opts).merge(page: pg - 1)
else
page_params(opts).merge(page: nil)
end
2014-02-26 01:15:20 +08:00
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
2013-02-06 03:16:51 +08:00
end
def set_category
slug_or_id = params.fetch(:category)
parent_slug_or_id = params[:parent_category]
id = params[:id].to_i
2013-10-24 02:40:39 +08:00
parent_category_id = nil
2014-01-17 22:32:23 +08:00
if parent_slug_or_id.present?
parent_category_id = Category.query_parent_category(parent_slug_or_id)
raise Discourse::NotFound.new("category not found", check_permalinks: true) if parent_category_id.blank? && !id
2013-10-24 02:40:39 +08:00
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
raise Discourse::NotFound.new("category not found", check_permalinks: true) 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 = {}
2018-04-14 19:12:53 +08:00
params[:tags] = [params[:tag_id].parameterize] if params[:tag_id].present? && guardian.can_tag_pms?
2017-02-16 06:27:10 +08:00
TopicQuery.public_valid_options.each do |key|
if params.key?(key)
val = options[key] = params[key]
if !TopicQuery.validate?(key, val)
raise Discourse::InvalidParameters.new key
end
end
end
# hacky columns get special handling
options[:topic_ids] = param_to_integer_list(:topic_ids)
if options[:no_subcategories] == 'true'
options[:no_subcategories] = true
end
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)
2019-05-07 09:27:05 +08:00
TopicQuery.new(current_user, opts).public_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
2017-07-28 09:20:09 +08:00
url.sub('.json?', '?')
2014-02-26 01:15:20 +08:00
end
2017-07-28 09:20:09 +08:00
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)
2013-12-24 07:50:36 +08:00
end
2017-07-28 09:20:09 +08:00
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
2017-07-28 09:20:09 +08:00
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).find 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)
top_topics.count == SiteSetting.topics_per_period_in_top_page
end
end
2017-07-28 09:20:09 +08:00
def self.best_periods_for(date, default_period = :all)
return [default_period, :all].uniq unless date
periods = []
periods << :daily if date > (1.week + 1.day).ago
periods << :weekly if date > (1.month + 1.week).ago
periods << :monthly if date > (3.months + 3.weeks).ago
periods << :quarterly if date > (1.year + 1.month).ago
periods << :yearly if date > 3.years.ago
periods << :all
periods
2014-01-14 08:02:14 +08:00
end
2013-02-06 03:16:51 +08:00
end