DEV: Apply syntax_tree formatting to `app/*`

This commit is contained in:
David Taylor 2023-01-09 12:20:10 +00:00
parent a641ce4b62
commit 5a003715d3
No known key found for this signature in database
GPG Key ID: 46904C18B1D3F434
696 changed files with 18447 additions and 15481 deletions

View File

@ -1,3 +1,2 @@
--print-width=100 --print-width=100
--plugins=plugin/trailing_comma,disable_ternary --plugins=plugin/trailing_comma,disable_ternary
--ignore-files=app/*

View File

@ -1,30 +1,28 @@
# frozen_string_literal: true # frozen_string_literal: true
class AboutController < ApplicationController class AboutController < ApplicationController
requires_login only: [:live_post_counts] requires_login only: [:live_post_counts]
skip_before_action :check_xhr, only: [:index] skip_before_action :check_xhr, only: [:index]
def index def index
return redirect_to path('/login') if SiteSetting.login_required? && current_user.nil? return redirect_to path("/login") if SiteSetting.login_required? && current_user.nil?
@about = About.new(current_user) @about = About.new(current_user)
@title = "#{I18n.t("js.about.simple_title")} - #{SiteSetting.title}" @title = "#{I18n.t("js.about.simple_title")} - #{SiteSetting.title}"
respond_to do |format| respond_to do |format|
format.html do format.html { render :index }
render :index format.json { render_json_dump(AboutSerializer.new(@about, scope: guardian)) }
end
format.json do
render_json_dump(AboutSerializer.new(@about, scope: guardian))
end
end end
end end
def live_post_counts def live_post_counts
RateLimiter.new(current_user, "live_post_counts", 1, 10.minutes).performed! unless current_user.staff? unless current_user.staff?
RateLimiter.new(current_user, "live_post_counts", 1, 10.minutes).performed!
end
category_topic_ids = Category.pluck(:topic_id).compact! category_topic_ids = Category.pluck(:topic_id).compact!
public_topics = Topic.listable_topics.visible.secured(Guardian.new(nil)).where.not(id: category_topic_ids) public_topics =
Topic.listable_topics.visible.secured(Guardian.new(nil)).where.not(id: category_topic_ids)
stats = { public_topic_count: public_topics.count } stats = { public_topic_count: public_topics.count }
stats[:public_post_count] = public_topics.sum(:posts_count) - stats[:public_topic_count] stats[:public_post_count] = public_topics.sum(:posts_count) - stats[:public_topic_count]
render json: stats render json: stats

View File

@ -7,5 +7,4 @@ class Admin::AdminController < ApplicationController
def index def index
render body: nil render body: nil
end end
end end

View File

@ -8,40 +8,40 @@ class Admin::ApiController < Admin::AdminController
offset = (params[:offset] || 0).to_i offset = (params[:offset] || 0).to_i
limit = (params[:limit] || 50).to_i.clamp(1, 50) limit = (params[:limit] || 50).to_i.clamp(1, 50)
keys = ApiKey keys =
.where(hidden: false) ApiKey
.includes(:user, :api_key_scopes) .where(hidden: false)
# Sort revoked keys by revoked_at and active keys by created_at .includes(:user, :api_key_scopes)
.order("revoked_at DESC NULLS FIRST, created_at DESC") # Sort revoked keys by revoked_at and active keys by created_at
.offset(offset) .order("revoked_at DESC NULLS FIRST, created_at DESC")
.limit(limit) .offset(offset)
.limit(limit)
render_json_dump( render_json_dump(keys: serialize_data(keys, ApiKeySerializer), offset: offset, limit: limit)
keys: serialize_data(keys, ApiKeySerializer),
offset: offset,
limit: limit
)
end end
def show def show
api_key = ApiKey.includes(:api_key_scopes).find_by!(id: params[:id]) api_key = ApiKey.includes(:api_key_scopes).find_by!(id: params[:id])
render_serialized(api_key, ApiKeySerializer, root: 'key') render_serialized(api_key, ApiKeySerializer, root: "key")
end end
def scopes def scopes
scopes = ApiKeyScope.scope_mappings.reduce({}) do |memo, (resource, actions)| scopes =
memo.tap do |m| ApiKeyScope
m[resource] = actions.map do |k, v| .scope_mappings
{ .reduce({}) do |memo, (resource, actions)|
scope_id: "#{resource}:#{k}", memo.tap do |m|
key: k, m[resource] = actions.map do |k, v|
name: k.to_s.gsub('_', ' '), {
params: v[:params], scope_id: "#{resource}:#{k}",
urls: v[:urls] key: k,
} name: k.to_s.gsub("_", " "),
params: v[:params],
urls: v[:urls],
}
end
end
end end
end
end
render json: { scopes: scopes } render json: { scopes: scopes }
end end
@ -52,7 +52,7 @@ class Admin::ApiController < Admin::AdminController
api_key.update!(update_params) api_key.update!(update_params)
log_api_key(api_key, UserHistory.actions[:api_key_update], changes: api_key.saved_changes) log_api_key(api_key, UserHistory.actions[:api_key_update], changes: api_key.saved_changes)
end end
render_serialized(api_key, ApiKeySerializer, root: 'key') render_serialized(api_key, ApiKeySerializer, root: "key")
end end
def destroy def destroy
@ -76,7 +76,7 @@ class Admin::ApiController < Admin::AdminController
api_key.save! api_key.save!
log_api_key(api_key, UserHistory.actions[:api_key_create], changes: api_key.saved_changes) log_api_key(api_key, UserHistory.actions[:api_key_create], changes: api_key.saved_changes)
end end
render_serialized(api_key, ApiKeySerializer, root: 'key') render_serialized(api_key, ApiKeySerializer, root: "key")
end end
def undo_revoke_key def undo_revoke_key
@ -105,7 +105,7 @@ class Admin::ApiController < Admin::AdminController
def build_scopes def build_scopes
params.require(:key)[:scopes].to_a.map do |scope_params| params.require(:key)[:scopes].to_a.map do |scope_params|
resource, action = scope_params[:scope_id].split(':') resource, action = scope_params[:scope_id].split(":")
mapping = ApiKeyScope.scope_mappings.dig(resource.to_sym, action.to_sym) mapping = ApiKeyScope.scope_mappings.dig(resource.to_sym, action.to_sym)
raise Discourse::InvalidParameters if mapping.nil? # invalid mapping raise Discourse::InvalidParameters if mapping.nil? # invalid mapping
@ -113,7 +113,7 @@ class Admin::ApiController < Admin::AdminController
ApiKeyScope.new( ApiKeyScope.new(
resource: resource, resource: resource,
action: action, action: action,
allowed_parameters: build_params(scope_params, mapping[:params]) allowed_parameters: build_params(scope_params, mapping[:params]),
) )
end end
end end
@ -121,11 +121,13 @@ class Admin::ApiController < Admin::AdminController
def build_params(scope_params, params) def build_params(scope_params, params)
return if params.nil? return if params.nil?
scope_params.slice(*params).tap do |allowed_params| scope_params
allowed_params.each do |k, v| .slice(*params)
v.blank? ? allowed_params.delete(k) : allowed_params[k] = v.split(',') .tap do |allowed_params|
allowed_params.each do |k, v|
v.blank? ? allowed_params.delete(k) : allowed_params[k] = v.split(",")
end
end end
end
end end
def update_params def update_params
@ -146,5 +148,4 @@ class Admin::ApiController < Admin::AdminController
def log_api_key_restore(*args) def log_api_key_restore(*args)
StaffActionLogger.new(current_user).log_api_key_restore(*args) StaffActionLogger.new(current_user).log_api_key_restore(*args)
end end
end end

View File

@ -7,7 +7,7 @@ class Admin::BackupsController < Admin::AdminController
include ExternalUploadHelpers include ExternalUploadHelpers
before_action :ensure_backups_enabled before_action :ensure_backups_enabled
skip_before_action :check_xhr, only: [:index, :show, :logs, :check_backup_chunk, :upload_backup_chunk] skip_before_action :check_xhr, only: %i[index show logs check_backup_chunk upload_backup_chunk]
def index def index
respond_to do |format| respond_to do |format|
@ -62,7 +62,7 @@ class Admin::BackupsController < Admin::AdminController
Jobs.enqueue( Jobs.enqueue(
:download_backup_email, :download_backup_email,
user_id: current_user.id, user_id: current_user.id,
backup_file_path: url_for(controller: 'backups', action: 'show') backup_file_path: url_for(controller: "backups", action: "show"),
) )
render body: nil render body: nil
@ -73,8 +73,8 @@ class Admin::BackupsController < Admin::AdminController
def show def show
if !EmailBackupToken.compare(current_user.id, params.fetch(:token)) if !EmailBackupToken.compare(current_user.id, params.fetch(:token))
@error = I18n.t('download_backup_mailer.no_token') @error = I18n.t("download_backup_mailer.no_token")
return render layout: 'no_ember', status: 422, formats: [:html] return render layout: "no_ember", status: 422, formats: [:html]
end end
store = BackupRestore::BackupStore.create store = BackupRestore::BackupStore.create
@ -86,7 +86,7 @@ class Admin::BackupsController < Admin::AdminController
if store.remote? if store.remote?
redirect_to backup.source redirect_to backup.source
else else
headers['Content-Length'] = File.size(backup.source).to_s headers["Content-Length"] = File.size(backup.source).to_s
send_file backup.source send_file backup.source
end end
else else
@ -170,9 +170,15 @@ class Admin::BackupsController < Admin::AdminController
identifier = params.fetch(:resumableIdentifier) identifier = params.fetch(:resumableIdentifier)
raise Discourse::InvalidParameters.new(:resumableIdentifier) unless valid_filename?(identifier) raise Discourse::InvalidParameters.new(:resumableIdentifier) unless valid_filename?(identifier)
return render status: 415, plain: I18n.t("backup.backup_file_should_be_tar_gz") unless valid_extension?(filename) unless valid_extension?(filename)
return render status: 415, plain: I18n.t("backup.not_enough_space_on_disk") unless has_enough_space_on_disk?(total_size) return render status: 415, plain: I18n.t("backup.backup_file_should_be_tar_gz")
return render status: 415, plain: I18n.t("backup.invalid_filename") unless valid_filename?(filename) end
unless has_enough_space_on_disk?(total_size)
return render status: 415, plain: I18n.t("backup.not_enough_space_on_disk")
end
unless valid_filename?(filename)
return render status: 415, plain: I18n.t("backup.invalid_filename")
end
file = params.fetch(:file) file = params.fetch(:file)
chunk_number = params.fetch(:resumableChunkNumber).to_i chunk_number = params.fetch(:resumableChunkNumber).to_i
@ -187,7 +193,13 @@ class Admin::BackupsController < Admin::AdminController
uploaded_file_size = previous_chunk_number * chunk_size uploaded_file_size = previous_chunk_number * chunk_size
if uploaded_file_size + current_chunk_size >= total_size if uploaded_file_size + current_chunk_size >= total_size
# merge all the chunks in a background thread # merge all the chunks in a background thread
Jobs.enqueue_in(5.seconds, :backup_chunks_merger, filename: filename, identifier: identifier, chunks: chunk_number) Jobs.enqueue_in(
5.seconds,
:backup_chunks_merger,
filename: filename,
identifier: identifier,
chunks: chunk_number,
)
end end
render body: nil render body: nil
@ -197,7 +209,9 @@ class Admin::BackupsController < Admin::AdminController
params.require(:filename) params.require(:filename)
filename = params.fetch(:filename) filename = params.fetch(:filename)
return render_json_error(I18n.t("backup.backup_file_should_be_tar_gz")) unless valid_extension?(filename) unless valid_extension?(filename)
return render_json_error(I18n.t("backup.backup_file_should_be_tar_gz"))
end
return render_json_error(I18n.t("backup.invalid_filename")) unless valid_filename?(filename) return render_json_error(I18n.t("backup.invalid_filename")) unless valid_filename?(filename)
store = BackupRestore::BackupStore.create store = BackupRestore::BackupStore.create
@ -236,8 +250,16 @@ class Admin::BackupsController < Admin::AdminController
end end
def validate_before_create_multipart(file_name:, file_size:, upload_type:) def validate_before_create_multipart(file_name:, file_size:, upload_type:)
raise ExternalUploadHelpers::ExternalUploadValidationError.new(I18n.t("backup.backup_file_should_be_tar_gz")) unless valid_extension?(file_name) unless valid_extension?(file_name)
raise ExternalUploadHelpers::ExternalUploadValidationError.new(I18n.t("backup.invalid_filename")) unless valid_filename?(file_name) raise ExternalUploadHelpers::ExternalUploadValidationError.new(
I18n.t("backup.backup_file_should_be_tar_gz"),
)
end
unless valid_filename?(file_name)
raise ExternalUploadHelpers::ExternalUploadValidationError.new(
I18n.t("backup.invalid_filename"),
)
end
end end
def self.serialize_upload(_upload) def self.serialize_upload(_upload)
@ -248,7 +270,11 @@ class Admin::BackupsController < Admin::AdminController
begin begin
yield yield
rescue BackupRestore::BackupStore::StorageError => err rescue BackupRestore::BackupStore::StorageError => err
message = debug_upload_error(err, I18n.t("upload.create_multipart_failure", additional_detail: err.message)) message =
debug_upload_error(
err,
I18n.t("upload.create_multipart_failure", additional_detail: err.message),
)
raise ExternalUploadHelpers::ExternalUploadValidationError.new(message) raise ExternalUploadHelpers::ExternalUploadValidationError.new(message)
rescue BackupRestore::BackupStore::BackupFileExists rescue BackupRestore::BackupStore::BackupFileExists
raise ExternalUploadHelpers::ExternalUploadValidationError.new(I18n.t("backup.file_exists")) raise ExternalUploadHelpers::ExternalUploadValidationError.new(I18n.t("backup.file_exists"))

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'csv' require "csv"
class Admin::BadgesController < Admin::AdminController class Admin::BadgesController < Admin::AdminController
MAX_CSV_LINES = 50_000 MAX_CSV_LINES = 50_000
@ -10,25 +10,29 @@ class Admin::BadgesController < Admin::AdminController
data = { data = {
badge_types: BadgeType.all.order(:id).to_a, badge_types: BadgeType.all.order(:id).to_a,
badge_groupings: BadgeGrouping.all.order(:position).to_a, badge_groupings: BadgeGrouping.all.order(:position).to_a,
badges: Badge.includes(:badge_grouping) badges:
.includes(:badge_type, :image_upload) Badge
.references(:badge_grouping) .includes(:badge_grouping)
.order('badge_groupings.position, badge_type_id, badges.name').to_a, .includes(:badge_type, :image_upload)
.references(:badge_grouping)
.order("badge_groupings.position, badge_type_id, badges.name")
.to_a,
protected_system_fields: Badge.protected_system_fields, protected_system_fields: Badge.protected_system_fields,
triggers: Badge.trigger_hash triggers: Badge.trigger_hash,
} }
render_serialized(OpenStruct.new(data), AdminBadgesSerializer) render_serialized(OpenStruct.new(data), AdminBadgesSerializer)
end end
def preview def preview
unless SiteSetting.enable_badge_sql return render json: "preview not allowed", status: 403 unless SiteSetting.enable_badge_sql
return render json: "preview not allowed", status: 403
end
render json: BadgeGranter.preview(params[:sql], render json:
target_posts: params[:target_posts] == "true", BadgeGranter.preview(
explain: params[:explain] == "true", params[:sql],
trigger: params[:trigger].to_i) target_posts: params[:target_posts] == "true",
explain: params[:explain] == "true",
trigger: params[:trigger].to_i,
)
end end
def new def new
@ -47,18 +51,21 @@ class Admin::BadgesController < Admin::AdminController
if !badge.enabled? if !badge.enabled?
render_json_error( render_json_error(
I18n.t('badges.mass_award.errors.badge_disabled', badge_name: badge.display_name), I18n.t("badges.mass_award.errors.badge_disabled", badge_name: badge.display_name),
status: 422 status: 422,
) )
return return
end end
replace_badge_owners = params[:replace_badge_owners] == 'true' replace_badge_owners = params[:replace_badge_owners] == "true"
ensure_users_have_badge_once = params[:grant_existing_holders] != 'true' ensure_users_have_badge_once = params[:grant_existing_holders] != "true"
if !ensure_users_have_badge_once && !badge.multiple_grant? if !ensure_users_have_badge_once && !badge.multiple_grant?
render_json_error( render_json_error(
I18n.t('badges.mass_award.errors.cant_grant_multiple_times', badge_name: badge.display_name), I18n.t(
status: 422 "badges.mass_award.errors.cant_grant_multiple_times",
badge_name: badge.display_name,
),
status: 422,
) )
return return
end end
@ -72,7 +79,7 @@ class Admin::BadgesController < Admin::AdminController
line_number += 1 line_number += 1
if line.present? if line.present?
if line.include?('@') if line.include?("@")
emails << line emails << line
else else
usernames << line usernames << line
@ -80,26 +87,35 @@ class Admin::BadgesController < Admin::AdminController
end end
if emails.size + usernames.size > MAX_CSV_LINES if emails.size + usernames.size > MAX_CSV_LINES
return render_json_error I18n.t('badges.mass_award.errors.too_many_csv_entries', count: MAX_CSV_LINES), status: 400 return(
render_json_error I18n.t(
"badges.mass_award.errors.too_many_csv_entries",
count: MAX_CSV_LINES,
),
status: 400
)
end end
end end
end end
BadgeGranter.revoke_all(badge) if replace_badge_owners BadgeGranter.revoke_all(badge) if replace_badge_owners
results = BadgeGranter.enqueue_mass_grant_for_users( results =
badge, BadgeGranter.enqueue_mass_grant_for_users(
emails: emails, badge,
usernames: usernames, emails: emails,
ensure_users_have_badge_once: ensure_users_have_badge_once usernames: usernames,
) ensure_users_have_badge_once: ensure_users_have_badge_once,
)
render json: { render json: {
unmatched_entries: results[:unmatched_entries].first(100), unmatched_entries: results[:unmatched_entries].first(100),
matched_users_count: results[:matched_users_count], matched_users_count: results[:matched_users_count],
unmatched_entries_count: results[:unmatched_entries_count] unmatched_entries_count: results[:unmatched_entries_count],
}, status: :ok },
status: :ok
rescue CSV::MalformedCSVError rescue CSV::MalformedCSVError
render_json_error I18n.t('badges.mass_award.errors.invalid_csv', line_number: line_number), status: 400 render_json_error I18n.t("badges.mass_award.errors.invalid_csv", line_number: line_number),
status: 400
end end
def badge_types def badge_types
@ -119,9 +135,7 @@ class Admin::BadgesController < Admin::AdminController
group.save group.save
end end
badge_groupings.each do |g| badge_groupings.each { |g| g.destroy unless g.system? || ids.include?(g.id) }
g.destroy unless g.system? || ids.include?(g.id)
end
badge_groupings = BadgeGrouping.all.order(:position).to_a badge_groupings = BadgeGrouping.all.order(:position).to_a
render_serialized(badge_groupings, BadgeGroupingSerializer, root: "badge_groupings") render_serialized(badge_groupings, BadgeGroupingSerializer, root: "badge_groupings")
@ -173,21 +187,23 @@ class Admin::BadgesController < Admin::AdminController
def update_badge_from_params(badge, opts = {}) def update_badge_from_params(badge, opts = {})
errors = [] errors = []
Badge.transaction do Badge.transaction do
allowed = Badge.column_names.map(&:to_sym) allowed = Badge.column_names.map(&:to_sym)
allowed -= [:id, :created_at, :updated_at, :grant_count] allowed -= %i[id created_at updated_at grant_count]
allowed -= Badge.protected_system_fields if badge.system? allowed -= Badge.protected_system_fields if badge.system?
allowed -= [:query] unless SiteSetting.enable_badge_sql allowed -= [:query] unless SiteSetting.enable_badge_sql
params.permit(*allowed) params.permit(*allowed)
allowed.each do |key| allowed.each { |key| badge.public_send("#{key}=", params[key]) if params[key] }
badge.public_send("#{key}=" , params[key]) if params[key]
end
# Badge query contract checks # Badge query contract checks
begin begin
if SiteSetting.enable_badge_sql if SiteSetting.enable_badge_sql
BadgeGranter.contract_checks!(badge.query, target_posts: badge.target_posts, trigger: badge.trigger) BadgeGranter.contract_checks!(
badge.query,
target_posts: badge.target_posts,
trigger: badge.trigger,
)
end end
rescue => e rescue => e
errors << e.message errors << e.message
@ -203,7 +219,7 @@ class Admin::BadgesController < Admin::AdminController
:bulk_user_title_update, :bulk_user_title_update,
new_title: badge.name, new_title: badge.name,
granted_badge_id: badge.id, granted_badge_id: badge.id,
action: Jobs::BulkUserTitleUpdate::UPDATE_ACTION action: Jobs::BulkUserTitleUpdate::UPDATE_ACTION,
) )
end end

View File

@ -1,11 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::ColorSchemesController < Admin::AdminController class Admin::ColorSchemesController < Admin::AdminController
before_action :fetch_color_scheme, only: %i[update destroy]
before_action :fetch_color_scheme, only: [:update, :destroy]
def index def index
render_serialized(ColorScheme.base_color_schemes + ColorScheme.order('id ASC').all.to_a, ColorSchemeSerializer) render_serialized(
ColorScheme.base_color_schemes + ColorScheme.order("id ASC").all.to_a,
ColorSchemeSerializer,
)
end end
def create def create
@ -38,6 +40,8 @@ class Admin::ColorSchemesController < Admin::AdminController
end end
def color_scheme_params def color_scheme_params
params.permit(color_scheme: [:base_scheme_id, :name, :user_selectable, colors: [:name, :hex]])[:color_scheme] params.permit(color_scheme: [:base_scheme_id, :name, :user_selectable, colors: %i[name hex]])[
:color_scheme
]
end end
end end

View File

@ -11,9 +11,12 @@ class Admin::DashboardController < Admin::StaffController
render json: data render json: data
end end
def moderation; end def moderation
def security; end end
def reports; end def security
end
def reports
end
def general def general
render json: AdminDashboardGeneralData.fetch_cached_stats render json: AdminDashboardGeneralData.fetch_cached_stats
@ -33,7 +36,7 @@ class Admin::DashboardController < Admin::StaffController
data = { data = {
new_features: new_features, new_features: new_features,
has_unseen_features: DiscourseUpdates.has_unseen_features?(current_user.id), has_unseen_features: DiscourseUpdates.has_unseen_features?(current_user.id),
release_notes_link: AdminDashboardGeneralData.fetch_cached_stats["release_notes_link"] release_notes_link: AdminDashboardGeneralData.fetch_cached_stats["release_notes_link"],
} }
render json: data render json: data
end end

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::EmailController < Admin::AdminController class Admin::EmailController < Admin::AdminController
def index def index
data = { delivery_method: delivery_method, settings: delivery_settings } data = { delivery_method: delivery_method, settings: delivery_settings }
render_json_dump(data) render_json_dump(data)
@ -35,29 +34,22 @@ class Admin::EmailController < Admin::AdminController
else else
email_logs.where( email_logs.where(
"replace(post_reply_keys.reply_key::VARCHAR, '-', '') ILIKE ?", "replace(post_reply_keys.reply_key::VARCHAR, '-', '') ILIKE ?",
"%#{reply_key}%" "%#{reply_key}%",
) )
end end
end end
email_logs = email_logs.to_a email_logs = email_logs.to_a
tuples = email_logs.map do |email_log| tuples = email_logs.map { |email_log| [email_log.post_id, email_log.user_id] }
[email_log.post_id, email_log.user_id]
end
reply_keys = {} reply_keys = {}
if tuples.present? if tuples.present?
PostReplyKey PostReplyKey
.where( .where("(post_id,user_id) IN (#{(["(?)"] * tuples.size).join(", ")})", *tuples)
"(post_id,user_id) IN (#{(['(?)'] * tuples.size).join(', ')})",
*tuples
)
.pluck(:post_id, :user_id, "reply_key::text") .pluck(:post_id, :user_id, "reply_key::text")
.each do |post_id, user_id, key| .each { |post_id, user_id, key| reply_keys[[post_id, user_id]] = key }
reply_keys[[post_id, user_id]] = key
end
end end
render_serialized(email_logs, EmailLogSerializer, reply_keys: reply_keys) render_serialized(email_logs, EmailLogSerializer, reply_keys: reply_keys)
@ -96,14 +88,10 @@ class Admin::EmailController < Admin::AdminController
def advanced_test def advanced_test
params.require(:email) params.require(:email)
receiver = Email::Receiver.new(params['email']) receiver = Email::Receiver.new(params["email"])
text, elided, format = receiver.select_body text, elided, format = receiver.select_body
render json: success_json.merge!( render json: success_json.merge!(text: text, elided: elided, format: format)
text: text,
elided: elided,
format: format
)
end end
def send_digest def send_digest
@ -112,9 +100,8 @@ class Admin::EmailController < Admin::AdminController
params.require(:email) params.require(:email)
user = User.find_by_username(params[:username]) user = User.find_by_username(params[:username])
message, skip_reason = UserNotifications.public_send(:digest, user, message, skip_reason =
since: params[:last_seen_at] UserNotifications.public_send(:digest, user, since: params[:last_seen_at])
)
if message if message
message.to = params[:email] message.to = params[:email]
@ -134,9 +121,16 @@ class Admin::EmailController < Admin::AdminController
params.require(:to) params.require(:to)
# These strings aren't localized; they are sent to an anonymous SMTP user. # These strings aren't localized; they are sent to an anonymous SMTP user.
if !User.with_email(Email.downcase(params[:from])).exists? && !SiteSetting.enable_staged_users if !User.with_email(Email.downcase(params[:from])).exists? && !SiteSetting.enable_staged_users
render json: { reject: true, reason: "Mail from your address is not accepted. Do you have an account here?" } render json: {
reject: true,
reason: "Mail from your address is not accepted. Do you have an account here?",
}
elsif Email::Receiver.check_address(Email.downcase(params[:to])).nil? elsif Email::Receiver.check_address(Email.downcase(params[:to])).nil?
render json: { reject: true, reason: "Mail to this address is not accepted. Check the address and try to send again?" } render json: {
reject: true,
reason:
"Mail to this address is not accepted. Check the address and try to send again?",
}
else else
render json: { reject: false } render json: { reject: false }
end end
@ -157,10 +151,15 @@ class Admin::EmailController < Admin::AdminController
retry_count = 0 retry_count = 0
begin begin
Jobs.enqueue(:process_email, mail: email_raw, retry_on_rate_limit: true, source: "handle_mail") Jobs.enqueue(
:process_email,
mail: email_raw,
retry_on_rate_limit: true,
source: "handle_mail",
)
rescue JSON::GeneratorError, Encoding::UndefinedConversionError => e rescue JSON::GeneratorError, Encoding::UndefinedConversionError => e
if retry_count == 0 if retry_count == 0
email_raw = email_raw.force_encoding('iso-8859-1').encode("UTF-8") email_raw = email_raw.force_encoding("iso-8859-1").encode("UTF-8")
retry_count += 1 retry_count += 1
retry retry
else else
@ -171,7 +170,8 @@ class Admin::EmailController < Admin::AdminController
# TODO: 2022-05-01 Remove this route once all sites have migrated over # TODO: 2022-05-01 Remove this route once all sites have migrated over
# to using the new email_encoded param. # to using the new email_encoded param.
if deprecated_email_param_used if deprecated_email_param_used
render plain: "warning: the email parameter is deprecated. all POST requests to this route should be sent with a base64 strict encoded email_encoded parameter instead. email has been received and is queued for processing" render plain:
"warning: the email parameter is deprecated. all POST requests to this route should be sent with a base64 strict encoded email_encoded parameter instead. email has been received and is queued for processing"
else else
render plain: "email has been received and is queued for processing" render plain: "email has been received and is queued for processing"
end end
@ -204,15 +204,15 @@ class Admin::EmailController < Admin::AdminController
end end
if incoming_email.nil? if incoming_email.nil?
email_local_part, email_domain = SiteSetting.notification_email.split('@') email_local_part, email_domain = SiteSetting.notification_email.split("@")
bounced_to_address = "#{email_local_part}+verp-#{email_log.bounce_key}@#{email_domain}" bounced_to_address = "#{email_local_part}+verp-#{email_log.bounce_key}@#{email_domain}"
incoming_email = IncomingEmail.find_by(to_addresses: bounced_to_address) incoming_email = IncomingEmail.find_by(to_addresses: bounced_to_address)
end end
# Temporary fix until all old format of emails has been purged via lib/email/cleaner.rb # Temporary fix until all old format of emails has been purged via lib/email/cleaner.rb
if incoming_email.nil? if incoming_email.nil?
email_local_part, email_domain = SiteSetting.reply_by_email_address.split('@') email_local_part, email_domain = SiteSetting.reply_by_email_address.split("@")
subdomain, root_domain, extension = email_domain&.split('.') subdomain, root_domain, extension = email_domain&.split(".")
bounced_to_address = "#{subdomain}+verp-#{email_log.bounce_key}@#{root_domain}.#{extension}" bounced_to_address = "#{subdomain}+verp-#{email_log.bounce_key}@#{root_domain}.#{extension}"
incoming_email = IncomingEmail.find_by(to_addresses: bounced_to_address) incoming_email = IncomingEmail.find_by(to_addresses: bounced_to_address)
end end
@ -231,41 +231,61 @@ class Admin::EmailController < Admin::AdminController
def filter_logs(logs, params) def filter_logs(logs, params)
table_name = logs.table_name table_name = logs.table_name
logs = logs.includes(:user, post: :topic) logs =
.references(:user) logs
.order(created_at: :desc) .includes(:user, post: :topic)
.offset(params[:offset] || 0) .references(:user)
.limit(50) .order(created_at: :desc)
.offset(params[:offset] || 0)
.limit(50)
logs = logs.where("users.username ILIKE ?", "%#{params[:user]}%") if params[:user].present? logs = logs.where("users.username ILIKE ?", "%#{params[:user]}%") if params[:user].present?
logs = logs.where("#{table_name}.to_address ILIKE ?", "%#{params[:address]}%") if params[:address].present? logs = logs.where("#{table_name}.to_address ILIKE ?", "%#{params[:address]}%") if params[
logs = logs.where("#{table_name}.email_type ILIKE ?", "%#{params[:type]}%") if params[:type].present? :address
].present?
logs = logs.where("#{table_name}.email_type ILIKE ?", "%#{params[:type]}%") if params[
:type
].present?
if table_name == "email_logs" && params[:smtp_transaction_response].present? if table_name == "email_logs" && params[:smtp_transaction_response].present?
logs = logs.where("#{table_name}.smtp_transaction_response ILIKE ?", "%#{params[:smtp_transaction_response]}%") logs =
logs.where(
"#{table_name}.smtp_transaction_response ILIKE ?",
"%#{params[:smtp_transaction_response]}%",
)
end end
logs logs
end end
def filter_incoming_emails(incoming_emails, params) def filter_incoming_emails(incoming_emails, params)
incoming_emails = incoming_emails.includes(:user, post: :topic) incoming_emails =
.order(created_at: :desc) incoming_emails
.offset(params[:offset] || 0) .includes(:user, post: :topic)
.limit(50) .order(created_at: :desc)
.offset(params[:offset] || 0)
.limit(50)
incoming_emails = incoming_emails.where("from_address ILIKE ?", "%#{params[:from]}%") if params[:from].present? incoming_emails = incoming_emails.where("from_address ILIKE ?", "%#{params[:from]}%") if params[
incoming_emails = incoming_emails.where("to_addresses ILIKE :to OR cc_addresses ILIKE :to", to: "%#{params[:to]}%") if params[:to].present? :from
incoming_emails = incoming_emails.where("subject ILIKE ?", "%#{params[:subject]}%") if params[:subject].present? ].present?
incoming_emails = incoming_emails.where("error ILIKE ?", "%#{params[:error]}%") if params[:error].present? incoming_emails =
incoming_emails.where(
"to_addresses ILIKE :to OR cc_addresses ILIKE :to",
to: "%#{params[:to]}%",
) if params[:to].present?
incoming_emails = incoming_emails.where("subject ILIKE ?", "%#{params[:subject]}%") if params[
:subject
].present?
incoming_emails = incoming_emails.where("error ILIKE ?", "%#{params[:error]}%") if params[
:error
].present?
incoming_emails incoming_emails
end end
def delivery_settings def delivery_settings
action_mailer_settings action_mailer_settings.reject { |k, _| k == :password }.map { |k, v| { name: k, value: v } }
.reject { |k, _| k == :password }
.map { |k, v| { name: k, value: v } }
end end
def delivery_method def delivery_method

View File

@ -1,70 +1,69 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::EmailTemplatesController < Admin::AdminController class Admin::EmailTemplatesController < Admin::AdminController
def self.email_keys def self.email_keys
@email_keys ||= [ @email_keys ||= %w[
"custom_invite_forum_mailer", custom_invite_forum_mailer
"custom_invite_mailer", custom_invite_mailer
"invite_forum_mailer", invite_forum_mailer
"invite_mailer", invite_mailer
"invite_password_instructions", invite_password_instructions
"new_version_mailer", new_version_mailer
"new_version_mailer_with_notes", new_version_mailer_with_notes
"system_messages.backup_failed", system_messages.backup_failed
"system_messages.backup_succeeded", system_messages.backup_succeeded
"system_messages.bulk_invite_failed", system_messages.bulk_invite_failed
"system_messages.bulk_invite_succeeded", system_messages.bulk_invite_succeeded
"system_messages.csv_export_failed", system_messages.csv_export_failed
"system_messages.csv_export_succeeded", system_messages.csv_export_succeeded
"system_messages.download_remote_images_disabled", system_messages.download_remote_images_disabled
"system_messages.email_error_notification", system_messages.email_error_notification
"system_messages.email_reject_auto_generated", system_messages.email_reject_auto_generated
"system_messages.email_reject_bad_destination_address", system_messages.email_reject_bad_destination_address
"system_messages.email_reject_empty", system_messages.email_reject_empty
"system_messages.email_reject_invalid_access", system_messages.email_reject_invalid_access
"system_messages.email_reject_parsing", system_messages.email_reject_parsing
"system_messages.email_reject_reply_key", system_messages.email_reject_reply_key
"system_messages.email_reject_screened_email", system_messages.email_reject_screened_email
"system_messages.email_reject_topic_closed", system_messages.email_reject_topic_closed
"system_messages.email_reject_topic_not_found", system_messages.email_reject_topic_not_found
"system_messages.email_reject_unrecognized_error", system_messages.email_reject_unrecognized_error
"system_messages.email_reject_user_not_found", system_messages.email_reject_user_not_found
"system_messages.email_revoked", system_messages.email_revoked
"system_messages.pending_users_reminder", system_messages.pending_users_reminder
"system_messages.post_hidden", system_messages.post_hidden
"system_messages.post_hidden_again", system_messages.post_hidden_again
"system_messages.queued_posts_reminder", system_messages.queued_posts_reminder
"system_messages.restore_failed", system_messages.restore_failed
"system_messages.restore_succeeded", system_messages.restore_succeeded
"system_messages.silenced_by_staff", system_messages.silenced_by_staff
"system_messages.spam_post_blocked", system_messages.spam_post_blocked
"system_messages.too_many_spam_flags", system_messages.too_many_spam_flags
"system_messages.unsilenced", system_messages.unsilenced
"system_messages.user_automatically_silenced", system_messages.user_automatically_silenced
"system_messages.welcome_invite", system_messages.welcome_invite
"system_messages.welcome_user", system_messages.welcome_user
"system_messages.welcome_staff", system_messages.welcome_staff
"test_mailer", test_mailer
"user_notifications.account_created", user_notifications.account_created
"user_notifications.admin_login", user_notifications.admin_login
"user_notifications.confirm_new_email", user_notifications.confirm_new_email
"user_notifications.email_login", user_notifications.email_login
"user_notifications.forgot_password", user_notifications.forgot_password
"user_notifications.notify_old_email", user_notifications.notify_old_email
"user_notifications.set_password", user_notifications.set_password
"user_notifications.signup", user_notifications.signup
"user_notifications.signup_after_approval", user_notifications.signup_after_approval
"user_notifications.suspicious_login", user_notifications.suspicious_login
"user_notifications.user_invited_to_private_message_pm", user_notifications.user_invited_to_private_message_pm
"user_notifications.user_invited_to_private_message_pm_group", user_notifications.user_invited_to_private_message_pm_group
"user_notifications.user_invited_to_topic", user_notifications.user_invited_to_topic
"user_notifications.user_linked", user_notifications.user_linked
"user_notifications.user_mentioned", user_notifications.user_mentioned
"user_notifications.user_posted", user_notifications.user_posted
"user_notifications.user_posted_pm", user_notifications.user_posted_pm
"user_notifications.user_quoted", user_notifications.user_quoted
"user_notifications.user_replied", user_notifications.user_replied
] ]
end end
@ -91,9 +90,18 @@ class Admin::EmailTemplatesController < Admin::AdminController
log_site_text_change(subject_result) log_site_text_change(subject_result)
log_site_text_change(body_result) log_site_text_change(body_result)
render_serialized(key, AdminEmailTemplateSerializer, root: 'email_template', rest_serializer: true) render_serialized(
key,
AdminEmailTemplateSerializer,
root: "email_template",
rest_serializer: true,
)
else else
TranslationOverride.upsert!(I18n.locale, "#{key}.subject_template", subject_result[:old_value]) TranslationOverride.upsert!(
I18n.locale,
"#{key}.subject_template",
subject_result[:old_value],
)
TranslationOverride.upsert!(I18n.locale, "#{key}.text_body_template", body_result[:old_value]) TranslationOverride.upsert!(I18n.locale, "#{key}.text_body_template", body_result[:old_value])
render_json_error(error_messages) render_json_error(error_messages)
@ -105,11 +113,22 @@ class Admin::EmailTemplatesController < Admin::AdminController
raise Discourse::NotFound unless self.class.email_keys.include?(params[:id]) raise Discourse::NotFound unless self.class.email_keys.include?(params[:id])
revert_and_log("#{key}.subject_template", "#{key}.text_body_template") revert_and_log("#{key}.subject_template", "#{key}.text_body_template")
render_serialized(key, AdminEmailTemplateSerializer, root: 'email_template', rest_serializer: true) render_serialized(
key,
AdminEmailTemplateSerializer,
root: "email_template",
rest_serializer: true,
)
end end
def index def index
render_serialized(self.class.email_keys, AdminEmailTemplateSerializer, root: 'email_templates', rest_serializer: true, overridden_keys: overridden_keys) render_serialized(
self.class.email_keys,
AdminEmailTemplateSerializer,
root: "email_templates",
rest_serializer: true,
overridden_keys: overridden_keys,
)
end end
private private
@ -121,11 +140,7 @@ class Admin::EmailTemplatesController < Admin::AdminController
translation_override = TranslationOverride.upsert!(I18n.locale, key, value) translation_override = TranslationOverride.upsert!(I18n.locale, key, value)
end end
{ { key: key, old_value: old_value, error_messages: translation_override&.errors&.full_messages }
key: key,
old_value: old_value,
error_messages: translation_override&.errors&.full_messages
}
end end
def revert_and_log(*keys) def revert_and_log(*keys)
@ -144,7 +159,10 @@ class Admin::EmailTemplatesController < Admin::AdminController
def log_site_text_change(update_result) def log_site_text_change(update_result)
new_value = I18n.t(update_result[:key]) new_value = I18n.t(update_result[:key])
StaffActionLogger.new(current_user).log_site_text_change( StaffActionLogger.new(current_user).log_site_text_change(
update_result[:key], new_value, update_result[:old_value]) update_result[:key],
new_value,
update_result[:old_value],
)
end end
def format_error_message(update_result, attribute_key) def format_error_message(update_result, attribute_key)

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::EmbeddableHostsController < Admin::AdminController class Admin::EmbeddableHostsController < Admin::AdminController
def create def create
save_host(EmbeddableHost.new, :create) save_host(EmbeddableHost.new, :create)
end end
@ -14,7 +13,10 @@ class Admin::EmbeddableHostsController < Admin::AdminController
def destroy def destroy
host = EmbeddableHost.where(id: params[:id]).first host = EmbeddableHost.where(id: params[:id]).first
host.destroy host.destroy
StaffActionLogger.new(current_user).log_embeddable_host(host, UserHistory.actions[:embeddable_host_destroy]) StaffActionLogger.new(current_user).log_embeddable_host(
host,
UserHistory.actions[:embeddable_host_destroy],
)
render json: success_json render json: success_json
end end
@ -23,17 +25,25 @@ class Admin::EmbeddableHostsController < Admin::AdminController
def save_host(host, action) def save_host(host, action)
host.host = params[:embeddable_host][:host] host.host = params[:embeddable_host][:host]
host.allowed_paths = params[:embeddable_host][:allowed_paths] host.allowed_paths = params[:embeddable_host][:allowed_paths]
host.class_name = params[:embeddable_host][:class_name] host.class_name = params[:embeddable_host][:class_name]
host.category_id = params[:embeddable_host][:category_id] host.category_id = params[:embeddable_host][:category_id]
host.category_id = SiteSetting.uncategorized_category_id if host.category_id.blank? host.category_id = SiteSetting.uncategorized_category_id if host.category_id.blank?
if host.save if host.save
changes = host.saved_changes if action == :update changes = host.saved_changes if action == :update
StaffActionLogger.new(current_user).log_embeddable_host(host, UserHistory.actions[:"embeddable_host_#{action}"], changes: changes) StaffActionLogger.new(current_user).log_embeddable_host(
render_serialized(host, EmbeddableHostSerializer, root: 'embeddable_host', rest_serializer: true) host,
UserHistory.actions[:"embeddable_host_#{action}"],
changes: changes,
)
render_serialized(
host,
EmbeddableHostSerializer,
root: "embeddable_host",
rest_serializer: true,
)
else else
render_json_error(host) render_json_error(host)
end end
end end
end end

View File

@ -1,25 +1,22 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::EmbeddingController < Admin::AdminController class Admin::EmbeddingController < Admin::AdminController
before_action :fetch_embedding before_action :fetch_embedding
def show def show
render_serialized(@embedding, EmbeddingSerializer, root: 'embedding', rest_serializer: true) render_serialized(@embedding, EmbeddingSerializer, root: "embedding", rest_serializer: true)
end end
def update def update
if params[:embedding][:embed_by_username].blank? if params[:embedding][:embed_by_username].blank?
return render_json_error(I18n.t('site_settings.embed_username_required')) return render_json_error(I18n.t("site_settings.embed_username_required"))
end end
Embedding.settings.each do |s| Embedding.settings.each { |s| @embedding.public_send("#{s}=", params[:embedding][s]) }
@embedding.public_send("#{s}=", params[:embedding][s])
end
if @embedding.save if @embedding.save
fetch_embedding fetch_embedding
render_serialized(@embedding, EmbeddingSerializer, root: 'embedding', rest_serializer: true) render_serialized(@embedding, EmbeddingSerializer, root: "embedding", rest_serializer: true)
else else
render_json_error(@embedding) render_json_error(@embedding)
end end

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::EmojisController < Admin::AdminController class Admin::EmojisController < Admin::AdminController
def index def index
render_serialized(Emoji.custom, EmojiSerializer, root: false) render_serialized(Emoji.custom, EmojiSerializer, root: false)
end end
@ -17,15 +16,12 @@ class Admin::EmojisController < Admin::AdminController
hijack do hijack do
# fix the name # fix the name
name = File.basename(name, ".*") name = File.basename(name, ".*")
name = name.gsub(/[^a-z0-9]+/i, '_') name = name.gsub(/[^a-z0-9]+/i, "_").gsub(/_{2,}/, "_").downcase
.gsub(/_{2,}/, '_')
.downcase
upload = UploadCreator.new( upload =
file.tempfile, UploadCreator.new(file.tempfile, file.original_filename, type: "custom_emoji").create_for(
file.original_filename, current_user.id,
type: 'custom_emoji' )
).create_for(current_user.id)
good = true good = true
@ -61,5 +57,4 @@ class Admin::EmojisController < Admin::AdminController
render json: success_json render json: success_json
end end
end end

View File

@ -7,27 +7,19 @@ class Admin::GroupsController < Admin::StaffController
attributes = group_params.to_h.except(:owner_usernames, :usernames) attributes = group_params.to_h.except(:owner_usernames, :usernames)
group = Group.new(attributes) group = Group.new(attributes)
unless group_params[:allow_membership_requests] group.membership_request_template = nil unless group_params[:allow_membership_requests]
group.membership_request_template = nil
end
if group_params[:owner_usernames].present? if group_params[:owner_usernames].present?
owner_ids = User.where( owner_ids = User.where(username: group_params[:owner_usernames].split(",")).pluck(:id)
username: group_params[:owner_usernames].split(",")
).pluck(:id)
owner_ids.each do |user_id| owner_ids.each { |user_id| group.group_users.build(user_id: user_id, owner: true) }
group.group_users.build(user_id: user_id, owner: true)
end
end end
if group_params[:usernames].present? if group_params[:usernames].present?
user_ids = User.where(username: group_params[:usernames].split(",")).pluck(:id) user_ids = User.where(username: group_params[:usernames].split(",")).pluck(:id)
user_ids -= owner_ids if owner_ids user_ids -= owner_ids if owner_ids
user_ids.each do |user_id| user_ids.each { |user_id| group.group_users.build(user_id: user_id) }
group.group_users.build(user_id: user_id)
end
end end
if group.save if group.save
@ -140,45 +132,43 @@ class Admin::GroupsController < Admin::StaffController
protected protected
def can_not_modify_automatic def can_not_modify_automatic
render_json_error(I18n.t('groups.errors.can_not_modify_automatic')) render_json_error(I18n.t("groups.errors.can_not_modify_automatic"))
end end
private private
def group_params def group_params
permitted = [ permitted = %i[
:name, name
:mentionable_level, mentionable_level
:messageable_level, messageable_level
:visibility_level, visibility_level
:members_visibility_level, members_visibility_level
:automatic_membership_email_domains, automatic_membership_email_domains
:title, title
:primary_group, primary_group
:grant_trust_level, grant_trust_level
:incoming_email, incoming_email
:flair_icon, flair_icon
:flair_upload_id, flair_upload_id
:flair_bg_color, flair_bg_color
:flair_color, flair_color
:bio_raw, bio_raw
:public_admission, public_admission
:public_exit, public_exit
:allow_membership_requests, allow_membership_requests
:full_name, full_name
:default_notification_level, default_notification_level
:membership_request_template, membership_request_template
:owner_usernames, owner_usernames
:usernames, usernames
:publish_read_state, publish_read_state
:notify_users notify_users
] ]
custom_fields = DiscoursePluginRegistry.editable_group_custom_fields custom_fields = DiscoursePluginRegistry.editable_group_custom_fields
permitted << { custom_fields: custom_fields } unless custom_fields.blank? permitted << { custom_fields: custom_fields } unless custom_fields.blank?
if guardian.can_associate_groups? permitted << { associated_group_ids: [] } if guardian.can_associate_groups?
permitted << { associated_group_ids: [] }
end
permitted = permitted | DiscoursePluginRegistry.group_params permitted = permitted | DiscoursePluginRegistry.group_params

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::ImpersonateController < Admin::AdminController class Admin::ImpersonateController < Admin::AdminController
def create def create
params.require(:username_or_email) params.require(:username_or_email)
@ -18,5 +17,4 @@ class Admin::ImpersonateController < Admin::AdminController
render body: nil render body: nil
end end
end end

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::PermalinksController < Admin::AdminController class Admin::PermalinksController < Admin::AdminController
before_action :fetch_permalink, only: [:destroy] before_action :fetch_permalink, only: [:destroy]
def index def index
@ -20,7 +19,8 @@ class Admin::PermalinksController < Admin::AdminController
params[:permalink_type_value] = Tag.find_by_name(params[:permalink_type_value])&.id params[:permalink_type_value] = Tag.find_by_name(params[:permalink_type_value])&.id
end end
permalink = Permalink.new(:url => params[:url], params[:permalink_type] => params[:permalink_type_value]) permalink =
Permalink.new(:url => params[:url], params[:permalink_type] => params[:permalink_type_value])
if permalink.save if permalink.save
render_serialized(permalink, PermalinkSerializer) render_serialized(permalink, PermalinkSerializer)
else else
@ -38,5 +38,4 @@ class Admin::PermalinksController < Admin::AdminController
def fetch_permalink def fetch_permalink
@permalink = Permalink.find(params[:id]) @permalink = Permalink.find(params[:id])
end end
end end

View File

@ -1,9 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::PluginsController < Admin::StaffController class Admin::PluginsController < Admin::StaffController
def index def index
render_serialized(Discourse.visible_plugins, AdminPluginSerializer, root: 'plugins') render_serialized(Discourse.visible_plugins, AdminPluginSerializer, root: "plugins")
end end
end end

View File

@ -2,24 +2,28 @@
class Admin::ReportsController < Admin::StaffController class Admin::ReportsController < Admin::StaffController
def index def index
reports_methods = ['page_view_total_reqs'] + reports_methods =
ApplicationRequest.req_types.keys ["page_view_total_reqs"] +
.select { |r| r =~ /^page_view_/ && r !~ /mobile/ } ApplicationRequest
.map { |r| r + "_reqs" } + .req_types
Report.singleton_methods.grep(/^report_(?!about|storage_stats)/) .keys
.select { |r| r =~ /^page_view_/ && r !~ /mobile/ }
.map { |r| r + "_reqs" } +
Report.singleton_methods.grep(/^report_(?!about|storage_stats)/)
reports = reports_methods.map do |name| reports =
type = name.to_s.gsub('report_', '') reports_methods.map do |name|
description = I18n.t("reports.#{type}.description", default: '') type = name.to_s.gsub("report_", "")
description_link = I18n.t("reports.#{type}.description_link", default: '') description = I18n.t("reports.#{type}.description", default: "")
description_link = I18n.t("reports.#{type}.description_link", default: "")
{ {
type: type, type: type,
title: I18n.t("reports.#{type}.title"), title: I18n.t("reports.#{type}.title"),
description: description.presence ? description : nil, description: description.presence ? description : nil,
description_link: description_link.presence ? description_link : nil description_link: description_link.presence ? description_link : nil,
} }
end end
render_json_dump(reports: reports.sort_by { |report| report[:title] }) render_json_dump(reports: reports.sort_by { |report| report[:title] })
end end
@ -32,18 +36,14 @@ class Admin::ReportsController < Admin::StaffController
args = parse_params(report_params) args = parse_params(report_params)
report = nil report = nil
if (report_params[:cache]) report = Report.find_cached(report_type, args) if (report_params[:cache])
report = Report.find_cached(report_type, args)
end
if report if report
reports << report reports << report
else else
report = Report.find(report_type, args) report = Report.find(report_type, args)
if (report_params[:cache]) && report Report.cache(report) if (report_params[:cache]) && report
Report.cache(report)
end
if report.blank? if report.blank?
report = Report._get(report_type, args) report = Report._get(report_type, args)
@ -66,22 +66,16 @@ class Admin::ReportsController < Admin::StaffController
args = parse_params(params) args = parse_params(params)
report = nil report = nil
if (params[:cache]) report = Report.find_cached(report_type, args) if (params[:cache])
report = Report.find_cached(report_type, args)
end
if report return render_json_dump(report: report) if report
return render_json_dump(report: report)
end
hijack do hijack do
report = Report.find(report_type, args) report = Report.find(report_type, args)
raise Discourse::NotFound if report.blank? raise Discourse::NotFound if report.blank?
if (params[:cache]) Report.cache(report) if (params[:cache])
Report.cache(report)
end
render_json_dump(report: report) render_json_dump(report: report)
end end
@ -91,16 +85,28 @@ class Admin::ReportsController < Admin::StaffController
def parse_params(report_params) def parse_params(report_params)
begin begin
start_date = (report_params[:start_date].present? ? Time.parse(report_params[:start_date]).to_date : 1.days.ago).beginning_of_day start_date =
end_date = (report_params[:end_date].present? ? Time.parse(report_params[:end_date]).to_date : start_date + 30.days).end_of_day (
if report_params[:start_date].present?
Time.parse(report_params[:start_date]).to_date
else
1.days.ago
end
).beginning_of_day
end_date =
(
if report_params[:end_date].present?
Time.parse(report_params[:end_date]).to_date
else
start_date + 30.days
end
).end_of_day
rescue ArgumentError => e rescue ArgumentError => e
raise Discourse::InvalidParameters.new(e.message) raise Discourse::InvalidParameters.new(e.message)
end end
facets = nil facets = nil
if Array === report_params[:facets] facets = report_params[:facets].map { |s| s.to_s.to_sym } if Array === report_params[:facets]
facets = report_params[:facets].map { |s| s.to_s.to_sym }
end
limit = nil limit = nil
if report_params.has_key?(:limit) && report_params[:limit].to_i > 0 if report_params.has_key?(:limit) && report_params[:limit].to_i > 0
@ -108,16 +114,8 @@ class Admin::ReportsController < Admin::StaffController
end end
filters = nil filters = nil
if report_params.has_key?(:filters) filters = report_params[:filters] if report_params.has_key?(:filters)
filters = report_params[:filters]
end
{ { start_date: start_date, end_date: end_date, filters: filters, facets: facets, limit: limit }
start_date: start_date,
end_date: end_date,
filters: filters,
facets: facets,
limit: limit
}
end end
end end

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::RobotsTxtController < Admin::AdminController class Admin::RobotsTxtController < Admin::AdminController
def show def show
render json: { robots_txt: current_robots_txt, overridden: @overridden } render json: { robots_txt: current_robots_txt, overridden: @overridden }
end end

View File

@ -1,9 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::ScreenedEmailsController < Admin::StaffController class Admin::ScreenedEmailsController < Admin::StaffController
def index def index
screened_emails = ScreenedEmail.limit(200).order('last_match_at desc').to_a screened_emails = ScreenedEmail.limit(200).order("last_match_at desc").to_a
render_serialized(screened_emails, ScreenedEmailSerializer) render_serialized(screened_emails, ScreenedEmailSerializer)
end end
@ -12,5 +11,4 @@ class Admin::ScreenedEmailsController < Admin::StaffController
screen.destroy! screen.destroy!
render json: success_json render json: success_json
end end
end end

View File

@ -1,16 +1,19 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::ScreenedIpAddressesController < Admin::StaffController class Admin::ScreenedIpAddressesController < Admin::StaffController
before_action :fetch_screened_ip_address, only: %i[update destroy]
before_action :fetch_screened_ip_address, only: [:update, :destroy]
def index def index
filter = params[:filter] filter = params[:filter]
filter = IPAddr.handle_wildcards(filter) filter = IPAddr.handle_wildcards(filter)
screened_ip_addresses = ScreenedIpAddress screened_ip_addresses = ScreenedIpAddress
screened_ip_addresses = screened_ip_addresses.where("cidr :filter >>= ip_address OR ip_address >>= cidr :filter", filter: filter) if filter.present? screened_ip_addresses =
screened_ip_addresses = screened_ip_addresses.limit(200).order('match_count desc') screened_ip_addresses.where(
"cidr :filter >>= ip_address OR ip_address >>= cidr :filter",
filter: filter,
) if filter.present?
screened_ip_addresses = screened_ip_addresses.limit(200).order("match_count desc")
begin begin
screened_ip_addresses = screened_ip_addresses.to_a screened_ip_addresses = screened_ip_addresses.to_a
@ -54,5 +57,4 @@ class Admin::ScreenedIpAddressesController < Admin::StaffController
def fetch_screened_ip_address def fetch_screened_ip_address
@screened_ip_address = ScreenedIpAddress.find(params[:id]) @screened_ip_address = ScreenedIpAddress.find(params[:id])
end end
end end

View File

@ -1,10 +1,15 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::ScreenedUrlsController < Admin::StaffController class Admin::ScreenedUrlsController < Admin::StaffController
def index def index
screened_urls = ScreenedUrl.select("domain, sum(match_count) as match_count, max(last_match_at) as last_match_at, min(created_at) as created_at").group(:domain).order('last_match_at DESC').to_a screened_urls =
ScreenedUrl
.select(
"domain, sum(match_count) as match_count, max(last_match_at) as last_match_at, min(created_at) as created_at",
)
.group(:domain)
.order("last_match_at DESC")
.to_a
render_serialized(screened_urls, GroupedScreenedUrlSerializer) render_serialized(screened_urls, GroupedScreenedUrlSerializer)
end end
end end

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::SearchLogsController < Admin::StaffController class Admin::SearchLogsController < Admin::StaffController
def index def index
period = params[:period] || "all" period = params[:period] || "all"
search_type = params[:search_type] || "all" search_type = params[:search_type] || "all"
@ -22,5 +21,4 @@ class Admin::SearchLogsController < Admin::StaffController
details[:search_result] = serialize_data(result, GroupedSearchResultSerializer, result: result) details[:search_result] = serialize_data(result, GroupedSearchResultSerializer, result: result)
render_json_dump(term: details) render_json_dump(term: details)
end end
end end

View File

@ -6,10 +6,7 @@ class Admin::SiteSettingsController < Admin::AdminController
end end
def index def index
render_json_dump( render_json_dump(site_settings: SiteSetting.all_settings, diags: SiteSetting.diags)
site_settings: SiteSetting.all_settings,
diags: SiteSetting.diags
)
end end
def update def update
@ -18,15 +15,17 @@ class Admin::SiteSettingsController < Admin::AdminController
value = params[id] value = params[id]
value.strip! if value.is_a?(String) value.strip! if value.is_a?(String)
new_setting_name = SiteSettings::DeprecatedSettings::SETTINGS.find do |old_name, new_name, override, _| new_setting_name =
if old_name == id SiteSettings::DeprecatedSettings::SETTINGS.find do |old_name, new_name, override, _|
if !override if old_name == id
raise Discourse::InvalidParameters, "You cannot change this site setting because it is deprecated, use #{new_name} instead." if !override
end raise Discourse::InvalidParameters,
"You cannot change this site setting because it is deprecated, use #{new_name} instead."
end
break new_name break new_name
end
end end
end
id = new_setting_name if new_setting_name id = new_setting_name if new_setting_name
@ -36,9 +35,7 @@ class Admin::SiteSettingsController < Admin::AdminController
value = Upload.get_from_urls(value.split("|")).to_a value = Upload.get_from_urls(value.split("|")).to_a
end end
if SiteSetting.type_supervisor.get_type(id) == :upload value = Upload.get_from_url(value) || "" if SiteSetting.type_supervisor.get_type(id) == :upload
value = Upload.get_from_url(value) || ""
end
update_existing_users = params[:update_existing_user].present? update_existing_users = params[:update_existing_user].present?
previous_value = value_or_default(SiteSetting.public_send(id)) if update_existing_users previous_value = value_or_default(SiteSetting.public_send(id)) if update_existing_users
@ -68,22 +65,40 @@ class Admin::SiteSettingsController < Admin::AdminController
notification_level = category_notification_level(id) notification_level = category_notification_level(id)
categories_to_unwatch = previous_category_ids - new_category_ids categories_to_unwatch = previous_category_ids - new_category_ids
CategoryUser.where(category_id: categories_to_unwatch, notification_level: notification_level).delete_all CategoryUser.where(
category_id: categories_to_unwatch,
notification_level: notification_level,
).delete_all
TopicUser TopicUser
.joins(:topic) .joins(:topic)
.where(notification_level: TopicUser.notification_levels[:watching], .where(
notifications_reason_id: TopicUser.notification_reasons[:auto_watch_category], notification_level: TopicUser.notification_levels[:watching],
topics: { category_id: categories_to_unwatch }) notifications_reason_id: TopicUser.notification_reasons[:auto_watch_category],
topics: {
category_id: categories_to_unwatch,
},
)
.update_all(notification_level: TopicUser.notification_levels[:regular]) .update_all(notification_level: TopicUser.notification_levels[:regular])
(new_category_ids - previous_category_ids).each do |category_id| (new_category_ids - previous_category_ids).each do |category_id|
skip_user_ids = CategoryUser.where(category_id: category_id).pluck(:user_id) skip_user_ids = CategoryUser.where(category_id: category_id).pluck(:user_id)
User.real.where(staged: false).where.not(id: skip_user_ids).select(:id).find_in_batches do |users| User
category_users = [] .real
users.each { |user| category_users << { category_id: category_id, user_id: user.id, notification_level: notification_level } } .where(staged: false)
CategoryUser.insert_all!(category_users) .where.not(id: skip_user_ids)
end .select(:id)
.find_in_batches do |users|
category_users = []
users.each do |user|
category_users << {
category_id: category_id,
user_id: user.id,
notification_level: notification_level,
}
end
CategoryUser.insert_all!(category_users)
end
end end
elsif id.start_with?("default_tags_") elsif id.start_with?("default_tags_")
previous_tag_ids = Tag.where(name: previous_value.split("|")).pluck(:id) previous_tag_ids = Tag.where(name: previous_value.split("|")).pluck(:id)
@ -92,19 +107,40 @@ class Admin::SiteSettingsController < Admin::AdminController
notification_level = tag_notification_level(id) notification_level = tag_notification_level(id)
TagUser.where(tag_id: (previous_tag_ids - new_tag_ids), notification_level: notification_level).delete_all TagUser.where(
tag_id: (previous_tag_ids - new_tag_ids),
notification_level: notification_level,
).delete_all
(new_tag_ids - previous_tag_ids).each do |tag_id| (new_tag_ids - previous_tag_ids).each do |tag_id|
skip_user_ids = TagUser.where(tag_id: tag_id).pluck(:user_id) skip_user_ids = TagUser.where(tag_id: tag_id).pluck(:user_id)
User.real.where(staged: false).where.not(id: skip_user_ids).select(:id).find_in_batches do |users| User
tag_users = [] .real
users.each { |user| tag_users << { tag_id: tag_id, user_id: user.id, notification_level: notification_level, created_at: now, updated_at: now } } .where(staged: false)
TagUser.insert_all!(tag_users) .where.not(id: skip_user_ids)
end .select(:id)
.find_in_batches do |users|
tag_users = []
users.each do |user|
tag_users << {
tag_id: tag_id,
user_id: user.id,
notification_level: notification_level,
created_at: now,
updated_at: now,
}
end
TagUser.insert_all!(tag_users)
end
end end
elsif is_sidebar_default_setting?(id) elsif is_sidebar_default_setting?(id)
Jobs.enqueue(:backfill_sidebar_site_settings, setting_name: id, previous_value: previous_value, new_value: new_value) Jobs.enqueue(
:backfill_sidebar_site_settings,
setting_name: id,
previous_value: previous_value,
new_value: new_value,
)
end end
end end
@ -135,15 +171,26 @@ class Admin::SiteSettingsController < Admin::AdminController
notification_level = category_notification_level(id) notification_level = category_notification_level(id)
user_ids = CategoryUser.where(category_id: previous_category_ids - new_category_ids, notification_level: notification_level).distinct.pluck(:user_id) user_ids =
user_ids += User CategoryUser
.real .where(
.joins("CROSS JOIN categories c") category_id: previous_category_ids - new_category_ids,
.joins("LEFT JOIN category_users cu ON users.id = cu.user_id AND c.id = cu.category_id") notification_level: notification_level,
.where(staged: false) )
.where("c.id IN (?) AND cu.notification_level IS NULL", new_category_ids - previous_category_ids) .distinct
.distinct .pluck(:user_id)
.pluck("users.id") user_ids +=
User
.real
.joins("CROSS JOIN categories c")
.joins("LEFT JOIN category_users cu ON users.id = cu.user_id AND c.id = cu.category_id")
.where(staged: false)
.where(
"c.id IN (?) AND cu.notification_level IS NULL",
new_category_ids - previous_category_ids,
)
.distinct
.pluck("users.id")
json[:user_count] = user_ids.uniq.count json[:user_count] = user_ids.uniq.count
elsif id.start_with?("default_tags_") elsif id.start_with?("default_tags_")
@ -152,19 +199,28 @@ class Admin::SiteSettingsController < Admin::AdminController
notification_level = tag_notification_level(id) notification_level = tag_notification_level(id)
user_ids = TagUser.where(tag_id: previous_tag_ids - new_tag_ids, notification_level: notification_level).distinct.pluck(:user_id) user_ids =
user_ids += User TagUser
.real .where(tag_id: previous_tag_ids - new_tag_ids, notification_level: notification_level)
.joins("CROSS JOIN tags t") .distinct
.joins("LEFT JOIN tag_users tu ON users.id = tu.user_id AND t.id = tu.tag_id") .pluck(:user_id)
.where(staged: false) user_ids +=
.where("t.id IN (?) AND tu.notification_level IS NULL", new_tag_ids - previous_tag_ids) User
.distinct .real
.pluck("users.id") .joins("CROSS JOIN tags t")
.joins("LEFT JOIN tag_users tu ON users.id = tu.user_id AND t.id = tu.tag_id")
.where(staged: false)
.where("t.id IN (?) AND tu.notification_level IS NULL", new_tag_ids - previous_tag_ids)
.distinct
.pluck("users.id")
json[:user_count] = user_ids.uniq.count json[:user_count] = user_ids.uniq.count
elsif is_sidebar_default_setting?(id) elsif is_sidebar_default_setting?(id)
json[:user_count] = SidebarSiteSettingsBackfiller.new(id, previous_value: previous_value, new_value: new_value).number_of_users_to_backfill json[:user_count] = SidebarSiteSettingsBackfiller.new(
id,
previous_value: previous_value,
new_value: new_value,
).number_of_users_to_backfill
end end
render json: json render json: json
@ -173,7 +229,7 @@ class Admin::SiteSettingsController < Admin::AdminController
private private
def is_sidebar_default_setting?(setting_name) def is_sidebar_default_setting?(setting_name)
%w{default_sidebar_categories default_sidebar_tags}.include?(setting_name.to_s) %w[default_sidebar_categories default_sidebar_tags].include?(setting_name.to_s)
end end
def user_options def user_options
@ -198,7 +254,7 @@ class Admin::SiteSettingsController < Admin::AdminController
default_include_tl0_in_digests: "include_tl0_in_digests", default_include_tl0_in_digests: "include_tl0_in_digests",
default_text_size: "text_size_key", default_text_size: "text_size_key",
default_title_count_mode: "title_count_mode_key", default_title_count_mode: "title_count_mode_key",
default_hide_profile_and_presence: "hide_profile_and_presence" default_hide_profile_and_presence: "hide_profile_and_presence",
} }
end end

View File

@ -1,22 +1,25 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::SiteTextsController < Admin::AdminController class Admin::SiteTextsController < Admin::AdminController
def self.preferred_keys def self.preferred_keys
['system_messages.usage_tips.text_body_template', %w[
'education.new-topic', system_messages.usage_tips.text_body_template
'education.new-reply', education.new-topic
'login_required.welcome_message'] education.new-reply
login_required.welcome_message
]
end end
def self.restricted_keys def self.restricted_keys
['user_notifications.confirm_old_email.title', %w[
'user_notifications.confirm_old_email.subject_template', user_notifications.confirm_old_email.title
'user_notifications.confirm_old_email.text_body_template'] user_notifications.confirm_old_email.subject_template
user_notifications.confirm_old_email.text_body_template
]
end end
def index def index
overridden = params[:overridden] == 'true' overridden = params[:overridden] == "true"
extras = {} extras = {}
query = params[:q] || "" query = params[:q] || ""
@ -61,7 +64,7 @@ class Admin::SiteTextsController < Admin::AdminController
render_serialized( render_serialized(
results[first..last - 1], results[first..last - 1],
SiteTextSerializer, SiteTextSerializer,
root: 'site_texts', root: "site_texts",
rest_serializer: true, rest_serializer: true,
extras: extras, extras: extras,
overridden_keys: overridden, overridden_keys: overridden,
@ -71,7 +74,7 @@ class Admin::SiteTextsController < Admin::AdminController
def show def show
locale = fetch_locale(params[:locale]) locale = fetch_locale(params[:locale])
site_text = find_site_text(locale) site_text = find_site_text(locale)
render_serialized(site_text, SiteTextSerializer, root: 'site_text', rest_serializer: true) render_serialized(site_text, SiteTextSerializer, root: "site_text", rest_serializer: true)
end end
def update def update
@ -92,14 +95,14 @@ class Admin::SiteTextsController < Admin::AdminController
:bulk_user_title_update, :bulk_user_title_update,
new_title: value, new_title: value,
granted_badge_id: system_badge_id, granted_badge_id: system_badge_id,
action: Jobs::BulkUserTitleUpdate::UPDATE_ACTION action: Jobs::BulkUserTitleUpdate::UPDATE_ACTION,
) )
end end
render_serialized(site_text, SiteTextSerializer, root: 'site_text', rest_serializer: true) render_serialized(site_text, SiteTextSerializer, root: "site_text", rest_serializer: true)
else else
render json: failed_json.merge( render json:
message: translation_override.errors.full_messages.join("\n\n") failed_json.merge(message: translation_override.errors.full_messages.join("\n\n")),
), status: 422 status: 422
end end
end end
@ -118,31 +121,27 @@ class Admin::SiteTextsController < Admin::AdminController
Jobs.enqueue( Jobs.enqueue(
:bulk_user_title_update, :bulk_user_title_update,
granted_badge_id: system_badge_id, granted_badge_id: system_badge_id,
action: Jobs::BulkUserTitleUpdate::RESET_ACTION action: Jobs::BulkUserTitleUpdate::RESET_ACTION,
) )
end end
render_serialized(site_text, SiteTextSerializer, root: 'site_text', rest_serializer: true) render_serialized(site_text, SiteTextSerializer, root: "site_text", rest_serializer: true)
end end
def get_reseed_options def get_reseed_options
render_json_dump( render_json_dump(
categories: SeedData::Categories.with_default_locale.reseed_options, categories: SeedData::Categories.with_default_locale.reseed_options,
topics: SeedData::Topics.with_default_locale.reseed_options topics: SeedData::Topics.with_default_locale.reseed_options,
) )
end end
def reseed def reseed
hijack do hijack do
if params[:category_ids].present? if params[:category_ids].present?
SeedData::Categories.with_default_locale.update( SeedData::Categories.with_default_locale.update(site_setting_names: params[:category_ids])
site_setting_names: params[:category_ids]
)
end end
if params[:topic_ids].present? if params[:topic_ids].present?
SeedData::Topics.with_default_locale.update( SeedData::Topics.with_default_locale.update(site_setting_names: params[:topic_ids])
site_setting_names: params[:topic_ids]
)
end end
render json: success_json render json: success_json
@ -152,8 +151,8 @@ class Admin::SiteTextsController < Admin::AdminController
protected protected
def is_badge_title?(id = "") def is_badge_title?(id = "")
badge_parts = id.split('.') badge_parts = id.split(".")
badge_parts[0] == 'badges' && badge_parts[2] == 'name' badge_parts[0] == "badges" && badge_parts[2] == "name"
end end
def record_for(key:, value: nil, locale:) def record_for(key:, value: nil, locale:)
@ -165,10 +164,15 @@ class Admin::SiteTextsController < Admin::AdminController
def find_site_text(locale) def find_site_text(locale)
if self.class.restricted_keys.include?(params[:id]) if self.class.restricted_keys.include?(params[:id])
raise Discourse::InvalidAccess.new(nil, nil, custom_message: 'email_template_cant_be_modified') raise Discourse::InvalidAccess.new(
nil,
nil,
custom_message: "email_template_cant_be_modified",
)
end end
if I18n.exists?(params[:id], locale) || TranslationOverride.exists?(locale: locale, translation_key: params[:id]) if I18n.exists?(params[:id], locale) ||
TranslationOverride.exists?(locale: locale, translation_key: params[:id])
return record_for(key: params[:id], locale: locale) return record_for(key: params[:id], locale: locale)
end end
@ -182,9 +186,7 @@ class Admin::SiteTextsController < Admin::AdminController
def find_translations(query, overridden, locale) def find_translations(query, overridden, locale)
translations = Hash.new { |hash, key| hash[key] = {} } translations = Hash.new { |hash, key| hash[key] = {} }
search_results = I18n.with_locale(locale) do search_results = I18n.with_locale(locale) { I18n.search(query, only_overridden: overridden) }
I18n.search(query, only_overridden: overridden)
end
search_results.each do |key, value| search_results.each do |key, value|
if PLURALIZED_REGEX.match(key) if PLURALIZED_REGEX.match(key)
@ -205,7 +207,9 @@ class Admin::SiteTextsController < Admin::AdminController
plural_value = plural[1] plural_value = plural[1]
results << record_for( results << record_for(
key: "#{key}.#{plural_key}", value: plural_value, locale: plural.last key: "#{key}.#{plural_key}",
value: plural_value,
locale: plural.last,
) )
end end
else else
@ -218,7 +222,7 @@ class Admin::SiteTextsController < Admin::AdminController
def fix_plural_keys(key, value, locale) def fix_plural_keys(key, value, locale)
value = value.with_indifferent_access value = value.with_indifferent_access
plural_keys = I18n.with_locale(locale) { I18n.t('i18n.plural.keys') } plural_keys = I18n.with_locale(locale) { I18n.t("i18n.plural.keys") }
return value if value.keys.size == plural_keys.size && plural_keys.all? { |k| value.key?(k) } return value if value.keys.size == plural_keys.size && plural_keys.all? { |k| value.key?(k) }
fallback_value = I18n.t(key, locale: :en, default: {}) fallback_value = I18n.t(key, locale: :en, default: {})

View File

@ -1,9 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::StaffActionLogsController < Admin::StaffController class Admin::StaffActionLogsController < Admin::StaffController
def index def index
filters = params.slice(*UserHistory.staff_filters + [:page, :limit]) filters = params.slice(*UserHistory.staff_filters + %i[page limit])
page = (params[:page] || 0).to_i page = (params[:page] || 0).to_i
page_size = (params[:limit] || 200).to_i.clamp(1, 200) page_size = (params[:limit] || 200).to_i.clamp(1, 200)
@ -20,8 +19,8 @@ class Admin::StaffActionLogsController < Admin::StaffController
total_rows_staff_action_logs: count, total_rows_staff_action_logs: count,
load_more_staff_action_logs: admin_staff_action_logs_path(load_more_params), load_more_staff_action_logs: admin_staff_action_logs_path(load_more_params),
extras: { extras: {
user_history_actions: staff_available_actions user_history_actions: staff_available_actions,
} },
) )
end end
@ -37,16 +36,10 @@ class Admin::StaffActionLogsController < Admin::StaffController
output = +"<h2>#{CGI.escapeHTML(cur&.dig("name").to_s)}</h2><p></p>" output = +"<h2>#{CGI.escapeHTML(cur&.dig("name").to_s)}</h2><p></p>"
diff_fields["name"] = { diff_fields["name"] = { prev: prev&.dig("name").to_s, cur: cur&.dig("name").to_s }
prev: prev&.dig("name").to_s,
cur: cur&.dig("name").to_s,
}
["default", "user_selectable"].each do |f| %w[default user_selectable].each do |f|
diff_fields[f] = { diff_fields[f] = { prev: (!!prev&.dig(f)).to_s, cur: (!!cur&.dig(f)).to_s }
prev: (!!prev&.dig(f)).to_s,
cur: (!!cur&.dig(f)).to_s
}
end end
diff_fields["color scheme"] = { diff_fields["color scheme"] = {
@ -54,10 +47,7 @@ class Admin::StaffActionLogsController < Admin::StaffController
cur: cur&.dig("color_scheme", "name").to_s, cur: cur&.dig("color_scheme", "name").to_s,
} }
diff_fields["included themes"] = { diff_fields["included themes"] = { prev: child_themes(prev), cur: child_themes(cur) }
prev: child_themes(prev),
cur: child_themes(cur)
}
load_diff(diff_fields, :cur, cur) load_diff(diff_fields, :cur, cur)
load_diff(diff_fields, :prev, prev) load_diff(diff_fields, :prev, prev)
@ -94,10 +84,7 @@ class Admin::StaffActionLogsController < Admin::StaffController
def staff_available_actions def staff_available_actions
UserHistory.staff_actions.sort.map do |name| UserHistory.staff_actions.sort.map do |name|
{ { id: name, action_id: UserHistory.actions[name] || UserHistory.actions[:custom_staff] }
id: name,
action_id: UserHistory.actions[name] || UserHistory.actions[:custom_staff],
}
end end
end end
end end

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'base64' require "base64"
class Admin::ThemesController < Admin::AdminController class Admin::ThemesController < Admin::AdminController
skip_before_action :check_xhr, only: %i[show preview export]
skip_before_action :check_xhr, only: [:show, :preview, :export]
before_action :ensure_admin before_action :ensure_admin
def preview def preview
@ -15,7 +14,6 @@ class Admin::ThemesController < Admin::AdminController
end end
def upload_asset def upload_asset
ban_in_allowlist_mode! ban_in_allowlist_mode!
path = params[:file].path path = params[:file].path
@ -34,32 +32,30 @@ class Admin::ThemesController < Admin::AdminController
end end
def generate_key_pair def generate_key_pair
require 'sshkey' require "sshkey"
k = SSHKey.generate k = SSHKey.generate
Discourse.redis.setex("ssh_key_#{k.ssh_public_key}", 1.hour, k.private_key) Discourse.redis.setex("ssh_key_#{k.ssh_public_key}", 1.hour, k.private_key)
render json: { public_key: k.ssh_public_key } render json: { public_key: k.ssh_public_key }
end end
THEME_CONTENT_TYPES ||= %w{ THEME_CONTENT_TYPES ||= %w[
application/gzip application/gzip
application/x-gzip application/x-gzip
application/x-zip-compressed application/x-zip-compressed
application/zip application/zip
} ]
def import def import
@theme = nil @theme = nil
if params[:theme] && params[:theme].content_type == "application/json" if params[:theme] && params[:theme].content_type == "application/json"
ban_in_allowlist_mode! ban_in_allowlist_mode!
# .dcstyle.json import. Deprecated, but still available to allow conversion # .dcstyle.json import. Deprecated, but still available to allow conversion
json = JSON::parse(params[:theme].read) json = JSON.parse(params[:theme].read)
theme = json['theme'] theme = json["theme"]
@theme = Theme.new(name: theme["name"], user_id: theme_user.id, auto_update: false) @theme = Theme.new(name: theme["name"], user_id: theme_user.id, auto_update: false)
theme["theme_fields"]&.each do |field| theme["theme_fields"]&.each do |field|
if field["raw_upload"] if field["raw_upload"]
begin begin
tmp = Tempfile.new tmp = Tempfile.new
@ -79,7 +75,7 @@ class Admin::ThemesController < Admin::AdminController
name: field["name"], name: field["name"],
value: field["value"], value: field["value"],
type_id: field["type_id"], type_id: field["type_id"],
upload_id: field["upload_id"] upload_id: field["upload_id"],
) )
end end
@ -93,17 +89,22 @@ class Admin::ThemesController < Admin::AdminController
begin begin
guardian.ensure_allowed_theme_repo_import!(remote.strip) guardian.ensure_allowed_theme_repo_import!(remote.strip)
rescue Discourse::InvalidAccess rescue Discourse::InvalidAccess
render_json_error I18n.t("themes.import_error.not_allowed_theme", { repo: remote.strip }), status: :forbidden render_json_error I18n.t("themes.import_error.not_allowed_theme", { repo: remote.strip }),
status: :forbidden
return return
end end
hijack do hijack do
begin begin
branch = params[:branch] ? params[:branch] : nil branch = params[:branch] ? params[:branch] : nil
private_key = params[:public_key] ? Discourse.redis.get("ssh_key_#{params[:public_key]}") : nil private_key =
return render_json_error I18n.t("themes.import_error.ssh_key_gone") if params[:public_key].present? && private_key.blank? params[:public_key] ? Discourse.redis.get("ssh_key_#{params[:public_key]}") : nil
if params[:public_key].present? && private_key.blank?
return render_json_error I18n.t("themes.import_error.ssh_key_gone")
end
@theme = RemoteTheme.import_theme(remote, theme_user, private_key: private_key, branch: branch) @theme =
RemoteTheme.import_theme(remote, theme_user, private_key: private_key, branch: branch)
render json: @theme, status: :created render json: @theme, status: :created
rescue RemoteTheme::ImportError => e rescue RemoteTheme::ImportError => e
if params[:force] if params[:force]
@ -125,8 +126,8 @@ class Admin::ThemesController < Admin::AdminController
end end
end end
end end
elsif params[:bundle] || (params[:theme] && THEME_CONTENT_TYPES.include?(params[:theme].content_type)) elsif params[:bundle] ||
(params[:theme] && THEME_CONTENT_TYPES.include?(params[:theme].content_type))
ban_in_allowlist_mode! ban_in_allowlist_mode!
# params[:bundle] used by theme CLI. params[:theme] used by admin UI # params[:bundle] used by theme CLI. params[:theme] used by admin UI
@ -135,21 +136,23 @@ class Admin::ThemesController < Admin::AdminController
update_components = params[:components] update_components = params[:components]
match_theme_by_name = !!params[:bundle] && !params.key?(:theme_id) # Old theme CLI behavior, match by name. Remove Jan 2020 match_theme_by_name = !!params[:bundle] && !params.key?(:theme_id) # Old theme CLI behavior, match by name. Remove Jan 2020
begin begin
@theme = RemoteTheme.update_zipped_theme( @theme =
bundle.path, RemoteTheme.update_zipped_theme(
bundle.original_filename, bundle.path,
match_theme: match_theme_by_name, bundle.original_filename,
user: theme_user, match_theme: match_theme_by_name,
theme_id: theme_id, user: theme_user,
update_components: update_components theme_id: theme_id,
) update_components: update_components,
)
log_theme_change(nil, @theme) log_theme_change(nil, @theme)
render json: @theme, status: :created render json: @theme, status: :created
rescue RemoteTheme::ImportError => e rescue RemoteTheme::ImportError => e
render_json_error e.message render_json_error e.message
end end
else else
render_json_error I18n.t("themes.import_error.unknown_file_type"), status: :unprocessable_entity render_json_error I18n.t("themes.import_error.unknown_file_type"),
status: :unprocessable_entity
end end
end end
@ -160,24 +163,25 @@ class Admin::ThemesController < Admin::AdminController
payload = { payload = {
themes: ActiveModel::ArraySerializer.new(@themes, each_serializer: ThemeSerializer), themes: ActiveModel::ArraySerializer.new(@themes, each_serializer: ThemeSerializer),
extras: { extras: {
color_schemes: ActiveModel::ArraySerializer.new(@color_schemes, each_serializer: ColorSchemeSerializer) color_schemes:
} ActiveModel::ArraySerializer.new(@color_schemes, each_serializer: ColorSchemeSerializer),
},
} }
respond_to do |format| respond_to { |format| format.json { render json: payload } }
format.json { render json: payload }
end
end end
def create def create
ban_in_allowlist_mode! ban_in_allowlist_mode!
@theme = Theme.new(name: theme_params[:name], @theme =
user_id: theme_user.id, Theme.new(
user_selectable: theme_params[:user_selectable] || false, name: theme_params[:name],
color_scheme_id: theme_params[:color_scheme_id], user_id: theme_user.id,
component: [true, "true"].include?(theme_params[:component])) user_selectable: theme_params[:user_selectable] || false,
color_scheme_id: theme_params[:color_scheme_id],
component: [true, "true"].include?(theme_params[:component]),
)
set_fields set_fields
respond_to do |format| respond_to do |format|
@ -199,10 +203,8 @@ class Admin::ThemesController < Admin::AdminController
disables_component = [false, "false"].include?(theme_params[:enabled]) disables_component = [false, "false"].include?(theme_params[:enabled])
enables_component = [true, "true"].include?(theme_params[:enabled]) enables_component = [true, "true"].include?(theme_params[:enabled])
[:name, :color_scheme_id, :user_selectable, :enabled, :auto_update].each do |field| %i[name color_scheme_id user_selectable enabled auto_update].each do |field|
if theme_params.key?(field) @theme.public_send("#{field}=", theme_params[field]) if theme_params.key?(field)
@theme.public_send("#{field}=", theme_params[field])
end
end end
if theme_params.key?(:child_theme_ids) if theme_params.key?(:child_theme_ids)
@ -218,13 +220,9 @@ class Admin::ThemesController < Admin::AdminController
update_translations update_translations
handle_switch handle_switch
if params[:theme][:remote_check] @theme.remote_theme.update_remote_version if params[:theme][:remote_check]
@theme.remote_theme.update_remote_version
end
if params[:theme][:remote_update] @theme.remote_theme.update_from_remote if params[:theme][:remote_update]
@theme.remote_theme.update_from_remote
end
respond_to do |format| respond_to do |format|
if @theme.save if @theme.save
@ -245,7 +243,7 @@ class Admin::ThemesController < Admin::AdminController
error = I18n.t("themes.bad_color_scheme") if @theme.errors[:color_scheme].present? error = I18n.t("themes.bad_color_scheme") if @theme.errors[:color_scheme].present?
error ||= I18n.t("themes.other_error") error ||= I18n.t("themes.other_error")
render json: { errors: [ error ] }, status: :unprocessable_entity render json: { errors: [error] }, status: :unprocessable_entity
end end
end end
end end
@ -260,9 +258,7 @@ class Admin::ThemesController < Admin::AdminController
StaffActionLogger.new(current_user).log_theme_destroy(@theme) StaffActionLogger.new(current_user).log_theme_destroy(@theme)
@theme.destroy @theme.destroy
respond_to do |format| respond_to { |format| format.json { head :no_content } }
format.json { head :no_content }
end
end end
def show def show
@ -279,10 +275,10 @@ class Admin::ThemesController < Admin::AdminController
exporter = ThemeStore::ZipExporter.new(@theme) exporter = ThemeStore::ZipExporter.new(@theme)
file_path = exporter.package_filename file_path = exporter.package_filename
headers['Content-Length'] = File.size(file_path).to_s headers["Content-Length"] = File.size(file_path).to_s
send_data File.read(file_path), send_data File.read(file_path),
filename: File.basename(file_path), filename: File.basename(file_path),
content_type: "application/zip" content_type: "application/zip"
ensure ensure
exporter.cleanup! exporter.cleanup!
end end
@ -330,9 +326,7 @@ class Admin::ThemesController < Admin::AdminController
end end
end end
Theme.where(id: expected).each do |theme| Theme.where(id: expected).each { |theme| @theme.add_relative_theme!(kind, theme) }
@theme.add_relative_theme!(kind, theme)
end
end end
def update_default_theme def update_default_theme
@ -361,11 +355,13 @@ class Admin::ThemesController < Admin::AdminController
:component, :component,
:enabled, :enabled,
:auto_update, :auto_update,
settings: {}, settings: {
translations: {}, },
theme_fields: [:name, :target, :value, :upload_id, :type_id], translations: {
},
theme_fields: %i[name target value upload_id type_id],
child_theme_ids: [], child_theme_ids: [],
parent_theme_ids: [] parent_theme_ids: [],
) )
end end
end end
@ -382,7 +378,7 @@ class Admin::ThemesController < Admin::AdminController
name: field[:name], name: field[:name],
value: field[:value], value: field[:value],
type_id: field[:type_id], type_id: field[:type_id],
upload_id: field[:upload_id] upload_id: field[:upload_id],
) )
end end
end end
@ -408,7 +404,12 @@ class Admin::ThemesController < Admin::AdminController
end end
def log_theme_setting_change(setting_name, previous_value, new_value) def log_theme_setting_change(setting_name, previous_value, new_value)
StaffActionLogger.new(current_user).log_theme_setting_change(setting_name, previous_value, new_value, @theme) StaffActionLogger.new(current_user).log_theme_setting_change(
setting_name,
previous_value,
new_value,
@theme,
)
end end
def log_theme_component_disabled def log_theme_component_disabled
@ -422,10 +423,14 @@ class Admin::ThemesController < Admin::AdminController
def handle_switch def handle_switch
param = theme_params[:component] param = theme_params[:component]
if param.to_s == "false" && @theme.component? if param.to_s == "false" && @theme.component?
raise Discourse::InvalidParameters.new(:component) if @theme.id == SiteSetting.default_theme_id if @theme.id == SiteSetting.default_theme_id
raise Discourse::InvalidParameters.new(:component)
end
@theme.switch_to_theme! @theme.switch_to_theme!
elsif param.to_s == "true" && !@theme.component? elsif param.to_s == "true" && !@theme.component?
raise Discourse::InvalidParameters.new(:component) if @theme.id == SiteSetting.default_theme_id if @theme.id == SiteSetting.default_theme_id
raise Discourse::InvalidParameters.new(:component)
end
@theme.switch_to_component! @theme.switch_to_component!
end end
end end

View File

@ -1,9 +1,18 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::UserFieldsController < Admin::AdminController class Admin::UserFieldsController < Admin::AdminController
def self.columns def self.columns
%i(name field_type editable description required show_on_profile show_on_user_card position searchable) %i[
name
field_type
editable
description
required
show_on_profile
show_on_user_card
position
searchable
]
end end
def create def create
@ -13,14 +22,12 @@ class Admin::UserFieldsController < Admin::AdminController
field.required = params[:user_field][:required] == "true" field.required = params[:user_field][:required] == "true"
update_options(field) update_options(field)
json_result(field, serializer: UserFieldSerializer) do json_result(field, serializer: UserFieldSerializer) { field.save }
field.save
end
end end
def index def index
user_fields = UserField.all.includes(:user_field_options).order(:position) user_fields = UserField.all.includes(:user_field_options).order(:position)
render_serialized(user_fields, UserFieldSerializer, root: 'user_fields') render_serialized(user_fields, UserFieldSerializer, root: "user_fields")
end end
def update def update
@ -28,9 +35,7 @@ class Admin::UserFieldsController < Admin::AdminController
field = UserField.where(id: params.require(:id)).first field = UserField.where(id: params.require(:id)).first
Admin::UserFieldsController.columns.each do |col| Admin::UserFieldsController.columns.each do |col|
unless field_params[col].nil? field.public_send("#{col}=", field_params[col]) unless field_params[col].nil?
field.public_send("#{col}=", field_params[col])
end
end end
update_options(field) update_options(field)
@ -38,7 +43,7 @@ class Admin::UserFieldsController < Admin::AdminController
if !field.show_on_profile && !field.show_on_user_card if !field.show_on_profile && !field.show_on_user_card
DirectoryColumn.where(user_field_id: field.id).destroy_all DirectoryColumn.where(user_field_id: field.id).destroy_all
end end
render_serialized(field, UserFieldSerializer, root: 'user_field') render_serialized(field, UserFieldSerializer, root: "user_field")
else else
render_json_error(field) render_json_error(field)
end end

View File

@ -3,28 +3,31 @@
class Admin::UsersController < Admin::StaffController class Admin::UsersController < Admin::StaffController
MAX_SIMILAR_USERS = 10 MAX_SIMILAR_USERS = 10
before_action :fetch_user, only: [:suspend, before_action :fetch_user,
:unsuspend, only: %i[
:log_out, suspend
:revoke_admin, unsuspend
:revoke_moderation, log_out
:grant_moderation, revoke_admin
:approve, revoke_moderation
:activate, grant_moderation
:deactivate, approve
:silence, activate
:unsilence, deactivate
:trust_level, silence
:trust_level_lock, unsilence
:add_group, trust_level
:remove_group, trust_level_lock
:primary_group, add_group
:anonymize, remove_group
:merge, primary_group
:reset_bounce_score, anonymize
:disable_second_factor, merge
:delete_posts_batch, reset_bounce_score
:sso_record] disable_second_factor
delete_posts_batch
sso_record
]
def index def index
users = ::AdminUserIndexQuery.new(params).find_users users = ::AdminUserIndexQuery.new(params).find_users
@ -42,9 +45,7 @@ class Admin::UsersController < Admin::StaffController
@user = User.find_by(id: params[:id]) @user = User.find_by(id: params[:id])
raise Discourse::NotFound unless @user raise Discourse::NotFound unless @user
similar_users = User.real similar_users = User.real.where.not(id: @user.id).where(ip_address: @user.ip_address)
.where.not(id: @user.id)
.where(ip_address: @user.ip_address)
render_serialized( render_serialized(
@user, @user,
@ -64,7 +65,6 @@ class Admin::UsersController < Admin::StaffController
# DELETE action to delete penalty history for a user # DELETE action to delete penalty history for a user
def penalty_history def penalty_history
# We don't delete any history, we merely remove the action type # We don't delete any history, we merely remove the action type
# with a removed type. It can still be viewed in the logs but # with a removed type. It can still be viewed in the logs but
# will not affect TL3 promotions. # will not affect TL3 promotions.
@ -87,16 +87,19 @@ class Admin::UsersController < Admin::StaffController
DB.exec( DB.exec(
sql, sql,
UserHistory.actions.slice( UserHistory
:silence_user, .actions
:suspend_user, .slice(
:unsilence_user, :silence_user,
:unsuspend_user, :suspend_user,
:removed_silence_user, :unsilence_user,
:removed_unsilence_user, :unsuspend_user,
:removed_suspend_user, :removed_silence_user,
:removed_unsuspend_user :removed_unsilence_user,
).merge(user_id: params[:user_id].to_i) :removed_suspend_user,
:removed_unsuspend_user,
)
.merge(user_id: params[:user_id].to_i),
) )
render json: success_json render json: success_json
@ -107,14 +110,21 @@ class Admin::UsersController < Admin::StaffController
if @user.suspended? if @user.suspended?
suspend_record = @user.suspend_record suspend_record = @user.suspend_record
message = I18n.t("user.already_suspended", message =
staff: suspend_record.acting_user.username, I18n.t(
time_ago: FreedomPatches::Rails4.time_ago_in_words(suspend_record.created_at, true, scope: :'datetime.distance_in_words_verbose') "user.already_suspended",
) staff: suspend_record.acting_user.username,
time_ago:
FreedomPatches::Rails4.time_ago_in_words(
suspend_record.created_at,
true,
scope: :"datetime.distance_in_words_verbose",
),
)
return render json: failed_json.merge(message: message), status: 409 return render json: failed_json.merge(message: message), status: 409
end end
params.require([:suspend_until, :reason]) params.require(%i[suspend_until reason])
all_users = [@user] all_users = [@user]
if Array === params[:other_user_ids] if Array === params[:other_user_ids]
@ -133,12 +143,13 @@ class Admin::UsersController < Admin::StaffController
User.transaction do User.transaction do
user.save! user.save!
user_history = StaffActionLogger.new(current_user).log_user_suspend( user_history =
user, StaffActionLogger.new(current_user).log_user_suspend(
params[:reason], user,
message: message, params[:reason],
post_id: params[:post_id] message: message,
) post_id: params[:post_id],
)
end end
user.logged_out user.logged_out
@ -147,7 +158,7 @@ class Admin::UsersController < Admin::StaffController
:critical_user_email, :critical_user_email,
type: "account_suspended", type: "account_suspended",
user_id: user.id, user_id: user.id,
user_history_id: user_history.id user_history_id: user_history.id,
) )
end end
@ -159,7 +170,7 @@ class Admin::UsersController < Admin::StaffController
user_history: user_history, user_history: user_history,
post_id: params[:post_id], post_id: params[:post_id],
suspended_till: params[:suspend_until], suspended_till: params[:suspend_until],
suspended_at: DateTime.now suspended_at: DateTime.now,
) )
end end
@ -171,8 +182,8 @@ class Admin::UsersController < Admin::StaffController
full_suspend_reason: user_history.try(:details), full_suspend_reason: user_history.try(:details),
suspended_till: @user.suspended_till, suspended_till: @user.suspended_till,
suspended_at: @user.suspended_at, suspended_at: @user.suspended_at,
suspended_by: BasicUserSerializer.new(current_user, root: false).as_json suspended_by: BasicUserSerializer.new(current_user, root: false).as_json,
} },
) )
end end
@ -185,12 +196,7 @@ class Admin::UsersController < Admin::StaffController
DiscourseEvent.trigger(:user_unsuspended, user: @user) DiscourseEvent.trigger(:user_unsuspended, user: @user)
render_json_dump( render_json_dump(suspension: { suspended_till: nil, suspended_at: nil })
suspension: {
suspended_till: nil,
suspended_at: nil
}
)
end end
def log_out def log_out
@ -199,7 +205,7 @@ class Admin::UsersController < Admin::StaffController
@user.logged_out @user.logged_out
render json: success_json render json: success_json
else else
render json: { error: I18n.t('admin_js.admin.users.id_not_found') }, status: 404 render json: { error: I18n.t("admin_js.admin.users.id_not_found") }, status: 404
end end
end end
@ -237,7 +243,7 @@ class Admin::UsersController < Admin::StaffController
group = Group.find(params[:group_id].to_i) group = Group.find(params[:group_id].to_i)
raise Discourse::NotFound unless group raise Discourse::NotFound unless group
return render_json_error(I18n.t('groups.errors.can_not_modify_automatic')) if group.automatic return render_json_error(I18n.t("groups.errors.can_not_modify_automatic")) if group.automatic
guardian.ensure_can_edit!(group) guardian.ensure_can_edit!(group)
group.add(@user) group.add(@user)
@ -250,7 +256,7 @@ class Admin::UsersController < Admin::StaffController
group = Group.find(params[:group_id].to_i) group = Group.find(params[:group_id].to_i)
raise Discourse::NotFound unless group raise Discourse::NotFound unless group
return render_json_error(I18n.t('groups.errors.can_not_modify_automatic')) if group.automatic return render_json_error(I18n.t("groups.errors.can_not_modify_automatic")) if group.automatic
guardian.ensure_can_edit!(group) guardian.ensure_can_edit!(group)
if group.remove(@user) if group.remove(@user)
@ -266,9 +272,7 @@ class Admin::UsersController < Admin::StaffController
if group = Group.find(primary_group_id) if group = Group.find(primary_group_id)
guardian.ensure_can_change_primary_group!(@user, group) guardian.ensure_can_change_primary_group!(@user, group)
if group.user_ids.include?(@user.id) @user.primary_group_id = primary_group_id if group.user_ids.include?(@user.id)
@user.primary_group_id = primary_group_id
end
end end
else else
@user.primary_group_id = nil @user.primary_group_id = nil
@ -304,9 +308,7 @@ class Admin::UsersController < Admin::StaffController
guardian.ensure_can_change_trust_level!(@user) guardian.ensure_can_change_trust_level!(@user)
new_lock = params[:locked].to_s new_lock = params[:locked].to_s
unless new_lock =~ /true|false/ return render_json_error I18n.t("errors.invalid_boolean") unless new_lock =~ /true|false/
return render_json_error I18n.t('errors.invalid_boolean')
end
@user.manual_locked_trust_level = (new_lock == "true") ? @user.trust_level : nil @user.manual_locked_trust_level = (new_lock == "true") ? @user.trust_level : nil
@user.save @user.save
@ -320,31 +322,38 @@ class Admin::UsersController < Admin::StaffController
def approve def approve
guardian.ensure_can_approve!(@user) guardian.ensure_can_approve!(@user)
reviewable = ReviewableUser.find_by(target: @user) || reviewable =
Jobs::CreateUserReviewable.new.execute(user_id: @user.id).reviewable ReviewableUser.find_by(target: @user) ||
Jobs::CreateUserReviewable.new.execute(user_id: @user.id).reviewable
reviewable.perform(current_user, :approve_user) reviewable.perform(current_user, :approve_user)
render body: nil render body: nil
end end
def approve_bulk def approve_bulk
Reviewable.bulk_perform_targets(current_user, :approve_user, 'ReviewableUser', params[:users]) Reviewable.bulk_perform_targets(current_user, :approve_user, "ReviewableUser", params[:users])
render body: nil render body: nil
end end
def activate def activate
guardian.ensure_can_activate!(@user) guardian.ensure_can_activate!(@user)
# ensure there is an active email token # ensure there is an active email token
@user.email_tokens.create!(email: @user.email, scope: EmailToken.scopes[:signup]) if !@user.email_tokens.active.exists? if !@user.email_tokens.active.exists?
@user.email_tokens.create!(email: @user.email, scope: EmailToken.scopes[:signup])
end
@user.activate @user.activate
StaffActionLogger.new(current_user).log_user_activate(@user, I18n.t('user.activated_by_staff')) StaffActionLogger.new(current_user).log_user_activate(@user, I18n.t("user.activated_by_staff"))
render json: success_json render json: success_json
end end
def deactivate def deactivate
guardian.ensure_can_deactivate!(@user) guardian.ensure_can_deactivate!(@user)
@user.deactivate(current_user) @user.deactivate(current_user)
StaffActionLogger.new(current_user).log_user_deactivate(@user, I18n.t('user.deactivated_by_staff'), params.slice(:context)) StaffActionLogger.new(current_user).log_user_deactivate(
@user,
I18n.t("user.deactivated_by_staff"),
params.slice(:context),
)
refresh_browser @user refresh_browser @user
render json: success_json render json: success_json
end end
@ -354,10 +363,17 @@ class Admin::UsersController < Admin::StaffController
if @user.silenced? if @user.silenced?
silenced_record = @user.silenced_record silenced_record = @user.silenced_record
message = I18n.t("user.already_silenced", message =
staff: silenced_record.acting_user.username, I18n.t(
time_ago: FreedomPatches::Rails4.time_ago_in_words(silenced_record.created_at, true, scope: :'datetime.distance_in_words_verbose') "user.already_silenced",
) staff: silenced_record.acting_user.username,
time_ago:
FreedomPatches::Rails4.time_ago_in_words(
silenced_record.created_at,
true,
scope: :"datetime.distance_in_words_verbose",
),
)
return render json: failed_json.merge(message: message), status: 409 return render json: failed_json.merge(message: message), status: 409
end end
@ -370,15 +386,16 @@ class Admin::UsersController < Admin::StaffController
user_history = nil user_history = nil
all_users.each do |user| all_users.each do |user|
silencer = UserSilencer.new( silencer =
user, UserSilencer.new(
current_user, user,
silenced_till: params[:silenced_till], current_user,
reason: params[:reason], silenced_till: params[:silenced_till],
message_body: params[:message], reason: params[:reason],
keep_posts: true, message_body: params[:message],
post_id: params[:post_id] keep_posts: true,
) post_id: params[:post_id],
)
if silencer.silence if silencer.silence
user_history = silencer.user_history user_history = silencer.user_history
@ -386,7 +403,7 @@ class Admin::UsersController < Admin::StaffController
:critical_user_email, :critical_user_email,
type: "account_silenced", type: "account_silenced",
user_id: user.id, user_id: user.id,
user_history_id: user_history.id user_history_id: user_history.id,
) )
end end
end end
@ -399,8 +416,8 @@ class Admin::UsersController < Admin::StaffController
silence_reason: user_history.try(:details), silence_reason: user_history.try(:details),
silenced_till: @user.silenced_till, silenced_till: @user.silenced_till,
silenced_at: @user.silenced_at, silenced_at: @user.silenced_at,
silenced_by: BasicUserSerializer.new(current_user, root: false).as_json silenced_by: BasicUserSerializer.new(current_user, root: false).as_json,
} },
) )
end end
@ -413,8 +430,8 @@ class Admin::UsersController < Admin::StaffController
silenced: false, silenced: false,
silence_reason: nil, silence_reason: nil,
silenced_till: nil, silenced_till: nil,
silenced_at: nil silenced_at: nil,
} },
) )
end end
@ -428,11 +445,7 @@ class Admin::UsersController < Admin::StaffController
user_security_key.destroy_all user_security_key.destroy_all
StaffActionLogger.new(current_user).log_disable_second_factor_auth(@user) StaffActionLogger.new(current_user).log_disable_second_factor_auth(@user)
Jobs.enqueue( Jobs.enqueue(:critical_user_email, type: "account_second_factor_disabled", user_id: @user.id)
:critical_user_email,
type: "account_second_factor_disabled",
user_id: @user.id
)
render json: success_json render json: success_json
end end
@ -442,7 +455,7 @@ class Admin::UsersController < Admin::StaffController
guardian.ensure_can_delete_user!(user) guardian.ensure_can_delete_user!(user)
options = params.slice(:context, :delete_as_spammer) options = params.slice(:context, :delete_as_spammer)
[:delete_posts, :block_email, :block_urls, :block_ip].each do |param_name| %i[delete_posts block_email block_urls block_ip].each do |param_name|
options[param_name] = ActiveModel::Type::Boolean.new.cast(params[param_name]) options[param_name] = ActiveModel::Type::Boolean.new.cast(params[param_name])
end end
options[:prepare_for_destroy] = true options[:prepare_for_destroy] = true
@ -453,15 +466,21 @@ class Admin::UsersController < Admin::StaffController
render json: { deleted: true } render json: { deleted: true }
else else
render json: { render json: {
deleted: false, deleted: false,
user: AdminDetailedUserSerializer.new(user, root: false).as_json user: AdminDetailedUserSerializer.new(user, root: false).as_json,
} }
end end
rescue UserDestroyer::PostsExistError rescue UserDestroyer::PostsExistError
render json: { render json: {
deleted: false, deleted: false,
message: I18n.t("user.cannot_delete_has_posts", username: user.username, count: user.posts.joins(:topic).count), message:
}, status: 403 I18n.t(
"user.cannot_delete_has_posts",
username: user.username,
count: user.posts.joins(:topic).count,
),
},
status: 403
end end
end end
end end
@ -482,9 +501,16 @@ class Admin::UsersController < Admin::StaffController
return render body: nil, status: 404 unless SiteSetting.enable_discourse_connect return render body: nil, status: 404 unless SiteSetting.enable_discourse_connect
begin begin
sso = DiscourseConnect.parse("sso=#{params[:sso]}&sig=#{params[:sig]}", secure_session: secure_session) sso =
DiscourseConnect.parse(
"sso=#{params[:sso]}&sig=#{params[:sig]}",
secure_session: secure_session,
)
rescue DiscourseConnect::ParseError rescue DiscourseConnect::ParseError
return render json: failed_json.merge(message: I18n.t("discourse_connect.login_error")), status: 422 return(
render json: failed_json.merge(message: I18n.t("discourse_connect.login_error")),
status: 422
)
end end
begin begin
@ -494,7 +520,8 @@ class Admin::UsersController < Admin::StaffController
rescue ActiveRecord::RecordInvalid => ex rescue ActiveRecord::RecordInvalid => ex
render json: failed_json.merge(message: ex.message), status: 403 render json: failed_json.merge(message: ex.message), status: 403
rescue DiscourseConnect::BlankExternalId => ex rescue DiscourseConnect::BlankExternalId => ex
render json: failed_json.merge(message: I18n.t('discourse_connect.blank_id_error')), status: 422 render json: failed_json.merge(message: I18n.t("discourse_connect.blank_id_error")),
status: 422
end end
end end
@ -510,12 +537,13 @@ class Admin::UsersController < Admin::StaffController
block_urls: true, block_urls: true,
block_ip: true, block_ip: true,
delete_as_spammer: true, delete_as_spammer: true,
context: I18n.t("user.destroy_reasons.same_ip_address", ip_address: params[:ip]) context: I18n.t("user.destroy_reasons.same_ip_address", ip_address: params[:ip]),
} }
AdminUserIndexQuery.new(params).find_users(50).each do |user| AdminUserIndexQuery
user_destroyer.destroy(user, options) .new(params)
end .find_users(50)
.each { |user| user_destroyer.destroy(user, options) }
render json: success_json render json: success_json
end end
@ -536,7 +564,8 @@ class Admin::UsersController < Admin::StaffController
if user = UserAnonymizer.new(@user, current_user, opts).make_anonymous if user = UserAnonymizer.new(@user, current_user, opts).make_anonymous
render json: success_json.merge(username: user.username) render json: success_json.merge(username: user.username)
else else
render json: failed_json.merge(user: AdminDetailedUserSerializer.new(user, root: false).as_json) render json:
failed_json.merge(user: AdminDetailedUserSerializer.new(user, root: false).as_json)
end end
end end
@ -547,7 +576,12 @@ class Admin::UsersController < Admin::StaffController
guardian.ensure_can_merge_users!(@user, target_user) guardian.ensure_can_merge_users!(@user, target_user)
Jobs.enqueue(:merge_user, user_id: @user.id, target_user_id: target_user.id, current_user_id: current_user.id) Jobs.enqueue(
:merge_user,
user_id: @user.id,
target_user_id: target_user.id,
current_user_id: current_user.id,
)
render json: success_json render json: success_json
end end
@ -566,24 +600,25 @@ class Admin::UsersController < Admin::StaffController
private private
def perform_post_action def perform_post_action
return unless params[:post_id].present? && return unless params[:post_id].present? && params[:post_action].present?
params[:post_action].present?
if post = Post.where(id: params[:post_id]).first if post = Post.where(id: params[:post_id]).first
case params[:post_action] case params[:post_action]
when 'delete' when "delete"
PostDestroyer.new(current_user, post).destroy if guardian.can_delete_post_or_topic?(post) PostDestroyer.new(current_user, post).destroy if guardian.can_delete_post_or_topic?(post)
when "delete_replies" when "delete_replies"
PostDestroyer.delete_with_replies(current_user, post) if guardian.can_delete_post_or_topic?(post) if guardian.can_delete_post_or_topic?(post)
when 'edit' PostDestroyer.delete_with_replies(current_user, post)
end
when "edit"
revisor = PostRevisor.new(post) revisor = PostRevisor.new(post)
# Take what the moderator edited in as gospel # Take what the moderator edited in as gospel
revisor.revise!( revisor.revise!(
current_user, current_user,
{ raw: params[:post_edit] }, { raw: params[:post_edit] },
skip_validations: true, skip_validations: true,
skip_revision: true skip_revision: true,
) )
end end
end end
@ -597,5 +632,4 @@ class Admin::UsersController < Admin::StaffController
def refresh_browser(user) def refresh_browser(user)
MessageBus.publish "/file-change", ["refresh"], user_ids: [user.id] MessageBus.publish "/file-change", ["refresh"], user_ids: [user.id]
end end
end end

View File

@ -1,13 +1,14 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'csv' require "csv"
class Admin::WatchedWordsController < Admin::StaffController class Admin::WatchedWordsController < Admin::StaffController
skip_before_action :check_xhr, only: [:download] skip_before_action :check_xhr, only: [:download]
def index def index
watched_words = WatchedWord.by_action watched_words = WatchedWord.by_action
watched_words = watched_words.where.not(action: WatchedWord.actions[:tag]) if !SiteSetting.tagging_enabled watched_words =
watched_words.where.not(action: WatchedWord.actions[:tag]) if !SiteSetting.tagging_enabled
render_json_dump WatchedWordListSerializer.new(watched_words, scope: guardian, root: false) render_json_dump WatchedWordListSerializer.new(watched_words, scope: guardian, root: false)
end end
@ -38,19 +39,20 @@ class Admin::WatchedWordsController < Admin::StaffController
begin begin
CSV.foreach(file.tempfile, encoding: "bom|utf-8") do |row| CSV.foreach(file.tempfile, encoding: "bom|utf-8") do |row|
if row[0].present? && (!has_replacement || row[1].present?) if row[0].present? && (!has_replacement || row[1].present?)
watched_word = WatchedWord.create_or_update_word( watched_word =
word: row[0], WatchedWord.create_or_update_word(
replacement: has_replacement ? row[1] : nil, word: row[0],
action_key: action_key, replacement: has_replacement ? row[1] : nil,
case_sensitive: "true" == row[2]&.strip&.downcase action_key: action_key,
) case_sensitive: "true" == row[2]&.strip&.downcase,
)
if watched_word.valid? if watched_word.valid?
StaffActionLogger.new(current_user).log_watched_words_creation(watched_word) StaffActionLogger.new(current_user).log_watched_words_creation(watched_word)
end end
end end
end end
data = { url: '/ok' } data = { url: "/ok" }
rescue => e rescue => e
data = failed_json.merge(errors: [e.message]) data = failed_json.merge(errors: [e.message])
end end
@ -73,10 +75,10 @@ class Admin::WatchedWordsController < Admin::StaffController
content = content.pluck(:word).join("\n") content = content.pluck(:word).join("\n")
end end
headers['Content-Length'] = content.bytesize.to_s headers["Content-Length"] = content.bytesize.to_s
send_data content, send_data content,
filename: "#{Discourse.current_hostname}-watched-words-#{name}.csv", filename: "#{Discourse.current_hostname}-watched-words-#{name}.csv",
content_type: "text/csv" content_type: "text/csv"
end end
def clear_all def clear_all
@ -85,10 +87,12 @@ class Admin::WatchedWordsController < Admin::StaffController
action = WatchedWord.actions[name] action = WatchedWord.actions[name]
raise Discourse::NotFound if !action raise Discourse::NotFound if !action
WatchedWord.where(action: action).find_each do |watched_word| WatchedWord
watched_word.destroy! .where(action: action)
StaffActionLogger.new(current_user).log_watched_words_deletion(watched_word) .find_each do |watched_word|
end watched_word.destroy!
StaffActionLogger.new(current_user).log_watched_words_deletion(watched_word)
end
WordWatcher.clear_cache! WordWatcher.clear_cache!
render json: success_json render json: success_json
end end

View File

@ -1,17 +1,19 @@
# frozen_string_literal: true # frozen_string_literal: true
class Admin::WebHooksController < Admin::AdminController class Admin::WebHooksController < Admin::AdminController
before_action :fetch_web_hook, only: %i(show update destroy list_events bulk_events ping) before_action :fetch_web_hook, only: %i[show update destroy list_events bulk_events ping]
def index def index
limit = 50 limit = 50
offset = params[:offset].to_i offset = params[:offset].to_i
web_hooks = WebHook.limit(limit) web_hooks =
.offset(offset) WebHook
.includes(:web_hook_event_types) .limit(limit)
.includes(:categories) .offset(offset)
.includes(:groups) .includes(:web_hook_event_types)
.includes(:categories)
.includes(:groups)
json = { json = {
web_hooks: serialize_data(web_hooks, AdminWebHookSerializer), web_hooks: serialize_data(web_hooks, AdminWebHookSerializer),
@ -19,29 +21,34 @@ class Admin::WebHooksController < Admin::AdminController
event_types: WebHookEventType.active, event_types: WebHookEventType.active,
default_event_types: WebHook.default_event_types, default_event_types: WebHook.default_event_types,
content_types: WebHook.content_types.map { |name, id| { id: id, name: name } }, content_types: WebHook.content_types.map { |name, id| { id: id, name: name } },
delivery_statuses: WebHook.last_delivery_statuses.map { |name, id| { id: id, name: name.to_s } }, delivery_statuses:
WebHook.last_delivery_statuses.map { |name, id| { id: id, name: name.to_s } },
}, },
total_rows_web_hooks: WebHook.count, total_rows_web_hooks: WebHook.count,
load_more_web_hooks: admin_web_hooks_path(limit: limit, offset: offset + limit, format: :json) load_more_web_hooks:
admin_web_hooks_path(limit: limit, offset: offset + limit, format: :json),
} }
render json: MultiJson.dump(json), status: 200 render json: MultiJson.dump(json), status: 200
end end
def show def show
render_serialized(@web_hook, AdminWebHookSerializer, root: 'web_hook') render_serialized(@web_hook, AdminWebHookSerializer, root: "web_hook")
end end
def edit def edit
render_serialized(@web_hook, AdminWebHookSerializer, root: 'web_hook') render_serialized(@web_hook, AdminWebHookSerializer, root: "web_hook")
end end
def create def create
web_hook = WebHook.new(web_hook_params) web_hook = WebHook.new(web_hook_params)
if web_hook.save if web_hook.save
StaffActionLogger.new(current_user).log_web_hook(web_hook, UserHistory.actions[:web_hook_create]) StaffActionLogger.new(current_user).log_web_hook(
render_serialized(web_hook, AdminWebHookSerializer, root: 'web_hook') web_hook,
UserHistory.actions[:web_hook_create],
)
render_serialized(web_hook, AdminWebHookSerializer, root: "web_hook")
else else
render_json_error web_hook.errors.full_messages render_json_error web_hook.errors.full_messages
end end
@ -49,8 +56,12 @@ class Admin::WebHooksController < Admin::AdminController
def update def update
if @web_hook.update(web_hook_params) if @web_hook.update(web_hook_params)
StaffActionLogger.new(current_user).log_web_hook(@web_hook, UserHistory.actions[:web_hook_update], changes: @web_hook.saved_changes) StaffActionLogger.new(current_user).log_web_hook(
render_serialized(@web_hook, AdminWebHookSerializer, root: 'web_hook') @web_hook,
UserHistory.actions[:web_hook_update],
changes: @web_hook.saved_changes,
)
render_serialized(@web_hook, AdminWebHookSerializer, root: "web_hook")
else else
render_json_error @web_hook.errors.full_messages render_json_error @web_hook.errors.full_messages
end end
@ -58,7 +69,10 @@ class Admin::WebHooksController < Admin::AdminController
def destroy def destroy
@web_hook.destroy! @web_hook.destroy!
StaffActionLogger.new(current_user).log_web_hook(@web_hook, UserHistory.actions[:web_hook_destroy]) StaffActionLogger.new(current_user).log_web_hook(
@web_hook,
UserHistory.actions[:web_hook_destroy],
)
render json: success_json render json: success_json
end end
@ -67,12 +81,17 @@ class Admin::WebHooksController < Admin::AdminController
offset = params[:offset].to_i offset = params[:offset].to_i
json = { json = {
web_hook_events: serialize_data(@web_hook.web_hook_events.limit(limit).offset(offset), AdminWebHookEventSerializer), web_hook_events:
serialize_data(
@web_hook.web_hook_events.limit(limit).offset(offset),
AdminWebHookEventSerializer,
),
total_rows_web_hook_events: @web_hook.web_hook_events.count, total_rows_web_hook_events: @web_hook.web_hook_events.count,
load_more_web_hook_events: web_hook_events_admin_api_index_path(limit: limit, offset: offset + limit, format: :json), load_more_web_hook_events:
web_hook_events_admin_api_index_path(limit: limit, offset: offset + limit, format: :json),
extras: { extras: {
web_hook_id: @web_hook.id web_hook_id: @web_hook.id,
} },
} }
render json: MultiJson.dump(json), status: 200 render json: MultiJson.dump(json), status: 200
@ -91,26 +110,37 @@ class Admin::WebHooksController < Admin::AdminController
web_hook = web_hook_event.web_hook web_hook = web_hook_event.web_hook
emitter = WebHookEmitter.new(web_hook, web_hook_event) emitter = WebHookEmitter.new(web_hook, web_hook_event)
emitter.emit!(headers: MultiJson.load(web_hook_event.headers), body: web_hook_event.payload) emitter.emit!(headers: MultiJson.load(web_hook_event.headers), body: web_hook_event.payload)
render_serialized(web_hook_event, AdminWebHookEventSerializer, root: 'web_hook_event') render_serialized(web_hook_event, AdminWebHookEventSerializer, root: "web_hook_event")
else else
render json: failed_json render json: failed_json
end end
end end
def ping def ping
Jobs.enqueue(:emit_web_hook_event, web_hook_id: @web_hook.id, event_type: 'ping', event_name: 'ping') Jobs.enqueue(
:emit_web_hook_event,
web_hook_id: @web_hook.id,
event_type: "ping",
event_name: "ping",
)
render json: success_json render json: success_json
end end
private private
def web_hook_params def web_hook_params
params.require(:web_hook).permit(:payload_url, :content_type, :secret, params.require(:web_hook).permit(
:wildcard_web_hook, :active, :verify_certificate, :payload_url,
web_hook_event_type_ids: [], :content_type,
group_ids: [], :secret,
tag_names: [], :wildcard_web_hook,
category_ids: []) :active,
:verify_certificate,
web_hook_event_type_ids: [],
group_ids: [],
tag_names: [],
category_ids: [],
)
end end
def fetch_web_hook def fetch_web_hook

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'current_user' require "current_user"
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include CurrentUser include CurrentUser
@ -43,17 +43,18 @@ class ApplicationController < ActionController::Base
before_action :block_if_requires_login before_action :block_if_requires_login
before_action :preload_json before_action :preload_json
before_action :check_xhr before_action :check_xhr
after_action :add_readonly_header after_action :add_readonly_header
after_action :perform_refresh_session after_action :perform_refresh_session
after_action :dont_cache_page after_action :dont_cache_page
after_action :conditionally_allow_site_embedding after_action :conditionally_allow_site_embedding
after_action :ensure_vary_header after_action :ensure_vary_header
after_action :add_noindex_header, if: -> { is_feed_request? || !SiteSetting.allow_index_in_robots_txt } after_action :add_noindex_header,
after_action :add_noindex_header_to_non_canonical, if: :spa_boot_request? if: -> { is_feed_request? || !SiteSetting.allow_index_in_robots_txt }
after_action :add_noindex_header_to_non_canonical, if: :spa_boot_request?
around_action :link_preload, if: -> { spa_boot_request? && GlobalSetting.preload_link_header } around_action :link_preload, if: -> { spa_boot_request? && GlobalSetting.preload_link_header }
HONEYPOT_KEY ||= 'HONEYPOT_KEY' HONEYPOT_KEY ||= "HONEYPOT_KEY"
CHALLENGE_KEY ||= 'CHALLENGE_KEY' CHALLENGE_KEY ||= "CHALLENGE_KEY"
layout :set_layout layout :set_layout
@ -68,12 +69,12 @@ class ApplicationController < ActionController::Base
def use_crawler_layout? def use_crawler_layout?
@use_crawler_layout ||= @use_crawler_layout ||=
request.user_agent && request.user_agent && (request.media_type.blank? || request.media_type.include?("html")) &&
(request.media_type.blank? || request.media_type.include?('html')) && !%w[json rss].include?(params[:format]) &&
!['json', 'rss'].include?(params[:format]) && (
(has_escaped_fragment? || params.key?("print") || show_browser_update? || has_escaped_fragment? || params.key?("print") || show_browser_update? ||
CrawlerDetection.crawler?(request.user_agent, request.headers["HTTP_VIA"]) CrawlerDetection.crawler?(request.user_agent, request.headers["HTTP_VIA"])
) )
end end
def perform_refresh_session def perform_refresh_session
@ -91,19 +92,16 @@ class ApplicationController < ActionController::Base
response.cache_control[:no_cache] = true response.cache_control[:no_cache] = true
response.cache_control[:extras] = ["no-store"] response.cache_control[:extras] = ["no-store"]
end end
if SiteSetting.login_required response.headers["Discourse-No-Onebox"] = "1" if SiteSetting.login_required
response.headers['Discourse-No-Onebox'] = '1'
end
end end
def conditionally_allow_site_embedding def conditionally_allow_site_embedding
if SiteSetting.allow_embedding_site_in_an_iframe response.headers.delete("X-Frame-Options") if SiteSetting.allow_embedding_site_in_an_iframe
response.headers.delete('X-Frame-Options')
end
end end
def ember_cli_required? def ember_cli_required?
Rails.env.development? && ENV["ALLOW_EMBER_CLI_PROXY_BYPASS"] != "1" && request.headers['X-Discourse-Ember-CLI'] != 'true' Rails.env.development? && ENV["ALLOW_EMBER_CLI_PROXY_BYPASS"] != "1" &&
request.headers["X-Discourse-Ember-CLI"] != "true"
end end
def application_layout def application_layout
@ -118,14 +116,16 @@ class ApplicationController < ActionController::Base
return "crawler" return "crawler"
end end
use_crawler_layout? ? 'crawler' : application_layout use_crawler_layout? ? "crawler" : application_layout
end end
class RenderEmpty < StandardError; end class RenderEmpty < StandardError
class PluginDisabled < StandardError; end end
class PluginDisabled < StandardError
end
rescue_from RenderEmpty do rescue_from RenderEmpty do
with_resolved_locale { render 'default/empty' } with_resolved_locale { render "default/empty" }
end end
rescue_from ArgumentError do |e| rescue_from ArgumentError do |e|
@ -147,10 +147,10 @@ class ApplicationController < ActionController::Base
end end
rescue_from Discourse::SiteSettingMissing do |e| rescue_from Discourse::SiteSettingMissing do |e|
render_json_error I18n.t('site_setting_missing', name: e.message), status: 500 render_json_error I18n.t("site_setting_missing", name: e.message), status: 500
end end
rescue_from ActionController::RoutingError, PluginDisabled do rescue_from ActionController::RoutingError, PluginDisabled do
rescue_discourse_actions(:not_found, 404) rescue_discourse_actions(:not_found, 404)
end end
@ -180,21 +180,20 @@ class ApplicationController < ActionController::Base
rescue_from RateLimiter::LimitExceeded do |e| rescue_from RateLimiter::LimitExceeded do |e|
retry_time_in_seconds = e&.available_in retry_time_in_seconds = e&.available_in
response_headers = { response_headers = { "Retry-After": retry_time_in_seconds.to_s }
'Retry-After': retry_time_in_seconds.to_s
}
if e&.error_code response_headers["Discourse-Rate-Limit-Error-Code"] = e.error_code if e&.error_code
response_headers['Discourse-Rate-Limit-Error-Code'] = e.error_code
end
with_resolved_locale do with_resolved_locale do
render_json_error( render_json_error(
e.description, e.description,
type: :rate_limit, type: :rate_limit,
status: 429, status: 429,
extras: { wait_seconds: retry_time_in_seconds, time_left: e&.time_left }, extras: {
headers: response_headers wait_seconds: retry_time_in_seconds,
time_left: e&.time_left,
},
headers: response_headers,
) )
end end
end end
@ -208,10 +207,7 @@ class ApplicationController < ActionController::Base
end end
rescue_from Discourse::InvalidParameters do |e| rescue_from Discourse::InvalidParameters do |e|
opts = { opts = { custom_message: "invalid_params", custom_message_params: { message: e.message } }
custom_message: 'invalid_params',
custom_message_params: { message: e.message }
}
if (request.format && request.format.json?) || request.xhr? || !request.get? if (request.format && request.format.json?) || request.xhr? || !request.get?
rescue_discourse_actions(:invalid_parameters, 400, opts.merge(include_ember: true)) rescue_discourse_actions(:invalid_parameters, 400, opts.merge(include_ember: true))
@ -226,14 +222,12 @@ class ApplicationController < ActionController::Base
e.status, e.status,
check_permalinks: e.check_permalinks, check_permalinks: e.check_permalinks,
original_path: e.original_path, original_path: e.original_path,
custom_message: e.custom_message custom_message: e.custom_message,
) )
end end
rescue_from Discourse::InvalidAccess do |e| rescue_from Discourse::InvalidAccess do |e|
if e.opts[:delete_cookie].present? cookies.delete(e.opts[:delete_cookie]) if e.opts[:delete_cookie].present?
cookies.delete(e.opts[:delete_cookie])
end
rescue_discourse_actions( rescue_discourse_actions(
:invalid_access, :invalid_access,
@ -241,7 +235,7 @@ class ApplicationController < ActionController::Base
include_ember: true, include_ember: true,
custom_message: e.custom_message, custom_message: e.custom_message,
custom_message_params: e.custom_message_params, custom_message_params: e.custom_message_params,
group: e.group group: e.group,
) )
end end
@ -249,20 +243,16 @@ class ApplicationController < ActionController::Base
unless response_body unless response_body
respond_to do |format| respond_to do |format|
format.json do format.json do
render_json_error I18n.t('read_only_mode_enabled'), type: :read_only, status: 503 render_json_error I18n.t("read_only_mode_enabled"), type: :read_only, status: 503
end
format.html do
render status: 503, layout: 'no_ember', template: 'exceptions/read_only'
end end
format.html { render status: 503, layout: "no_ember", template: "exceptions/read_only" }
end end
end end
end end
rescue_from SecondFactor::AuthManager::SecondFactorRequired do |e| rescue_from SecondFactor::AuthManager::SecondFactorRequired do |e|
if request.xhr? if request.xhr?
render json: { render json: { second_factor_challenge_nonce: e.nonce }, status: 403
second_factor_challenge_nonce: e.nonce
}, status: 403
else else
redirect_to session_2fa_path(nonce: e.nonce) redirect_to session_2fa_path(nonce: e.nonce)
end end
@ -274,7 +264,7 @@ class ApplicationController < ActionController::Base
def redirect_with_client_support(url, options = {}) def redirect_with_client_support(url, options = {})
if request.xhr? if request.xhr?
response.headers['Discourse-Xhr-Redirect'] = 'true' response.headers["Discourse-Xhr-Redirect"] = "true"
render plain: url render plain: url
else else
redirect_to url, options redirect_to url, options
@ -283,9 +273,9 @@ class ApplicationController < ActionController::Base
def rescue_discourse_actions(type, status_code, opts = nil) def rescue_discourse_actions(type, status_code, opts = nil)
opts ||= {} opts ||= {}
show_json_errors = (request.format && request.format.json?) || show_json_errors =
(request.xhr?) || (request.format && request.format.json?) || (request.xhr?) ||
((params[:external_id] || '').ends_with? '.json') ((params[:external_id] || "").ends_with? ".json")
if type == :not_found && opts[:check_permalinks] if type == :not_found && opts[:check_permalinks]
url = opts[:original_path] || request.fullpath url = opts[:original_path] || request.fullpath
@ -295,7 +285,9 @@ class ApplicationController < ActionController::Base
# cause category / topic was deleted # cause category / topic was deleted
if permalink.present? && permalink.target_url if permalink.present? && permalink.target_url
# permalink present, redirect to that URL # permalink present, redirect to that URL
redirect_with_client_support permalink.target_url, status: :moved_permanently, allow_other_host: true redirect_with_client_support permalink.target_url,
status: :moved_permanently,
allow_other_host: true
return return
end end
end end
@ -321,11 +313,15 @@ class ApplicationController < ActionController::Base
with_resolved_locale(check_current_user: false) do with_resolved_locale(check_current_user: false) do
# Include error in HTML format for topics#show. # Include error in HTML format for topics#show.
if (request.params[:controller] == 'topics' && request.params[:action] == 'show') || (request.params[:controller] == 'categories' && request.params[:action] == 'find_by_slug') if (request.params[:controller] == "topics" && request.params[:action] == "show") ||
(
request.params[:controller] == "categories" &&
request.params[:action] == "find_by_slug"
)
opts[:extras] = { opts[:extras] = {
title: I18n.t('page_not_found.page_title'), title: I18n.t("page_not_found.page_title"),
html: build_not_found_page(error_page_opts), html: build_not_found_page(error_page_opts),
group: error_page_opts[:group] group: error_page_opts[:group],
} }
end end
end end
@ -340,7 +336,7 @@ class ApplicationController < ActionController::Base
return render plain: message, status: status_code return render plain: message, status: status_code
end end
with_resolved_locale do with_resolved_locale do
error_page_opts[:layout] = (opts[:include_ember] && @preloaded) ? 'application' : 'no_ember' error_page_opts[:layout] = (opts[:include_ember] && @preloaded) ? "application" : "no_ember"
render html: build_not_found_page(error_page_opts) render html: build_not_found_page(error_page_opts)
end end
end end
@ -373,9 +369,8 @@ class ApplicationController < ActionController::Base
def clear_notifications def clear_notifications
if current_user && !@readonly_mode if current_user && !@readonly_mode
cookie_notifications = cookies["cn"]
cookie_notifications = cookies['cn'] notifications = request.headers["Discourse-Clear-Notifications"]
notifications = request.headers['Discourse-Clear-Notifications']
if cookie_notifications if cookie_notifications
if notifications.present? if notifications.present?
@ -392,22 +387,28 @@ class ApplicationController < ActionController::Base
current_user.publish_notifications_state current_user.publish_notifications_state
cookie_args = {} cookie_args = {}
cookie_args[:path] = Discourse.base_path if Discourse.base_path.present? cookie_args[:path] = Discourse.base_path if Discourse.base_path.present?
cookies.delete('cn', cookie_args) cookies.delete("cn", cookie_args)
end end
end end
end end
def with_resolved_locale(check_current_user: true) def with_resolved_locale(check_current_user: true)
if check_current_user && (user = current_user rescue nil) if check_current_user &&
(
user =
begin
current_user
rescue StandardError
nil
end
)
locale = user.effective_locale locale = user.effective_locale
else else
locale = Discourse.anonymous_locale(request) locale = Discourse.anonymous_locale(request)
locale ||= SiteSetting.default_locale locale ||= SiteSetting.default_locale
end end
if !I18n.locale_available?(locale) locale = SiteSettings::DefaultsProvider::DEFAULT_LOCALE if !I18n.locale_available?(locale)
locale = SiteSettings::DefaultsProvider::DEFAULT_LOCALE
end
I18n.ensure_all_loaded! I18n.ensure_all_loaded!
I18n.with_locale(locale) { yield } I18n.with_locale(locale) { yield }
@ -458,7 +459,8 @@ class ApplicationController < ActionController::Base
safe_mode = safe_mode.split(",") safe_mode = safe_mode.split(",")
request.env[NO_THEMES] = safe_mode.include?(NO_THEMES) || safe_mode.include?(LEGACY_NO_THEMES) request.env[NO_THEMES] = safe_mode.include?(NO_THEMES) || safe_mode.include?(LEGACY_NO_THEMES)
request.env[NO_PLUGINS] = safe_mode.include?(NO_PLUGINS) request.env[NO_PLUGINS] = safe_mode.include?(NO_PLUGINS)
request.env[NO_UNOFFICIAL_PLUGINS] = safe_mode.include?(NO_UNOFFICIAL_PLUGINS) || safe_mode.include?(LEGACY_NO_UNOFFICIAL_PLUGINS) request.env[NO_UNOFFICIAL_PLUGINS] = safe_mode.include?(NO_UNOFFICIAL_PLUGINS) ||
safe_mode.include?(LEGACY_NO_UNOFFICIAL_PLUGINS)
end end
end end
@ -471,8 +473,7 @@ class ApplicationController < ActionController::Base
theme_id = nil theme_id = nil
if (preview_theme_id = request[:preview_theme_id]&.to_i) && if (preview_theme_id = request[:preview_theme_id]&.to_i) &&
guardian.allow_themes?([preview_theme_id], include_preview: true) guardian.allow_themes?([preview_theme_id], include_preview: true)
theme_id = preview_theme_id theme_id = preview_theme_id
end end
@ -491,7 +492,8 @@ class ApplicationController < ActionController::Base
theme_id = ids.first if guardian.allow_themes?(ids) theme_id = ids.first if guardian.allow_themes?(ids)
end end
if theme_id.blank? && SiteSetting.default_theme_id != -1 && guardian.allow_themes?([SiteSetting.default_theme_id]) if theme_id.blank? && SiteSetting.default_theme_id != -1 &&
guardian.allow_themes?([SiteSetting.default_theme_id])
theme_id = SiteSetting.default_theme_id theme_id = SiteSetting.default_theme_id
end end
@ -533,13 +535,11 @@ class ApplicationController < ActionController::Base
def render_json_dump(obj, opts = nil) def render_json_dump(obj, opts = nil)
opts ||= {} opts ||= {}
if opts[:rest_serializer] if opts[:rest_serializer]
obj['__rest_serializer'] = "1" obj["__rest_serializer"] = "1"
opts.each do |k, v| opts.each { |k, v| obj[k] = v if k.to_s.start_with?("refresh_") }
obj[k] = v if k.to_s.start_with?("refresh_")
end
obj['extras'] = opts[:extras] if opts[:extras] obj["extras"] = opts[:extras] if opts[:extras]
obj['meta'] = opts[:meta] if opts[:meta] obj["meta"] = opts[:meta] if opts[:meta]
end end
render json: MultiJson.dump(obj), status: opts[:status] || 200 render json: MultiJson.dump(obj), status: opts[:status] || 200
@ -557,29 +557,33 @@ class ApplicationController < ActionController::Base
def fetch_user_from_params(opts = nil, eager_load = []) def fetch_user_from_params(opts = nil, eager_load = [])
opts ||= {} opts ||= {}
user = if params[:username] user =
username_lower = params[:username].downcase.chomp('.json') if params[:username]
username_lower = params[:username].downcase.chomp(".json")
if current_user && current_user.username_lower == username_lower if current_user && current_user.username_lower == username_lower
current_user current_user
else else
find_opts = { username_lower: username_lower } find_opts = { username_lower: username_lower }
find_opts[:active] = true unless opts[:include_inactive] || current_user.try(:staff?) find_opts[:active] = true unless opts[:include_inactive] || current_user.try(:staff?)
result = User result = User
(result = result.includes(*eager_load)) if !eager_load.empty? (result = result.includes(*eager_load)) if !eager_load.empty?
result.find_by(find_opts) result.find_by(find_opts)
end
elsif params[:external_id]
external_id = params[:external_id].chomp(".json")
if provider_name = params[:external_provider]
raise Discourse::InvalidAccess unless guardian.is_admin? # external_id might be something sensitive
provider = Discourse.enabled_authenticators.find { |a| a.name == provider_name }
raise Discourse::NotFound if !provider&.is_managed? # Only managed authenticators use UserAssociatedAccount
UserAssociatedAccount.find_by(
provider_name: provider_name,
provider_uid: external_id,
)&.user
else
SingleSignOnRecord.find_by(external_id: external_id).try(:user)
end
end end
elsif params[:external_id]
external_id = params[:external_id].chomp('.json')
if provider_name = params[:external_provider]
raise Discourse::InvalidAccess unless guardian.is_admin? # external_id might be something sensitive
provider = Discourse.enabled_authenticators.find { |a| a.name == provider_name }
raise Discourse::NotFound if !provider&.is_managed? # Only managed authenticators use UserAssociatedAccount
UserAssociatedAccount.find_by(provider_name: provider_name, provider_uid: external_id)&.user
else
SingleSignOnRecord.find_by(external_id: external_id).try(:user)
end
end
raise Discourse::NotFound if user.blank? raise Discourse::NotFound if user.blank?
guardian.ensure_can_see!(user) guardian.ensure_can_see!(user)
@ -587,15 +591,17 @@ class ApplicationController < ActionController::Base
end end
def post_ids_including_replies def post_ids_including_replies
post_ids = params[:post_ids].map(&:to_i) post_ids = params[:post_ids].map(&:to_i)
post_ids |= PostReply.where(post_id: params[:reply_post_ids]).pluck(:reply_post_id) if params[:reply_post_ids] post_ids |= PostReply.where(post_id: params[:reply_post_ids]).pluck(:reply_post_id) if params[
:reply_post_ids
]
post_ids post_ids
end end
def no_cookies def no_cookies
# do your best to ensure response has no cookies # do your best to ensure response has no cookies
# longer term we may want to push this into middleware # longer term we may want to push this into middleware
headers.delete 'Set-Cookie' headers.delete "Set-Cookie"
request.session_options[:skip] = true request.session_options[:skip] = true
end end
@ -615,9 +621,7 @@ class ApplicationController < ActionController::Base
RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 6, 1.minute).performed! RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 6, 1.minute).performed!
if user RateLimiter.new(nil, "second-factor-min-#{user.username}", 6, 1.minute).performed! if user
RateLimiter.new(nil, "second-factor-min-#{user.username}", 6, 1.minute).performed!
end
end end
private private
@ -634,11 +638,17 @@ class ApplicationController < ActionController::Base
end end
def preload_current_user_data def preload_current_user_data
store_preloaded("currentUser", MultiJson.dump(CurrentUserSerializer.new(current_user, scope: guardian, root: false))) store_preloaded(
report = TopicTrackingState.report(current_user) "currentUser",
serializer = ActiveModel::ArraySerializer.new( MultiJson.dump(CurrentUserSerializer.new(current_user, scope: guardian, root: false)),
report, each_serializer: TopicTrackingStateSerializer, scope: guardian
) )
report = TopicTrackingState.report(current_user)
serializer =
ActiveModel::ArraySerializer.new(
report,
each_serializer: TopicTrackingStateSerializer,
scope: guardian,
)
store_preloaded("topicTrackingStates", MultiJson.dump(serializer)) store_preloaded("topicTrackingStates", MultiJson.dump(serializer))
end end
@ -648,20 +658,18 @@ class ApplicationController < ActionController::Base
data = data =
if @theme_id.present? if @theme_id.present?
{ {
top: Theme.lookup_field(@theme_id, target, "after_header"), top: Theme.lookup_field(@theme_id, target, "after_header"),
footer: Theme.lookup_field(@theme_id, target, "footer") footer: Theme.lookup_field(@theme_id, target, "footer"),
} }
else else
{} {}
end end
if DiscoursePluginRegistry.custom_html data.merge! DiscoursePluginRegistry.custom_html if DiscoursePluginRegistry.custom_html
data.merge! DiscoursePluginRegistry.custom_html
end
DiscoursePluginRegistry.html_builders.each do |name, _| DiscoursePluginRegistry.html_builders.each do |name, _|
if name.start_with?("client:") if name.start_with?("client:")
data[name.sub(/^client:/, '')] = DiscoursePluginRegistry.build_html(name, self) data[name.sub(/^client:/, "")] = DiscoursePluginRegistry.build_html(name, self)
end end
end end
@ -703,7 +711,7 @@ class ApplicationController < ActionController::Base
render( render(
json: MultiJson.dump(create_errors_json(obj, opts)), json: MultiJson.dump(create_errors_json(obj, opts)),
status: opts[:status] || status_code(obj) status: opts[:status] || status_code(obj),
) )
end end
@ -714,11 +722,11 @@ class ApplicationController < ActionController::Base
end end
def success_json def success_json
{ success: 'OK' } { success: "OK" }
end end
def failed_json def failed_json
{ failed: 'FAILED' } { failed: "FAILED" }
end end
def json_result(obj, opts = {}) def json_result(obj, opts = {})
@ -727,17 +735,21 @@ class ApplicationController < ActionController::Base
# If we were given a serializer, add the class to the json that comes back # If we were given a serializer, add the class to the json that comes back
if opts[:serializer].present? if opts[:serializer].present?
json[obj.class.name.underscore] = opts[:serializer].new(obj, scope: guardian).serializable_hash json[obj.class.name.underscore] = opts[:serializer].new(
obj,
scope: guardian,
).serializable_hash
end end
render json: MultiJson.dump(json) render json: MultiJson.dump(json)
else else
error_obj = nil error_obj = nil
if opts[:additional_errors] if opts[:additional_errors]
error_target = opts[:additional_errors].find do |o| error_target =
target = obj.public_send(o) opts[:additional_errors].find do |o|
target && target.errors.present? target = obj.public_send(o)
end target && target.errors.present?
end
error_obj = obj.public_send(error_target) if error_target error_obj = obj.public_send(error_target) if error_target
end end
render_json_error(error_obj || obj) render_json_error(error_obj || obj)
@ -756,11 +768,15 @@ class ApplicationController < ActionController::Base
def check_xhr def check_xhr
# bypass xhr check on PUT / POST / DELETE provided api key is there, otherwise calling api is annoying # bypass xhr check on PUT / POST / DELETE provided api key is there, otherwise calling api is annoying
return if !request.get? && (is_api? || is_user_api?) return if !request.get? && (is_api? || is_user_api?)
raise ApplicationController::RenderEmpty.new unless ((request.format && request.format.json?) || request.xhr?) unless ((request.format && request.format.json?) || request.xhr?)
raise ApplicationController::RenderEmpty.new
end
end end
def apply_cdn_headers def apply_cdn_headers
Discourse.apply_cdn_headers(response.headers) if Discourse.is_cdn_request?(request.env, request.method) if Discourse.is_cdn_request?(request.env, request.method)
Discourse.apply_cdn_headers(response.headers)
end
end end
def self.requires_login(arg = {}) def self.requires_login(arg = {})
@ -811,8 +827,9 @@ class ApplicationController < ActionController::Base
if SiteSetting.auth_immediately && SiteSetting.enable_discourse_connect? if SiteSetting.auth_immediately && SiteSetting.enable_discourse_connect?
# save original URL in a session so we can redirect after login # save original URL in a session so we can redirect after login
session[:destination_url] = destination_url session[:destination_url] = destination_url
redirect_to path('/session/sso') redirect_to path("/session/sso")
elsif SiteSetting.auth_immediately && !SiteSetting.enable_local_logins && Discourse.enabled_authenticators.length == 1 && !cookies[:authentication_data] elsif SiteSetting.auth_immediately && !SiteSetting.enable_local_logins &&
Discourse.enabled_authenticators.length == 1 && !cookies[:authentication_data]
# Only one authentication provider, direct straight to it. # Only one authentication provider, direct straight to it.
# If authentication_data is present, then we are halfway though registration. Don't redirect offsite # If authentication_data is present, then we are halfway though registration. Don't redirect offsite
cookies[:destination_url] = destination_url cookies[:destination_url] = destination_url
@ -831,9 +848,7 @@ class ApplicationController < ActionController::Base
# Redirects to provided URL scheme if # Redirects to provided URL scheme if
# - request uses a valid public key and auth_redirect scheme # - request uses a valid public key and auth_redirect scheme
# - one_time_password scope is allowed # - one_time_password scope is allowed
if !current_user && if !current_user && params.has_key?(:user_api_public_key) && params.has_key?(:auth_redirect)
params.has_key?(:user_api_public_key) &&
params.has_key?(:auth_redirect)
begin begin
OpenSSL::PKey::RSA.new(params[:user_api_public_key]) OpenSSL::PKey::RSA.new(params[:user_api_public_key])
rescue OpenSSL::PKey::RSAError rescue OpenSSL::PKey::RSAError
@ -872,26 +887,45 @@ class ApplicationController < ActionController::Base
def should_enforce_2fa? def should_enforce_2fa?
disqualified_from_2fa_enforcement = request.format.json? || is_api? || current_user.anonymous? disqualified_from_2fa_enforcement = request.format.json? || is_api? || current_user.anonymous?
enforcing_2fa = ((SiteSetting.enforce_second_factor == 'staff' && current_user.staff?) || SiteSetting.enforce_second_factor == 'all') enforcing_2fa =
!disqualified_from_2fa_enforcement && enforcing_2fa && !current_user.has_any_second_factor_methods_enabled? (
(SiteSetting.enforce_second_factor == "staff" && current_user.staff?) ||
SiteSetting.enforce_second_factor == "all"
)
!disqualified_from_2fa_enforcement && enforcing_2fa &&
!current_user.has_any_second_factor_methods_enabled?
end end
def build_not_found_page(opts = {}) def build_not_found_page(opts = {})
if SiteSetting.bootstrap_error_pages? if SiteSetting.bootstrap_error_pages?
preload_json preload_json
opts[:layout] = 'application' if opts[:layout] == 'no_ember' opts[:layout] = "application" if opts[:layout] == "no_ember"
end end
@current_user = current_user rescue nil @current_user =
begin
current_user
rescue StandardError
nil
end
if !SiteSetting.login_required? || @current_user if !SiteSetting.login_required? || @current_user
key = "page_not_found_topics:#{I18n.locale}" key = "page_not_found_topics:#{I18n.locale}"
@topics_partial = Discourse.cache.fetch(key, expires_in: 10.minutes) do @topics_partial =
category_topic_ids = Category.pluck(:topic_id).compact Discourse
@top_viewed = TopicQuery.new(nil, except_topic_ids: category_topic_ids).list_top_for("monthly").topics.first(10) .cache
@recent = Topic.includes(:category).where.not(id: category_topic_ids).recent(10) .fetch(key, expires_in: 10.minutes) do
render_to_string partial: '/exceptions/not_found_topics', formats: [:html] category_topic_ids = Category.pluck(:topic_id).compact
end.html_safe @top_viewed =
TopicQuery
.new(nil, except_topic_ids: category_topic_ids)
.list_top_for("monthly")
.topics
.first(10)
@recent = Topic.includes(:category).where.not(id: category_topic_ids).recent(10)
render_to_string partial: "/exceptions/not_found_topics", formats: [:html]
end
.html_safe
end end
@container_class = "wrap not-found-container" @container_class = "wrap not-found-container"
@ -902,13 +936,16 @@ class ApplicationController < ActionController::Base
params[:slug] = params[:slug].first if params[:slug].kind_of?(Array) params[:slug] = params[:slug].first if params[:slug].kind_of?(Array)
params[:id] = params[:id].first if params[:id].kind_of?(Array) params[:id] = params[:id].first if params[:id].kind_of?(Array)
@slug = (params[:slug].presence || params[:id].presence || "").to_s.tr('-', ' ') @slug = (params[:slug].presence || params[:id].presence || "").to_s.tr("-", " ")
render_to_string status: opts[:status], layout: opts[:layout], formats: [:html], template: '/exceptions/not_found' render_to_string status: opts[:status],
layout: opts[:layout],
formats: [:html],
template: "/exceptions/not_found"
end end
def is_asset_path def is_asset_path
request.env['DISCOURSE_IS_ASSET_PATH'] = 1 request.env["DISCOURSE_IS_ASSET_PATH"] = 1
end end
def is_feed_request? def is_feed_request?
@ -916,19 +953,20 @@ class ApplicationController < ActionController::Base
end end
def add_noindex_header def add_noindex_header
if request.get? && !response.headers['X-Robots-Tag'] if request.get? && !response.headers["X-Robots-Tag"]
if SiteSetting.allow_index_in_robots_txt if SiteSetting.allow_index_in_robots_txt
response.headers['X-Robots-Tag'] = 'noindex' response.headers["X-Robots-Tag"] = "noindex"
else else
response.headers['X-Robots-Tag'] = 'noindex, nofollow' response.headers["X-Robots-Tag"] = "noindex, nofollow"
end end
end end
end end
def add_noindex_header_to_non_canonical def add_noindex_header_to_non_canonical
canonical = (@canonical_url || @default_canonical) canonical = (@canonical_url || @default_canonical)
if canonical.present? && canonical != request.url && !SiteSetting.allow_indexing_non_canonical_urls if canonical.present? && canonical != request.url &&
response.headers['X-Robots-Tag'] ||= 'noindex' !SiteSetting.allow_indexing_non_canonical_urls
response.headers["X-Robots-Tag"] ||= "noindex"
end end
end end
@ -955,7 +993,7 @@ class ApplicationController < ActionController::Base
# returns an array of integers given a param key # returns an array of integers given a param key
# returns nil if key is not found # returns nil if key is not found
def param_to_integer_list(key, delimiter = ',') def param_to_integer_list(key, delimiter = ",")
case params[key] case params[key]
when String when String
params[key].split(delimiter).map(&:to_i) params[key].split(delimiter).map(&:to_i)
@ -978,20 +1016,19 @@ class ApplicationController < ActionController::Base
user_agent = request.user_agent&.downcase user_agent = request.user_agent&.downcase
return if user_agent.blank? return if user_agent.blank?
SiteSetting.slow_down_crawler_user_agents.downcase.split("|").each do |crawler| SiteSetting
if user_agent.include?(crawler) .slow_down_crawler_user_agents
key = "#{crawler}_crawler_rate_limit" .downcase
limiter = RateLimiter.new( .split("|")
nil, .each do |crawler|
key, if user_agent.include?(crawler)
1, key = "#{crawler}_crawler_rate_limit"
SiteSetting.slow_down_crawler_rate, limiter =
error_code: key RateLimiter.new(nil, key, 1, SiteSetting.slow_down_crawler_rate, error_code: key)
) limiter.performed!
limiter.performed! break
break end
end end
end
end end
def run_second_factor!(action_class, action_data = nil) def run_second_factor!(action_class, action_data = nil)
@ -1000,9 +1037,8 @@ class ApplicationController < ActionController::Base
yield(manager) if block_given? yield(manager) if block_given?
result = manager.run!(request, params, secure_session) result = manager.run!(request, params, secure_session)
if !result.no_second_factors_enabled? && if !result.no_second_factors_enabled? && !result.second_factor_auth_completed? &&
!result.second_factor_auth_completed? && !result.second_factor_auth_skipped?
!result.second_factor_auth_skipped?
# should never happen, but I want to know if somehow it does! (osama) # should never happen, but I want to know if somehow it does! (osama)
raise "2fa process ended up in a bad state!" raise "2fa process ended up in a bad state!"
end end
@ -1013,7 +1049,7 @@ class ApplicationController < ActionController::Base
def link_preload def link_preload
@links_to_preload = [] @links_to_preload = []
yield yield
response.headers['Link'] = @links_to_preload.join(', ') if !@links_to_preload.empty? response.headers["Link"] = @links_to_preload.join(", ") if !@links_to_preload.empty?
end end
def spa_boot_request? def spa_boot_request?

View File

@ -5,6 +5,6 @@ class AssociatedGroupsController < ApplicationController
def index def index
guardian.ensure_can_associate_groups! guardian.ensure_can_associate_groups!
render_serialized(AssociatedGroup.all, AssociatedGroupSerializer, root: 'associated_groups') render_serialized(AssociatedGroup.all, AssociatedGroupSerializer, root: "associated_groups")
end end
end end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class BadgesController < ApplicationController class BadgesController < ApplicationController
skip_before_action :check_xhr, only: [:index, :show] skip_before_action :check_xhr, only: %i[index show]
after_action :add_noindex_header after_action :add_noindex_header
def index def index
@ -16,18 +16,29 @@ class BadgesController < ApplicationController
if (params[:only_listable] == "true") || !request.xhr? if (params[:only_listable] == "true") || !request.xhr?
# NOTE: this is sorted client side if needed # NOTE: this is sorted client side if needed
badges = badges.includes(:badge_grouping) badges =
.includes(:badge_type, :image_upload) badges
.where(enabled: true, listable: true) .includes(:badge_grouping)
.includes(:badge_type, :image_upload)
.where(enabled: true, listable: true)
end end
badges = badges.to_a badges = badges.to_a
user_badges = nil user_badges = nil
if current_user if current_user
user_badges = Set.new(current_user.user_badges.select('distinct badge_id').pluck(:badge_id)) user_badges = Set.new(current_user.user_badges.select("distinct badge_id").pluck(:badge_id))
end end
serialized = MultiJson.dump(serialize_data(badges, BadgeIndexSerializer, root: "badges", user_badges: user_badges, include_long_description: true)) serialized =
MultiJson.dump(
serialize_data(
badges,
BadgeIndexSerializer,
root: "badges",
user_badges: user_badges,
include_long_description: true,
),
)
respond_to do |format| respond_to do |format|
format.html do format.html do
store_preloaded "badges", serialized store_preloaded "badges", serialized
@ -42,27 +53,27 @@ class BadgesController < ApplicationController
params.require(:id) params.require(:id)
@badge = Badge.enabled.find(params[:id]) @badge = Badge.enabled.find(params[:id])
@rss_title = I18n.t('rss_description.badge', display_name: @badge.display_name, site_title: SiteSetting.title) @rss_title =
I18n.t(
"rss_description.badge",
display_name: @badge.display_name,
site_title: SiteSetting.title,
)
@rss_link = "#{Discourse.base_url}/badges/#{@badge.id}/#{@badge.slug}" @rss_link = "#{Discourse.base_url}/badges/#{@badge.id}/#{@badge.slug}"
if current_user if current_user
user_badge = UserBadge.find_by(user_id: current_user.id, badge_id: @badge.id) user_badge = UserBadge.find_by(user_id: current_user.id, badge_id: @badge.id)
if user_badge && user_badge.notification user_badge.notification.update read: true if user_badge && user_badge.notification
user_badge.notification.update read: true @badge.has_badge = true if user_badge
end
if user_badge
@badge.has_badge = true
end
end end
serialized = MultiJson.dump(serialize_data(@badge, BadgeSerializer, root: "badge", include_long_description: true)) serialized =
MultiJson.dump(
serialize_data(@badge, BadgeSerializer, root: "badge", include_long_description: true),
)
respond_to do |format| respond_to do |format|
format.rss do format.rss { @rss_description = @badge.long_description }
@rss_description = @badge.long_description format.html { store_preloaded "badge", serialized }
end
format.html do
store_preloaded "badge", serialized
end
format.json { render json: serialized } format.json { render json: serialized }
end end
end end

View File

@ -6,26 +6,34 @@ class BookmarksController < ApplicationController
def create def create
params.require(:bookmarkable_id) params.require(:bookmarkable_id)
params.require(:bookmarkable_type) params.require(:bookmarkable_type)
params.permit(:bookmarkable_id, :bookmarkable_type, :name, :reminder_at, :auto_delete_preference) params.permit(
:bookmarkable_id,
:bookmarkable_type,
:name,
:reminder_at,
:auto_delete_preference,
)
RateLimiter.new( RateLimiter.new(
current_user, "create_bookmark", SiteSetting.max_bookmarks_per_day, 1.day.to_i current_user,
"create_bookmark",
SiteSetting.max_bookmarks_per_day,
1.day.to_i,
).performed! ).performed!
bookmark_manager = BookmarkManager.new(current_user) bookmark_manager = BookmarkManager.new(current_user)
bookmark = bookmark_manager.create_for( bookmark =
bookmarkable_id: params[:bookmarkable_id], bookmark_manager.create_for(
bookmarkable_type: params[:bookmarkable_type], bookmarkable_id: params[:bookmarkable_id],
name: params[:name], bookmarkable_type: params[:bookmarkable_type],
reminder_at: params[:reminder_at], name: params[:name],
options: { reminder_at: params[:reminder_at],
auto_delete_preference: params[:auto_delete_preference] options: {
} auto_delete_preference: params[:auto_delete_preference],
) },
)
if bookmark_manager.errors.empty? return render json: success_json.merge(id: bookmark.id) if bookmark_manager.errors.empty?
return render json: success_json.merge(id: bookmark.id)
end
render json: failed_json.merge(errors: bookmark_manager.errors.full_messages), status: 400 render json: failed_json.merge(errors: bookmark_manager.errors.full_messages), status: 400
end end
@ -33,7 +41,8 @@ class BookmarksController < ApplicationController
def destroy def destroy
params.require(:id) params.require(:id)
destroyed_bookmark = BookmarkManager.new(current_user).destroy(params[:id]) destroyed_bookmark = BookmarkManager.new(current_user).destroy(params[:id])
render json: success_json.merge(BookmarkManager.bookmark_metadata(destroyed_bookmark, current_user)) render json:
success_json.merge(BookmarkManager.bookmark_metadata(destroyed_bookmark, current_user))
end end
def update def update
@ -46,13 +55,11 @@ class BookmarksController < ApplicationController
name: params[:name], name: params[:name],
reminder_at: params[:reminder_at], reminder_at: params[:reminder_at],
options: { options: {
auto_delete_preference: params[:auto_delete_preference] auto_delete_preference: params[:auto_delete_preference],
} },
) )
if bookmark_manager.errors.empty? return render json: success_json if bookmark_manager.errors.empty?
return render json: success_json
end
render json: failed_json.merge(errors: bookmark_manager.errors.full_messages), status: 400 render json: failed_json.merge(errors: bookmark_manager.errors.full_messages), status: 400
end end
@ -63,9 +70,7 @@ class BookmarksController < ApplicationController
bookmark_manager = BookmarkManager.new(current_user) bookmark_manager = BookmarkManager.new(current_user)
bookmark_manager.toggle_pin(bookmark_id: params[:bookmark_id]) bookmark_manager.toggle_pin(bookmark_id: params[:bookmark_id])
if bookmark_manager.errors.empty? return render json: success_json if bookmark_manager.errors.empty?
return render json: success_json
end
render json: failed_json.merge(errors: bookmark_manager.errors.full_messages), status: 400 render json: failed_json.merge(errors: bookmark_manager.errors.full_messages), status: 400
end end

View File

@ -37,35 +37,34 @@ class BootstrapController < ApplicationController
assets_fake_request.env["QUERY_STRING"] = query assets_fake_request.env["QUERY_STRING"] = query
end end
Discourse.find_plugin_css_assets( Discourse
include_official: allow_plugins?, .find_plugin_css_assets(
include_unofficial: allow_third_party_plugins?, include_official: allow_plugins?,
mobile_view: mobile_view?, include_unofficial: allow_third_party_plugins?,
desktop_view: !mobile_view?, mobile_view: mobile_view?,
request: assets_fake_request desktop_view: !mobile_view?,
).each do |file| request: assets_fake_request,
add_style(file, plugin: true) )
end .each { |file| add_style(file, plugin: true) }
add_style(mobile_view? ? :mobile_theme : :desktop_theme) if theme_id.present? add_style(mobile_view? ? :mobile_theme : :desktop_theme) if theme_id.present?
extra_locales = [] extra_locales = []
if ExtraLocalesController.client_overrides_exist? if ExtraLocalesController.client_overrides_exist?
extra_locales << ExtraLocalesController.url('overrides') extra_locales << ExtraLocalesController.url("overrides")
end end
if staff? extra_locales << ExtraLocalesController.url("admin") if staff?
extra_locales << ExtraLocalesController.url('admin')
end
if admin? extra_locales << ExtraLocalesController.url("wizard") if admin?
extra_locales << ExtraLocalesController.url('wizard')
end
plugin_js = Discourse.find_plugin_js_assets( plugin_js =
include_official: allow_plugins?, Discourse
include_unofficial: allow_third_party_plugins?, .find_plugin_js_assets(
request: assets_fake_request include_official: allow_plugins?,
).map { |f| script_asset_path(f) } include_unofficial: allow_third_party_plugins?,
request: assets_fake_request,
)
.map { |f| script_asset_path(f) }
plugin_test_js = plugin_test_js =
if Rails.env != "production" if Rails.env != "production"
@ -76,7 +75,7 @@ class BootstrapController < ApplicationController
bootstrap = { bootstrap = {
theme_id: theme_id, theme_id: theme_id,
theme_color: "##{ColorScheme.hex_for_name('header_background', scheme_id)}", theme_color: "##{ColorScheme.hex_for_name("header_background", scheme_id)}",
title: SiteSetting.title, title: SiteSetting.title,
current_homepage: current_homepage, current_homepage: current_homepage,
locale_script: locale, locale_script: locale,
@ -90,7 +89,7 @@ class BootstrapController < ApplicationController
html_classes: html_classes, html_classes: html_classes,
html_lang: html_lang, html_lang: html_lang,
login_path: main_app.login_path, login_path: main_app.login_path,
authentication_data: authentication_data authentication_data: authentication_data,
} }
bootstrap[:extra_locales] = extra_locales if extra_locales.present? bootstrap[:extra_locales] = extra_locales if extra_locales.present?
bootstrap[:csrf_token] = form_authenticity_token if current_user bootstrap[:csrf_token] = form_authenticity_token if current_user
@ -99,39 +98,44 @@ class BootstrapController < ApplicationController
end end
def plugin_css_for_tests def plugin_css_for_tests
urls = Discourse.find_plugin_css_assets( urls =
include_disabled: true, Discourse
desktop_view: true, .find_plugin_css_assets(include_disabled: true, desktop_view: true)
).map do |target| .map do |target|
details = Stylesheet::Manager.new().stylesheet_details(target, 'all') details = Stylesheet::Manager.new().stylesheet_details(target, "all")
details[0][:new_href] details[0][:new_href]
end end
stylesheet = <<~CSS stylesheet = <<~CSS
/* For use in tests only - `@import`s all plugin stylesheets */ /* For use in tests only - `@import`s all plugin stylesheets */
#{urls.map { |url| "@import \"#{url}\";" }.join("\n") } #{urls.map { |url| "@import \"#{url}\";" }.join("\n")}
CSS CSS
render plain: stylesheet, content_type: 'text/css' render plain: stylesheet, content_type: "text/css"
end end
private private
def add_scheme(scheme_id, media, css_class) def add_scheme(scheme_id, media, css_class)
return if scheme_id.to_i == -1 return if scheme_id.to_i == -1
if style = Stylesheet::Manager.new(theme_id: theme_id).color_scheme_stylesheet_details(scheme_id, media) if style =
Stylesheet::Manager.new(theme_id: theme_id).color_scheme_stylesheet_details(
scheme_id,
media,
)
@stylesheets << { href: style[:new_href], media: media, class: css_class } @stylesheets << { href: style[:new_href], media: media, class: css_class }
end end
end end
def add_style(target, opts = nil) def add_style(target, opts = nil)
if styles = Stylesheet::Manager.new(theme_id: theme_id).stylesheet_details(target, 'all') if styles = Stylesheet::Manager.new(theme_id: theme_id).stylesheet_details(target, "all")
styles.each do |style| styles.each do |style|
@stylesheets << { @stylesheets << {
href: style[:new_href], href: style[:new_href],
media: 'all', media: "all",
theme_id: style[:theme_id], theme_id: style[:theme_id],
target: style[:target] target: style[:target],
}.merge(opts || {}) }.merge(opts || {})
end end
end end
@ -150,7 +154,11 @@ private
end end
def add_plugin_html(html, key) def add_plugin_html(html, key)
add_if_present(html, key, DiscoursePluginRegistry.build_html("server:#{key.to_s.dasherize}", self)) add_if_present(
html,
key,
DiscoursePluginRegistry.build_html("server:#{key.to_s.dasherize}", self),
)
end end
def create_theme_html def create_theme_html
@ -159,10 +167,14 @@ private
theme_view = mobile_view? ? :mobile : :desktop theme_view = mobile_view? ? :mobile : :desktop
add_if_present(theme_html, :body_tag, Theme.lookup_field(theme_id, theme_view, 'body_tag')) add_if_present(theme_html, :body_tag, Theme.lookup_field(theme_id, theme_view, "body_tag"))
add_if_present(theme_html, :head_tag, Theme.lookup_field(theme_id, theme_view, 'head_tag')) add_if_present(theme_html, :head_tag, Theme.lookup_field(theme_id, theme_view, "head_tag"))
add_if_present(theme_html, :header, Theme.lookup_field(theme_id, theme_view, 'header')) add_if_present(theme_html, :header, Theme.lookup_field(theme_id, theme_view, "header"))
add_if_present(theme_html, :translations, Theme.lookup_field(theme_id, :translations, I18n.locale)) add_if_present(
theme_html,
:translations,
Theme.lookup_field(theme_id, :translations, I18n.locale),
)
add_if_present(theme_html, :js, Theme.lookup_field(theme_id, :extra_js, nil)) add_if_present(theme_html, :js, Theme.lookup_field(theme_id, :extra_js, nil))
theme_html theme_html
@ -171,5 +183,4 @@ private
def add_if_present(hash, key, val) def add_if_present(hash, key, val)
hash[key] = val if val.present? hash[key] = val if val.present?
end end
end end

View File

@ -3,11 +3,19 @@
class CategoriesController < ApplicationController class CategoriesController < ApplicationController
include TopicQueryParams include TopicQueryParams
requires_login except: [:index, :categories_and_latest, :categories_and_top, :show, :redirect, :find_by_slug, :visible_groups] requires_login except: %i[
index
categories_and_latest
categories_and_top
show
redirect
find_by_slug
visible_groups
]
before_action :fetch_category, only: [:show, :update, :destroy, :visible_groups] before_action :fetch_category, only: %i[show update destroy visible_groups]
before_action :initialize_staff_action_logger, only: [:create, :update, :destroy] before_action :initialize_staff_action_logger, only: %i[create update destroy]
skip_before_action :check_xhr, only: [:index, :categories_and_latest, :categories_and_top, :redirect] skip_before_action :check_xhr, only: %i[index categories_and_latest categories_and_top redirect]
SYMMETRICAL_CATEGORIES_TO_TOPICS_FACTOR = 1.5 SYMMETRICAL_CATEGORIES_TO_TOPICS_FACTOR = 1.5
MIN_CATEGORIES_TOPICS = 5 MIN_CATEGORIES_TOPICS = 5
@ -22,17 +30,20 @@ class CategoriesController < ApplicationController
@description = SiteSetting.site_description @description = SiteSetting.site_description
parent_category = Category.find_by_slug(params[:parent_category_id]) || Category.find_by(id: params[:parent_category_id].to_i) parent_category =
Category.find_by_slug(params[:parent_category_id]) ||
Category.find_by(id: params[:parent_category_id].to_i)
include_subcategories = SiteSetting.desktop_category_page_style == "subcategories_with_featured_topics" || include_subcategories =
params[:include_subcategories] == "true" SiteSetting.desktop_category_page_style == "subcategories_with_featured_topics" ||
params[:include_subcategories] == "true"
category_options = { category_options = {
is_homepage: current_homepage == "categories", is_homepage: current_homepage == "categories",
parent_category_id: params[:parent_category_id], parent_category_id: params[:parent_category_id],
include_topics: include_topics(parent_category), include_topics: include_topics(parent_category),
include_subcategories: include_subcategories, include_subcategories: include_subcategories,
tag: params[:tag] tag: params[:tag],
} }
@category_list = CategoryList.new(guardian, category_options) @category_list = CategoryList.new(guardian, category_options)
@ -40,35 +51,38 @@ class CategoriesController < ApplicationController
if category_options[:is_homepage] && SiteSetting.short_site_description.present? if category_options[:is_homepage] && SiteSetting.short_site_description.present?
@title = "#{SiteSetting.title} - #{SiteSetting.short_site_description}" @title = "#{SiteSetting.title} - #{SiteSetting.short_site_description}"
elsif !category_options[:is_homepage] elsif !category_options[:is_homepage]
@title = "#{I18n.t('js.filters.categories.title')} - #{SiteSetting.title}" @title = "#{I18n.t("js.filters.categories.title")} - #{SiteSetting.title}"
end end
respond_to do |format| respond_to do |format|
format.html do format.html do
store_preloaded(@category_list.preload_key, MultiJson.dump(CategoryListSerializer.new(@category_list, scope: guardian))) store_preloaded(
@category_list.preload_key,
MultiJson.dump(CategoryListSerializer.new(@category_list, scope: guardian)),
)
style = SiteSetting.desktop_category_page_style style = SiteSetting.desktop_category_page_style
topic_options = { topic_options = { per_page: CategoriesController.topics_per_page, no_definitions: true }
per_page: CategoriesController.topics_per_page,
no_definitions: true,
}
if style == "categories_and_latest_topics_created_date" if style == "categories_and_latest_topics_created_date"
topic_options[:order] = 'created' topic_options[:order] = "created"
@topic_list = TopicQuery.new(current_user, topic_options).list_latest @topic_list = TopicQuery.new(current_user, topic_options).list_latest
@topic_list.more_topics_url = url_for(public_send("latest_path", sort: :created)) @topic_list.more_topics_url = url_for(public_send("latest_path", sort: :created))
elsif style == "categories_and_latest_topics" elsif style == "categories_and_latest_topics"
@topic_list = TopicQuery.new(current_user, topic_options).list_latest @topic_list = TopicQuery.new(current_user, topic_options).list_latest
@topic_list.more_topics_url = url_for(public_send("latest_path")) @topic_list.more_topics_url = url_for(public_send("latest_path"))
elsif style == "categories_and_top_topics" elsif style == "categories_and_top_topics"
@topic_list = TopicQuery.new(current_user, topic_options).list_top_for(SiteSetting.top_page_default_timeframe.to_sym) @topic_list =
TopicQuery.new(current_user, topic_options).list_top_for(
SiteSetting.top_page_default_timeframe.to_sym,
)
@topic_list.more_topics_url = url_for(public_send("top_path")) @topic_list.more_topics_url = url_for(public_send("top_path"))
end end
if @topic_list.present? && @topic_list.topics.present? if @topic_list.present? && @topic_list.topics.present?
store_preloaded( store_preloaded(
@topic_list.preload_key, @topic_list.preload_key,
MultiJson.dump(TopicListSerializer.new(@topic_list, scope: guardian)) MultiJson.dump(TopicListSerializer.new(@topic_list, scope: guardian)),
) )
end end
@ -109,7 +123,9 @@ class CategoriesController < ApplicationController
by_category = Hash[change_requests.map { |cat, pos| [Category.find(cat.to_i), pos] }] by_category = Hash[change_requests.map { |cat, pos| [Category.find(cat.to_i), pos] }]
unless guardian.is_admin? unless guardian.is_admin?
raise Discourse::InvalidAccess unless by_category.keys.all? { |c| guardian.can_see_category? c } unless by_category.keys.all? { |c| guardian.can_see_category? c }
raise Discourse::InvalidAccess
end
end end
by_category.each do |cat, pos| by_category.each do |cat, pos|
@ -187,14 +203,12 @@ class CategoriesController < ApplicationController
@category, @category,
old_category_params, old_category_params,
old_permissions: old_permissions, old_permissions: old_permissions,
old_custom_fields: old_custom_fields old_custom_fields: old_custom_fields,
) )
end end
end end
if result DiscourseEvent.trigger(:category_updated, cat) if result
DiscourseEvent.trigger(:category_updated, cat)
end
result result
end end
@ -207,7 +221,7 @@ class CategoriesController < ApplicationController
custom_slug = params[:slug].to_s custom_slug = params[:slug].to_s
if custom_slug.blank? if custom_slug.blank?
error = @category.errors.full_message(:slug, I18n.t('errors.messages.blank')) error = @category.errors.full_message(:slug, I18n.t("errors.messages.blank"))
render_json_error(error) render_json_error(error)
elsif @category.update(slug: custom_slug) elsif @category.update(slug: custom_slug)
render json: success_json render json: success_json
@ -221,7 +235,13 @@ class CategoriesController < ApplicationController
notification_level = params[:notification_level].to_i notification_level = params[:notification_level].to_i
CategoryUser.set_notification_level_for_category(current_user, notification_level, category_id) CategoryUser.set_notification_level_for_category(current_user, notification_level, category_id)
render json: success_json.merge({ indirectly_muted_category_ids: CategoryUser.indirectly_muted_category_ids(current_user) }) render json:
success_json.merge(
{
indirectly_muted_category_ids:
CategoryUser.indirectly_muted_category_ids(current_user),
},
)
end end
def destroy def destroy
@ -237,34 +257,40 @@ class CategoriesController < ApplicationController
def find_by_slug def find_by_slug
params.require(:category_slug) params.require(:category_slug)
@category = Category.find_by_slug_path(params[:category_slug].split('/')) @category = Category.find_by_slug_path(params[:category_slug].split("/"))
raise Discourse::NotFound unless @category.present? raise Discourse::NotFound unless @category.present?
if !guardian.can_see?(@category) if !guardian.can_see?(@category)
if SiteSetting.detailed_404 && group = @category.access_category_via_group if SiteSetting.detailed_404 && group = @category.access_category_via_group
raise Discourse::InvalidAccess.new( raise Discourse::InvalidAccess.new(
'not in group', "not in group",
@category, @category,
custom_message: 'not_in_group.title_category', custom_message: "not_in_group.title_category",
custom_message_params: { group: group.name }, custom_message_params: {
group: group group: group.name,
) },
group: group,
)
else else
raise Discourse::NotFound raise Discourse::NotFound
end end
end end
@category.permission = CategoryGroup.permission_types[:full] if Category.topic_create_allowed(guardian).where(id: @category.id).exists? @category.permission = CategoryGroup.permission_types[:full] if Category
.topic_create_allowed(guardian)
.where(id: @category.id)
.exists?
render_serialized(@category, CategorySerializer) render_serialized(@category, CategorySerializer)
end end
def visible_groups def visible_groups
@guardian.ensure_can_see!(@category) @guardian.ensure_can_see!(@category)
groups = if !@category.groups.exists?(id: Group::AUTO_GROUPS[:everyone]) groups =
@category.groups.merge(Group.visible_groups(current_user)).pluck("name") if !@category.groups.exists?(id: Group::AUTO_GROUPS[:everyone])
end @category.groups.merge(Group.visible_groups(current_user)).pluck("name")
end
render json: success_json.merge(groups: groups || []) render json: success_json.merge(groups: groups || [])
end end
@ -285,17 +311,14 @@ class CategoriesController < ApplicationController
category_options = { category_options = {
is_homepage: current_homepage == "categories", is_homepage: current_homepage == "categories",
parent_category_id: params[:parent_category_id], parent_category_id: params[:parent_category_id],
include_topics: false include_topics: false,
} }
topic_options = { topic_options = { per_page: CategoriesController.topics_per_page, no_definitions: true }
per_page: CategoriesController.topics_per_page,
no_definitions: true,
}
topic_options.merge!(build_topic_list_options) topic_options.merge!(build_topic_list_options)
style = SiteSetting.desktop_category_page_style style = SiteSetting.desktop_category_page_style
topic_options[:order] = 'created' if style == "categories_and_latest_topics_created_date" topic_options[:order] = "created" if style == "categories_and_latest_topics_created_date"
result = CategoryAndTopicLists.new result = CategoryAndTopicLists.new
result.category_list = CategoryList.new(guardian, category_options) result.category_list = CategoryList.new(guardian, category_options)
@ -303,9 +326,10 @@ class CategoriesController < ApplicationController
if topics_filter == :latest if topics_filter == :latest
result.topic_list = TopicQuery.new(current_user, topic_options).list_latest result.topic_list = TopicQuery.new(current_user, topic_options).list_latest
elsif topics_filter == :top elsif topics_filter == :top
result.topic_list = TopicQuery.new(current_user, topic_options).list_top_for( result.topic_list =
SiteSetting.top_page_default_timeframe.to_sym TopicQuery.new(current_user, topic_options).list_top_for(
) SiteSetting.top_page_default_timeframe.to_sym,
)
end end
render_serialized(result, CategoryAndTopicListsSerializer, root: false) render_serialized(result, CategoryAndTopicListsSerializer, root: false)
@ -316,88 +340,90 @@ class CategoriesController < ApplicationController
end end
def required_create_params def required_create_params
required_param_keys.each do |key| required_param_keys.each { |key| params.require(key) }
params.require(key)
end
category_params category_params
end end
def category_params def category_params
@category_params ||= begin @category_params ||=
if p = params[:permissions] begin
p.each do |k, v| if p = params[:permissions]
p[k] = v.to_i p.each { |k, v| p[k] = v.to_i }
end end
if SiteSetting.tagging_enabled
params[:allowed_tags] = params[:allowed_tags].presence || [] if params[:allowed_tags]
params[:allowed_tag_groups] = params[:allowed_tag_groups].presence || [] if params[
:allowed_tag_groups
]
params[:required_tag_groups] = params[:required_tag_groups].presence || [] if params[
:required_tag_groups
]
end
if SiteSetting.enable_category_group_moderation?
params[:reviewable_by_group_id] = Group.where(
name: params[:reviewable_by_group_name],
).pluck_first(:id) if params[:reviewable_by_group_name]
end
result =
params.permit(
*required_param_keys,
:position,
:name,
:color,
:text_color,
:email_in,
:email_in_allow_strangers,
:mailinglist_mirror,
:all_topics_wiki,
:allow_unlimited_owner_edits_on_first_post,
:default_slow_mode_seconds,
:parent_category_id,
:auto_close_hours,
:auto_close_based_on_last_post,
:uploaded_logo_id,
:uploaded_logo_dark_id,
:uploaded_background_id,
:slug,
:allow_badges,
:topic_template,
:sort_order,
:sort_ascending,
:topic_featured_link_allowed,
:show_subcategory_list,
:num_featured_topics,
:default_view,
:subcategory_list_style,
:default_top_period,
:minimum_required_tags,
:navigate_to_first_post_after_read,
:search_priority,
:allow_global_tags,
:read_only_banner,
:default_list_filter,
:reviewable_by_group_id,
custom_fields: [custom_field_params],
permissions: [*p.try(:keys)],
allowed_tags: [],
allowed_tag_groups: [],
required_tag_groups: %i[name min_count],
)
if result[:required_tag_groups] && !result[:required_tag_groups].is_a?(Array)
raise Discourse::InvalidParameters.new(:required_tag_groups)
end
result
end end
if SiteSetting.tagging_enabled
params[:allowed_tags] = params[:allowed_tags].presence || [] if params[:allowed_tags]
params[:allowed_tag_groups] = params[:allowed_tag_groups].presence || [] if params[:allowed_tag_groups]
params[:required_tag_groups] = params[:required_tag_groups].presence || [] if params[:required_tag_groups]
end
if SiteSetting.enable_category_group_moderation?
params[:reviewable_by_group_id] = Group.where(name: params[:reviewable_by_group_name]).pluck_first(:id) if params[:reviewable_by_group_name]
end
result = params.permit(
*required_param_keys,
:position,
:name,
:color,
:text_color,
:email_in,
:email_in_allow_strangers,
:mailinglist_mirror,
:all_topics_wiki,
:allow_unlimited_owner_edits_on_first_post,
:default_slow_mode_seconds,
:parent_category_id,
:auto_close_hours,
:auto_close_based_on_last_post,
:uploaded_logo_id,
:uploaded_logo_dark_id,
:uploaded_background_id,
:slug,
:allow_badges,
:topic_template,
:sort_order,
:sort_ascending,
:topic_featured_link_allowed,
:show_subcategory_list,
:num_featured_topics,
:default_view,
:subcategory_list_style,
:default_top_period,
:minimum_required_tags,
:navigate_to_first_post_after_read,
:search_priority,
:allow_global_tags,
:read_only_banner,
:default_list_filter,
:reviewable_by_group_id,
custom_fields: [custom_field_params],
permissions: [*p.try(:keys)],
allowed_tags: [],
allowed_tag_groups: [],
required_tag_groups: [:name, :min_count]
)
if result[:required_tag_groups] && !result[:required_tag_groups].is_a?(Array)
raise Discourse::InvalidParameters.new(:required_tag_groups)
end
result
end
end end
def custom_field_params def custom_field_params
keys = params[:custom_fields].try(:keys) keys = params[:custom_fields].try(:keys)
return if keys.blank? return if keys.blank?
keys.map do |key| keys.map { |key| params[:custom_fields][key].is_a?(Array) ? { key => [] } : key }
params[:custom_fields][key].is_a?(Array) ? { key => [] } : key
end
end end
def fetch_category def fetch_category
@ -411,12 +437,9 @@ class CategoriesController < ApplicationController
def include_topics(parent_category = nil) def include_topics(parent_category = nil)
style = SiteSetting.desktop_category_page_style style = SiteSetting.desktop_category_page_style
view_context.mobile_view? || view_context.mobile_view? || params[:include_topics] ||
params[:include_topics] ||
(parent_category && parent_category.subcategory_list_includes_topics?) || (parent_category && parent_category.subcategory_list_includes_topics?) ||
style == "categories_with_featured_topics" || style == "categories_with_featured_topics" || style == "subcategories_with_featured_topics" ||
style == "subcategories_with_featured_topics" || style == "categories_boxes_with_topics" || style == "categories_with_top_topics"
style == "categories_boxes_with_topics" ||
style == "categories_with_top_topics"
end end
end end

View File

@ -4,17 +4,16 @@ class ClicksController < ApplicationController
skip_before_action :check_xhr, :preload_json, :verify_authenticity_token skip_before_action :check_xhr, :preload_json, :verify_authenticity_token
def track def track
params.require([:url, :post_id, :topic_id]) params.require(%i[url post_id topic_id])
TopicLinkClick.create_from( TopicLinkClick.create_from(
url: params[:url], url: params[:url],
post_id: params[:post_id], post_id: params[:post_id],
topic_id: params[:topic_id], topic_id: params[:topic_id],
ip: request.remote_ip, ip: request.remote_ip,
user_id: current_user&.id user_id: current_user&.id,
) )
render json: success_json render json: success_json
end end
end end

View File

@ -13,12 +13,13 @@ class ComposerController < ApplicationController
end end
# allowed_names is necessary just for new private messages. # allowed_names is necessary just for new private messages.
@allowed_names = if params[:allowed_names].present? @allowed_names =
raise Discourse::InvalidParameters(:allowed_names) if !params[:allowed_names].is_a?(Array) if params[:allowed_names].present?
params[:allowed_names] << current_user.username raise Discourse.InvalidParameters(:allowed_names) if !params[:allowed_names].is_a?(Array)
else params[:allowed_names] << current_user.username
[] else
end []
end
user_reasons = {} user_reasons = {}
group_reasons = {} group_reasons = {}
@ -33,64 +34,73 @@ class ComposerController < ApplicationController
end end
if @topic && @names.include?(SiteSetting.here_mention) && guardian.can_mention_here? if @topic && @names.include?(SiteSetting.here_mention) && guardian.can_mention_here?
here_count = PostAlerter.new.expand_here_mention(@topic.first_post, exclude_ids: [current_user.id]).size here_count =
PostAlerter.new.expand_here_mention(@topic.first_post, exclude_ids: [current_user.id]).size
end end
serialized_groups = groups.values.reduce({}) do |hash, group| serialized_groups =
serialized_group = { user_count: group.user_count } groups
.values
.reduce({}) do |hash, group|
serialized_group = { user_count: group.user_count }
if group_reasons[group.name] == :not_allowed && if group_reasons[group.name] == :not_allowed &&
members_visible_group_ids.include?(group.id) && members_visible_group_ids.include?(group.id) &&
(@topic&.private_message? || @allowed_names.present?) (@topic&.private_message? || @allowed_names.present?)
# Find users that are notified already because they have been invited
# directly or via a group
notified_count =
GroupUser
# invited directly
.where(user_id: topic_allowed_user_ids)
.or(
# invited via a group
GroupUser.where(
user_id: GroupUser.where(group_id: topic_allowed_group_ids).select(:user_id),
),
)
.where(group_id: group.id)
.select(:user_id)
.distinct
.count
# Find users that are notified already because they have been invited if notified_count > 0
# directly or via a group group_reasons[group.name] = :some_not_allowed
notified_count = GroupUser serialized_group[:notified_count] = notified_count
# invited directly end
.where(user_id: topic_allowed_user_ids) end
.or(
# invited via a group
GroupUser.where(
user_id: GroupUser.where(group_id: topic_allowed_group_ids).select(:user_id)
)
)
.where(group_id: group.id)
.select(:user_id).distinct.count
if notified_count > 0 hash[group.name] = serialized_group
group_reasons[group.name] = :some_not_allowed hash
serialized_group[:notified_count] = notified_count
end end
end
hash[group.name] = serialized_group
hash
end
render json: { render json: {
users: users.keys, users: users.keys,
user_reasons: user_reasons, user_reasons: user_reasons,
groups: serialized_groups, groups: serialized_groups,
group_reasons: group_reasons, group_reasons: group_reasons,
here_count: here_count, here_count: here_count,
max_users_notified_per_group_mention: SiteSetting.max_users_notified_per_group_mention, max_users_notified_per_group_mention: SiteSetting.max_users_notified_per_group_mention,
} }
end end
private private
def user_reason(user) def user_reason(user)
reason = if @topic && !user.guardian.can_see?(@topic) reason =
@topic.private_message? ? :private : :category if @topic && !user.guardian.can_see?(@topic)
elsif @allowed_names.present? && !is_user_allowed?(user, topic_allowed_user_ids, topic_allowed_group_ids) @topic.private_message? ? :private : :category
# This would normally be handled by the previous if, but that does not work for new private messages. elsif @allowed_names.present? &&
:private !is_user_allowed?(user, topic_allowed_user_ids, topic_allowed_group_ids)
elsif topic_muted_by.include?(user.id) # This would normally be handled by the previous if, but that does not work for new private messages.
:muted_topic :private
elsif @topic&.private_message? && !is_user_allowed?(user, topic_allowed_user_ids, topic_allowed_group_ids) elsif topic_muted_by.include?(user.id)
# Admins can see the topic, but they will not be mentioned if they were not invited. :muted_topic
:not_allowed elsif @topic&.private_message? &&
end !is_user_allowed?(user, topic_allowed_user_ids, topic_allowed_group_ids)
# Admins can see the topic, but they will not be mentioned if they were not invited.
:not_allowed
end
# Regular users can see only basic information why the users cannot see the topic. # Regular users can see only basic information why the users cannot see the topic.
reason = nil if !guardian.is_staff? && reason != :private && reason != :category reason = nil if !guardian.is_staff? && reason != :private && reason != :category
@ -101,7 +111,8 @@ class ComposerController < ApplicationController
def group_reason(group) def group_reason(group)
if !mentionable_group_ids.include?(group.id) if !mentionable_group_ids.include?(group.id)
:not_mentionable :not_mentionable
elsif (@topic&.private_message? || @allowed_names.present?) && !topic_allowed_group_ids.include?(group.id) elsif (@topic&.private_message? || @allowed_names.present?) &&
!topic_allowed_group_ids.include?(group.id)
:not_allowed :not_allowed
end end
end end
@ -111,74 +122,57 @@ class ComposerController < ApplicationController
end end
def users def users
@users ||= User @users ||=
.not_staged User.not_staged.where(username_lower: @names.map(&:downcase)).index_by(&:username_lower)
.where(username_lower: @names.map(&:downcase))
.index_by(&:username_lower)
end end
def groups def groups
@groups ||= Group @groups ||=
.visible_groups(current_user) Group
.where('lower(name) IN (?)', @names.map(&:downcase)) .visible_groups(current_user)
.index_by(&:name) .where("lower(name) IN (?)", @names.map(&:downcase))
.index_by(&:name)
end end
def mentionable_group_ids def mentionable_group_ids
@mentionable_group_ids ||= Group @mentionable_group_ids ||=
.mentionable(current_user, include_public: false) Group.mentionable(current_user, include_public: false).where(name: @names).pluck(:id).to_set
.where(name: @names)
.pluck(:id)
.to_set
end end
def members_visible_group_ids def members_visible_group_ids
@members_visible_group_ids ||= Group @members_visible_group_ids ||=
.members_visible_groups(current_user) Group.members_visible_groups(current_user).where(name: @names).pluck(:id).to_set
.where(name: @names)
.pluck(:id)
.to_set
end end
def topic_muted_by def topic_muted_by
@topic_muted_by ||= if @topic.present? @topic_muted_by ||=
TopicUser if @topic.present?
.where(topic: @topic) TopicUser
.where(user_id: users.values.map(&:id)) .where(topic: @topic)
.where(notification_level: TopicUser.notification_levels[:muted]) .where(user_id: users.values.map(&:id))
.pluck(:user_id) .where(notification_level: TopicUser.notification_levels[:muted])
.to_set .pluck(:user_id)
else .to_set
Set.new else
end Set.new
end
end end
def topic_allowed_user_ids def topic_allowed_user_ids
@topic_allowed_user_ids ||= if @allowed_names.present? @topic_allowed_user_ids ||=
User if @allowed_names.present?
.where(username_lower: @allowed_names.map(&:downcase)) User.where(username_lower: @allowed_names.map(&:downcase)).pluck(:id).to_set
.pluck(:id) elsif @topic&.private_message?
.to_set TopicAllowedUser.where(topic: @topic).pluck(:user_id).to_set
elsif @topic&.private_message? end
TopicAllowedUser
.where(topic: @topic)
.pluck(:user_id)
.to_set
end
end end
def topic_allowed_group_ids def topic_allowed_group_ids
@topic_allowed_group_ids ||= if @allowed_names.present? @topic_allowed_group_ids ||=
Group if @allowed_names.present?
.messageable(current_user) Group.messageable(current_user).where(name: @allowed_names).pluck(:id).to_set
.where(name: @allowed_names) elsif @topic&.private_message?
.pluck(:id) TopicAllowedGroup.where(topic: @topic).pluck(:group_id).to_set
.to_set end
elsif @topic&.private_message?
TopicAllowedGroup
.where(topic: @topic)
.pluck(:group_id)
.to_set
end
end end
end end

View File

@ -1,11 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
class ComposerMessagesController < ApplicationController class ComposerMessagesController < ApplicationController
requires_login requires_login
def index def index
finder = ComposerMessagesFinder.new(current_user, params.slice(:composer_action, :topic_id, :post_id)) finder =
ComposerMessagesFinder.new(current_user, params.slice(:composer_action, :topic_id, :post_id))
json = { composer_messages: [finder.find].compact } json = { composer_messages: [finder.find].compact }
if params[:topic_id].present? if params[:topic_id].present?
@ -25,14 +25,24 @@ class ComposerMessagesController < ApplicationController
warning_message = nil warning_message = nil
if user_count > 0 if user_count > 0
message_locale = if user_count == 1 message_locale =
"education.user_not_seen_in_a_while.single" if user_count == 1
else "education.user_not_seen_in_a_while.single"
"education.user_not_seen_in_a_while.multiple" else
end "education.user_not_seen_in_a_while.multiple"
end
end end
json = { user_count: user_count, usernames: users, time_ago: FreedomPatches::Rails4.time_ago_in_words(SiteSetting.pm_warn_user_last_seen_months_ago.month.ago, true, scope: :'datetime.distance_in_words_verbose') } json = {
user_count: user_count,
usernames: users,
time_ago:
FreedomPatches::Rails4.time_ago_in_words(
SiteSetting.pm_warn_user_last_seen_months_ago.month.ago,
true,
scope: :"datetime.distance_in_words_verbose",
),
}
render_json_dump(json) render_json_dump(json)
end end
end end

View File

@ -10,12 +10,11 @@ class CspReportsController < ApplicationController
if report.blank? if report.blank?
render_json_error("empty CSP report", status: 422) render_json_error("empty CSP report", status: 422)
else else
Logster.add_to_env(request.env, 'CSP Report', report) Logster.add_to_env(request.env, "CSP Report", report)
Rails.logger.warn("CSP Violation: '#{report['blocked-uri']}' \n\n#{report['script-sample']}") Rails.logger.warn("CSP Violation: '#{report["blocked-uri"]}' \n\n#{report["script-sample"]}")
head :ok head :ok
end end
rescue JSON::ParserError rescue JSON::ParserError
render_json_error("invalid CSP report", status: 422) render_json_error("invalid CSP report", status: 422)
end end
@ -25,20 +24,20 @@ class CspReportsController < ApplicationController
def parse_report def parse_report
obj = JSON.parse(request.body.read) obj = JSON.parse(request.body.read)
if Hash === obj if Hash === obj
obj = obj['csp-report'] obj = obj["csp-report"]
if Hash === obj if Hash === obj
obj.slice( obj.slice(
'blocked-uri', "blocked-uri",
'disposition', "disposition",
'document-uri', "document-uri",
'effective-directive', "effective-directive",
'original-policy', "original-policy",
'referrer', "referrer",
'script-sample', "script-sample",
'status-code', "status-code",
'violated-directive', "violated-directive",
'line-number', "line-number",
'source-file' "source-file",
) )
end end
end end

View File

@ -3,6 +3,8 @@
class DirectoryColumnsController < ApplicationController class DirectoryColumnsController < ApplicationController
def index def index
directory_columns = DirectoryColumn.includes(:user_field).where(enabled: true).order(:position) directory_columns = DirectoryColumn.includes(:user_field).where(enabled: true).order(:position)
render_json_dump(directory_columns: serialize_data(directory_columns, DirectoryColumnSerializer)) render_json_dump(
directory_columns: serialize_data(directory_columns, DirectoryColumnSerializer),
)
end end
end end

View File

@ -4,7 +4,9 @@ class DirectoryItemsController < ApplicationController
PAGE_SIZE = 50 PAGE_SIZE = 50
def index def index
raise Discourse::InvalidAccess.new(:enable_user_directory) unless SiteSetting.enable_user_directory? unless SiteSetting.enable_user_directory?
raise Discourse::InvalidAccess.new(:enable_user_directory)
end
period = params.require(:period) period = params.require(:period)
period_type = DirectoryItem.period_types[period.to_sym] period_type = DirectoryItem.period_types[period.to_sym]
@ -23,30 +25,36 @@ class DirectoryItemsController < ApplicationController
end end
if params[:exclude_usernames] if params[:exclude_usernames]
result = result.references(:user).where.not(users: { username: params[:exclude_usernames].split(",") }) result =
result
.references(:user)
.where.not(users: { username: params[:exclude_usernames].split(",") })
end end
order = params[:order] || DirectoryColumn.automatic_column_names.first order = params[:order] || DirectoryColumn.automatic_column_names.first
dir = params[:asc] ? 'ASC' : 'DESC' dir = params[:asc] ? "ASC" : "DESC"
active_directory_column_names = DirectoryColumn.active_column_names active_directory_column_names = DirectoryColumn.active_column_names
if active_directory_column_names.include?(order.to_sym) if active_directory_column_names.include?(order.to_sym)
result = result.order("directory_items.#{order} #{dir}, directory_items.id") result = result.order("directory_items.#{order} #{dir}, directory_items.id")
elsif params[:order] === 'username' elsif params[:order] === "username"
result = result.order("users.#{order} #{dir}, directory_items.id") result = result.order("users.#{order} #{dir}, directory_items.id")
else else
# Ordering by user field value # Ordering by user field value
user_field = UserField.find_by(name: params[:order]) user_field = UserField.find_by(name: params[:order])
if user_field if user_field
result = result result =
.references(:user) result
.joins("LEFT OUTER JOIN user_custom_fields ON user_custom_fields.user_id = users.id AND user_custom_fields.name = 'user_field_#{user_field.id}'") .references(:user)
.order("user_custom_fields.name = 'user_field_#{user_field.id}' ASC, user_custom_fields.value #{dir}") .joins(
"LEFT OUTER JOIN user_custom_fields ON user_custom_fields.user_id = users.id AND user_custom_fields.name = 'user_field_#{user_field.id}'",
)
.order(
"user_custom_fields.name = 'user_field_#{user_field.id}' ASC, user_custom_fields.value #{dir}",
)
end end
end end
if period_type == DirectoryItem.period_types[:all] result = result.includes(:user_stat) if period_type == DirectoryItem.period_types[:all]
result = result.includes(:user_stat)
end
page = params[:page].to_i page = params[:page].to_i
user_ids = nil user_ids = nil
@ -54,12 +62,10 @@ class DirectoryItemsController < ApplicationController
user_ids = UserSearch.new(params[:name], include_staged_users: true).search.pluck(:id) user_ids = UserSearch.new(params[:name], include_staged_users: true).search.pluck(:id)
if user_ids.present? if user_ids.present?
# Add the current user if we have at least one other match # Add the current user if we have at least one other match
if current_user && result.dup.where(user_id: user_ids).exists? user_ids << current_user.id if current_user && result.dup.where(user_id: user_ids).exists?
user_ids << current_user.id
end
result = result.where(user_id: user_ids) result = result.where(user_id: user_ids)
else else
result = result.where('false') result = result.where("false")
end end
end end
@ -68,7 +74,7 @@ class DirectoryItemsController < ApplicationController
if user_id if user_id
result = result.where(user_id: user_id) result = result.where(user_id: user_id)
else else
result = result.where('false') result = result.where("false")
end end
end end
@ -82,7 +88,6 @@ class DirectoryItemsController < ApplicationController
# Put yourself at the top of the first page # Put yourself at the top of the first page
if result.present? && current_user.present? && page == 0 && !params[:group].present? if result.present? && current_user.present? && page == 0 && !params[:group].present?
position = result.index { |r| r.user_id == current_user.id } position = result.index { |r| r.user_id == current_user.id }
# Don't show the record unless you're not in the top positions already # Don't show the record unless you're not in the top positions already
@ -90,7 +95,6 @@ class DirectoryItemsController < ApplicationController
your_item = DirectoryItem.where(period_type: period_type, user_id: current_user.id).first your_item = DirectoryItem.where(period_type: period_type, user_id: current_user.id).first
result.insert(0, your_item) if your_item result.insert(0, your_item) if your_item
end end
end end
last_updated_at = DirectoryItem.last_updated_at(period_type) last_updated_at = DirectoryItem.last_updated_at(period_type)
@ -101,7 +105,9 @@ class DirectoryItemsController < ApplicationController
user_field_ids = params[:user_field_ids]&.split("|")&.map(&:to_i) user_field_ids = params[:user_field_ids]&.split("|")&.map(&:to_i)
user_field_ids.each do |user_field_id| user_field_ids.each do |user_field_id|
serializer_opts[:user_custom_field_map]["#{User::USER_FIELD_PREFIX}#{user_field_id}"] = user_field_id serializer_opts[:user_custom_field_map][
"#{User::USER_FIELD_PREFIX}#{user_field_id}"
] = user_field_id
end end
end end
@ -112,12 +118,13 @@ class DirectoryItemsController < ApplicationController
serializer_opts[:attributes] = active_directory_column_names serializer_opts[:attributes] = active_directory_column_names
serialized = serialize_data(result, DirectoryItemSerializer, serializer_opts) serialized = serialize_data(result, DirectoryItemSerializer, serializer_opts)
render_json_dump(directory_items: serialized, render_json_dump(
meta: { directory_items: serialized,
last_updated_at: last_updated_at, meta: {
total_rows_directory_items: result_count, last_updated_at: last_updated_at,
load_more_directory_items: load_more_directory_items_json total_rows_directory_items: result_count,
} load_more_directory_items: load_more_directory_items_json,
) },
)
end end
end end

View File

@ -6,11 +6,23 @@ class DoNotDisturbController < ApplicationController
def create def create
raise Discourse::InvalidParameters.new(:duration) if params[:duration].blank? raise Discourse::InvalidParameters.new(:duration) if params[:duration].blank?
duration_minutes = (Integer(params[:duration]) rescue false) duration_minutes =
(
begin
Integer(params[:duration])
rescue StandardError
false
end
)
ends_at = duration_minutes ? ends_at =
ends_at_from_minutes(duration_minutes) : (
ends_at_from_string(params[:duration]) if duration_minutes
ends_at_from_minutes(duration_minutes)
else
ends_at_from_string(params[:duration])
end
)
new_timing = current_user.do_not_disturb_timings.new(starts_at: Time.zone.now, ends_at: ends_at) new_timing = current_user.do_not_disturb_timings.new(starts_at: Time.zone.now, ends_at: ends_at)
@ -37,7 +49,7 @@ class DoNotDisturbController < ApplicationController
end end
def ends_at_from_string(string) def ends_at_from_string(string)
if string == 'tomorrow' if string == "tomorrow"
Time.now.end_of_day.utc Time.now.end_of_day.utc
else else
raise Discourse::InvalidParameters.new(:duration) raise Discourse::InvalidParameters.new(:duration)

View File

@ -9,15 +9,9 @@ class DraftsController < ApplicationController
params.permit(:offset) params.permit(:offset)
params.permit(:limit) params.permit(:limit)
stream = Draft.stream( stream = Draft.stream(user: current_user, offset: params[:offset], limit: params[:limit])
user: current_user,
offset: params[:offset],
limit: params[:limit]
)
render json: { render json: { drafts: stream ? serialize_data(stream, DraftSerializer) : [] }
drafts: stream ? serialize_data(stream, DraftSerializer) : []
}
end end
def show def show
@ -38,10 +32,9 @@ class DraftsController < ApplicationController
params[:sequence].to_i, params[:sequence].to_i,
params[:data], params[:data],
params[:owner], params[:owner],
force_save: params[:force_save] force_save: params[:force_save],
) )
rescue Draft::OutOfSequence rescue Draft::OutOfSequence
begin begin
if !Draft.exists?(user_id: current_user.id, draft_key: params[:draft_key]) if !Draft.exists?(user_id: current_user.id, draft_key: params[:draft_key])
Draft.set( Draft.set(
@ -49,18 +42,17 @@ class DraftsController < ApplicationController
params[:draft_key], params[:draft_key],
DraftSequence.current(current_user, params[:draft_key]), DraftSequence.current(current_user, params[:draft_key]),
params[:data], params[:data],
params[:owner] params[:owner],
) )
else else
raise Draft::OutOfSequence raise Draft::OutOfSequence
end end
rescue Draft::OutOfSequence rescue Draft::OutOfSequence
render_json_error I18n.t('draft.sequence_conflict_error.title'), render_json_error I18n.t("draft.sequence_conflict_error.title"),
status: 409, status: 409,
extras: { extras: {
description: I18n.t('draft.sequence_conflict_error.description') description: I18n.t("draft.sequence_conflict_error.description"),
} }
return return
end end
end end
@ -68,7 +60,7 @@ class DraftsController < ApplicationController
json = success_json.merge(draft_sequence: sequence) json = success_json.merge(draft_sequence: sequence)
begin begin
data = JSON::parse(params[:data]) data = JSON.parse(params[:data])
rescue JSON::ParserError rescue JSON::ParserError
raise Discourse::InvalidParameters.new(:data) raise Discourse::InvalidParameters.new(:data)
end end
@ -76,7 +68,8 @@ class DraftsController < ApplicationController
if data.present? if data.present?
# this is a bit of a kludge we need to remove (all the parsing) too many special cases here # this is a bit of a kludge we need to remove (all the parsing) too many special cases here
# we need to catch action edit and action editSharedDraft # we need to catch action edit and action editSharedDraft
if data["postId"].present? && data["originalText"].present? && data["action"].to_s.start_with?("edit") if data["postId"].present? && data["originalText"].present? &&
data["action"].to_s.start_with?("edit")
post = Post.find_by(id: data["postId"]) post = Post.find_by(id: data["postId"])
if post && post.raw != data["originalText"] if post && post.raw != data["originalText"]
conflict_user = BasicUserSerializer.new(post.last_editor, root: false) conflict_user = BasicUserSerializer.new(post.last_editor, root: false)

View File

@ -18,14 +18,20 @@ class EditDirectoryColumnsController < ApplicationController
directory_column_params = params.permit(directory_columns: {}) directory_column_params = params.permit(directory_columns: {})
directory_columns = DirectoryColumn.all directory_columns = DirectoryColumn.all
has_enabled_column = directory_column_params[:directory_columns].values.any? do |column_data| has_enabled_column =
column_data[:enabled].to_s == "true" directory_column_params[:directory_columns].values.any? do |column_data|
column_data[:enabled].to_s == "true"
end
unless has_enabled_column
raise Discourse::InvalidParameters, "Must have at least one column enabled"
end end
raise Discourse::InvalidParameters, "Must have at least one column enabled" unless has_enabled_column
directory_column_params[:directory_columns].values.each do |column_data| directory_column_params[:directory_columns].values.each do |column_data|
existing_column = directory_columns.detect { |c| c.id == column_data[:id].to_i } existing_column = directory_columns.detect { |c| c.id == column_data[:id].to_i }
if (existing_column.enabled != column_data[:enabled] || existing_column.position != column_data[:position].to_i) if (
existing_column.enabled != column_data[:enabled] ||
existing_column.position != column_data[:position].to_i
)
existing_column.update(enabled: column_data[:enabled], position: column_data[:position]) existing_column.update(enabled: column_data[:enabled], position: column_data[:position])
end end
end end
@ -37,7 +43,8 @@ class EditDirectoryColumnsController < ApplicationController
def ensure_user_fields_have_columns def ensure_user_fields_have_columns
user_fields_without_column = user_fields_without_column =
UserField.left_outer_joins(:directory_column) UserField
.left_outer_joins(:directory_column)
.where(directory_column: { user_field_id: nil }) .where(directory_column: { user_field_id: nil })
.where("show_on_profile=? OR show_on_user_card=?", true, true) .where("show_on_profile=? OR show_on_user_card=?", true, true)
@ -47,12 +54,14 @@ class EditDirectoryColumnsController < ApplicationController
new_directory_column_attrs = [] new_directory_column_attrs = []
user_fields_without_column.each do |user_field| user_fields_without_column.each do |user_field|
new_directory_column_attrs.push({ new_directory_column_attrs.push(
user_field_id: user_field.id, {
enabled: false, user_field_id: user_field.id,
type: DirectoryColumn.types[:user_field], enabled: false,
position: next_position type: DirectoryColumn.types[:user_field],
}) position: next_position,
},
)
next_position += 1 next_position += 1
end end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class EmailController < ApplicationController class EmailController < ApplicationController
layout 'no_ember' layout "no_ember"
skip_before_action :check_xhr, :preload_json, :redirect_to_login_if_required skip_before_action :check_xhr, :preload_json, :redirect_to_login_if_required
@ -11,9 +11,7 @@ class EmailController < ApplicationController
@key_owner_found = key&.user.present? @key_owner_found = key&.user.present?
if @found && @key_owner_found if @found && @key_owner_found
UnsubscribeKey UnsubscribeKey.get_unsubscribe_strategy_for(key)&.prepare_unsubscribe_options(self)
.get_unsubscribe_strategy_for(key)
&.prepare_unsubscribe_options(self)
if current_user.present? && (@user != current_user) if current_user.present? && (@user != current_user)
@different_user = @user.name @different_user = @user.name
@ -28,17 +26,14 @@ class EmailController < ApplicationController
key = UnsubscribeKey.find_by(key: params[:key]) key = UnsubscribeKey.find_by(key: params[:key])
raise Discourse::NotFound if key.nil? || key.user.nil? raise Discourse::NotFound if key.nil? || key.user.nil?
user = key.user user = key.user
updated = UnsubscribeKey.get_unsubscribe_strategy_for(key) updated = UnsubscribeKey.get_unsubscribe_strategy_for(key)&.unsubscribe(params)
&.unsubscribe(params)
if updated if updated
cache_key = "unsub_#{SecureRandom.hex}" cache_key = "unsub_#{SecureRandom.hex}"
Discourse.cache.write cache_key, user.email, expires_in: 1.hour Discourse.cache.write cache_key, user.email, expires_in: 1.hour
url = path("/email/unsubscribed?key=#{cache_key}") url = path("/email/unsubscribed?key=#{cache_key}")
if key.associated_topic url += "&topic_id=#{key.associated_topic.id}" if key.associated_topic
url += "&topic_id=#{key.associated_topic.id}"
end
redirect_to url redirect_to url
else else

View File

@ -5,10 +5,10 @@ class EmbedController < ApplicationController
skip_before_action :check_xhr, :preload_json, :verify_authenticity_token skip_before_action :check_xhr, :preload_json, :verify_authenticity_token
before_action :prepare_embeddable, except: [ :info ] before_action :prepare_embeddable, except: [:info]
before_action :ensure_api_request, only: [ :info ] before_action :ensure_api_request, only: [:info]
layout 'embed' layout "embed"
rescue_from Discourse::InvalidAccess do rescue_from Discourse::InvalidAccess do
if current_user.try(:admin?) if current_user.try(:admin?)
@ -16,14 +16,14 @@ class EmbedController < ApplicationController
@show_reason = true @show_reason = true
@hosts = EmbeddableHost.all @hosts = EmbeddableHost.all
end end
render 'embed_error', status: 400 render "embed_error", status: 400
end end
def topics def topics
discourse_expires_in 1.minute discourse_expires_in 1.minute
unless SiteSetting.embed_topics_list? unless SiteSetting.embed_topics_list?
render 'embed_topics_error', status: 400 render "embed_topics_error", status: 400
return return
end end
@ -32,10 +32,12 @@ class EmbedController < ApplicationController
end end
if @embed_class = params[:embed_class] if @embed_class = params[:embed_class]
raise Discourse::InvalidParameters.new(:embed_class) unless @embed_class =~ /^[a-zA-Z0-9\-_]+$/ unless @embed_class =~ /^[a-zA-Z0-9\-_]+$/
raise Discourse::InvalidParameters.new(:embed_class)
end
end end
response.headers['X-Robots-Tag'] = 'noindex, indexifembedded' response.headers["X-Robots-Tag"] = "noindex, indexifembedded"
if params.has_key?(:template) && params[:template] == "complete" if params.has_key?(:template) && params[:template] == "complete"
@template = "complete" @template = "complete"
@ -46,8 +48,7 @@ class EmbedController < ApplicationController
list_options = build_topic_list_options list_options = build_topic_list_options
if params.has_key?(:per_page) if params.has_key?(:per_page)
list_options[:per_page] = list_options[:per_page] = [params[:per_page].to_i, SiteSetting.embed_topic_limit_per_page].min
[params[:per_page].to_i, SiteSetting.embed_topic_limit_per_page].min
end end
if params[:allow_create] if params[:allow_create]
@ -67,11 +68,12 @@ class EmbedController < ApplicationController
valid_top_period = false valid_top_period = false
end end
@list = if valid_top_period @list =
topic_query.list_top_for(top_period) if valid_top_period
else topic_query.list_top_for(top_period)
topic_query.list_latest else
end topic_query.list_latest
end
end end
def comments def comments
@ -80,7 +82,7 @@ class EmbedController < ApplicationController
embed_topic_id = params[:topic_id]&.to_i embed_topic_id = params[:topic_id]&.to_i
unless embed_topic_id || EmbeddableHost.url_allowed?(embed_url) unless embed_topic_id || EmbeddableHost.url_allowed?(embed_url)
raise Discourse::InvalidAccess.new('invalid embed host') raise Discourse::InvalidAccess.new("invalid embed host")
end end
topic_id = nil topic_id = nil
@ -91,28 +93,33 @@ class EmbedController < ApplicationController
end end
if topic_id if topic_id
@topic_view = TopicView.new(topic_id, @topic_view =
current_user, TopicView.new(
limit: SiteSetting.embed_post_limit, topic_id,
only_regular: true, current_user,
exclude_first: true, limit: SiteSetting.embed_post_limit,
exclude_deleted_users: true, only_regular: true,
exclude_hidden: true) exclude_first: true,
exclude_deleted_users: true,
exclude_hidden: true,
)
raise Discourse::NotFound if @topic_view.blank? raise Discourse::NotFound if @topic_view.blank?
@posts_left = 0 @posts_left = 0
@second_post_url = "#{@topic_view.topic.url}/2" @second_post_url = "#{@topic_view.topic.url}/2"
@reply_count = @topic_view.filtered_posts.count - 1 @reply_count = @topic_view.filtered_posts.count - 1
@reply_count = 0 if @reply_count < 0 @reply_count = 0 if @reply_count < 0
@posts_left = @reply_count - SiteSetting.embed_post_limit if @reply_count > SiteSetting.embed_post_limit @posts_left = @reply_count - SiteSetting.embed_post_limit if @reply_count >
SiteSetting.embed_post_limit
elsif embed_url.present? elsif embed_url.present?
Jobs.enqueue(:retrieve_topic, Jobs.enqueue(
user_id: current_user.try(:id), :retrieve_topic,
embed_url: embed_url, user_id: current_user.try(:id),
author_username: embed_username, embed_url: embed_url,
referer: request.env['HTTP_REFERER'] author_username: embed_username,
) referer: request.env["HTTP_REFERER"],
render 'loading' )
render "loading"
end end
discourse_expires_in 1.minute discourse_expires_in 1.minute
@ -132,16 +139,16 @@ class EmbedController < ApplicationController
by_url = {} by_url = {}
if embed_urls.present? if embed_urls.present?
urls = embed_urls.map { |u| u.sub(/#discourse-comments$/, '').sub(/\/$/, '') } urls = embed_urls.map { |u| u.sub(/#discourse-comments$/, "").sub(%r{/$}, "") }
topic_embeds = TopicEmbed.where(embed_url: urls).includes(:topic).references(:topic) topic_embeds = TopicEmbed.where(embed_url: urls).includes(:topic).references(:topic)
topic_embeds.each do |te| topic_embeds.each do |te|
url = te.embed_url url = te.embed_url
url = "#{url}#discourse-comments" unless params[:embed_url].include?(url) url = "#{url}#discourse-comments" unless params[:embed_url].include?(url)
if te.topic.present? if te.topic.present?
by_url[url] = I18n.t('embed.replies', count: te.topic.posts_count - 1) by_url[url] = I18n.t("embed.replies", count: te.topic.posts_count - 1)
else else
by_url[url] = I18n.t('embed.replies', count: 0) by_url[url] = I18n.t("embed.replies", count: 0)
end end
end end
end end
@ -152,16 +159,18 @@ class EmbedController < ApplicationController
private private
def prepare_embeddable def prepare_embeddable
response.headers.delete('X-Frame-Options') response.headers.delete("X-Frame-Options")
@embeddable_css_class = "" @embeddable_css_class = ""
embeddable_host = EmbeddableHost.record_for_url(request.referer) embeddable_host = EmbeddableHost.record_for_url(request.referer)
@embeddable_css_class = " class=\"#{embeddable_host.class_name}\"" if embeddable_host.present? && embeddable_host.class_name.present? @embeddable_css_class =
" class=\"#{embeddable_host.class_name}\"" if embeddable_host.present? &&
embeddable_host.class_name.present?
@data_referer = request.referer @data_referer = request.referer
@data_referer = '*' if SiteSetting.embed_any_origin? && @data_referer.blank? @data_referer = "*" if SiteSetting.embed_any_origin? && @data_referer.blank?
end end
def ensure_api_request def ensure_api_request
raise Discourse::InvalidAccess.new('api key not set') if !is_api? raise Discourse::InvalidAccess.new("api key not set") if !is_api?
end end
end end

View File

@ -12,5 +12,4 @@ class ExceptionsController < ApplicationController
def not_found_body def not_found_body
render html: build_not_found_page(status: 200) render html: build_not_found_page(status: 200)
end end
end end

View File

@ -1,16 +1,20 @@
# frozen_string_literal: true # frozen_string_literal: true
class ExportCsvController < ApplicationController class ExportCsvController < ApplicationController
skip_before_action :preload_json, :check_xhr, only: [:show] skip_before_action :preload_json, :check_xhr, only: [:show]
def export_entity def export_entity
guardian.ensure_can_export_entity!(export_params[:entity]) guardian.ensure_can_export_entity!(export_params[:entity])
if export_params[:entity] == 'user_archive' if export_params[:entity] == "user_archive"
Jobs.enqueue(:export_user_archive, user_id: current_user.id, args: export_params[:args]) Jobs.enqueue(:export_user_archive, user_id: current_user.id, args: export_params[:args])
else else
Jobs.enqueue(:export_csv_file, entity: export_params[:entity], user_id: current_user.id, args: export_params[:args]) Jobs.enqueue(
:export_csv_file,
entity: export_params[:entity],
user_id: current_user.id,
args: export_params[:args],
)
end end
StaffActionLogger.new(current_user).log_entity_export(export_params[:entity]) StaffActionLogger.new(current_user).log_entity_export(export_params[:entity])
render json: success_json render json: success_json
@ -21,9 +25,10 @@ class ExportCsvController < ApplicationController
private private
def export_params def export_params
@_export_params ||= begin @_export_params ||=
params.require(:entity) begin
params.permit(:entity, args: Report::FILTERS).to_h params.require(:entity)
end params.permit(:entity, args: Report::FILTERS).to_h
end
end end
end end

View File

@ -4,11 +4,11 @@ class ExtraLocalesController < ApplicationController
layout :false layout :false
skip_before_action :check_xhr, skip_before_action :check_xhr,
:preload_json, :preload_json,
:redirect_to_login_if_required, :redirect_to_login_if_required,
:verify_authenticity_token :verify_authenticity_token
OVERRIDES_BUNDLE ||= 'overrides' OVERRIDES_BUNDLE ||= "overrides"
MD5_HASH_LENGTH ||= 32 MD5_HASH_LENGTH ||= 32
def show def show

View File

@ -2,9 +2,9 @@
class FinishInstallationController < ApplicationController class FinishInstallationController < ApplicationController
skip_before_action :check_xhr, :preload_json, :redirect_to_login_if_required skip_before_action :check_xhr, :preload_json, :redirect_to_login_if_required
layout 'finish_installation' layout "finish_installation"
before_action :ensure_no_admins, except: ['confirm_email', 'resend_email'] before_action :ensure_no_admins, except: %w[confirm_email resend_email]
def index def index
end end
@ -61,7 +61,9 @@ class FinishInstallationController < ApplicationController
end end
def find_allowed_emails def find_allowed_emails
return [] unless GlobalSetting.respond_to?(:developer_emails) && GlobalSetting.developer_emails.present? unless GlobalSetting.respond_to?(:developer_emails) && GlobalSetting.developer_emails.present?
return []
end
GlobalSetting.developer_emails.split(",").map(&:strip) GlobalSetting.developer_emails.split(",").map(&:strip)
end end

View File

@ -6,7 +6,7 @@ class ForumsController < ActionController::Base
include ReadOnlyMixin include ReadOnlyMixin
before_action :check_readonly_mode before_action :check_readonly_mode
after_action :add_readonly_header after_action :add_readonly_header
def status def status
if params[:cluster] if params[:cluster]

View File

@ -1,44 +1,38 @@
# frozen_string_literal: true # frozen_string_literal: true
class GroupsController < ApplicationController class GroupsController < ApplicationController
requires_login only: [ requires_login only: %i[
:set_notifications, set_notifications
:mentionable, mentionable
:messageable, messageable
:check_name, check_name
:update, update
:histories, histories
:request_membership, request_membership
:search, search
:new, new
:test_email_settings test_email_settings
] ]
skip_before_action :preload_json, :check_xhr, only: [:posts_feed, :mentions_feed] skip_before_action :preload_json, :check_xhr, only: %i[posts_feed mentions_feed]
skip_before_action :check_xhr, only: [:show] skip_before_action :check_xhr, only: [:show]
after_action :add_noindex_header after_action :add_noindex_header
TYPE_FILTERS = { TYPE_FILTERS = {
my: Proc.new { |groups, user| my:
raise Discourse::NotFound unless user Proc.new do |groups, user|
Group.member_of(groups, user) raise Discourse::NotFound unless user
}, Group.member_of(groups, user)
owner: Proc.new { |groups, user| end,
raise Discourse::NotFound unless user owner:
Group.owner_of(groups, user) Proc.new do |groups, user|
}, raise Discourse::NotFound unless user
public: Proc.new { |groups| Group.owner_of(groups, user)
groups.where(public_admission: true, automatic: false) end,
}, public: Proc.new { |groups| groups.where(public_admission: true, automatic: false) },
close: Proc.new { |groups| close: Proc.new { |groups| groups.where(public_admission: false, automatic: false) },
groups.where(public_admission: false, automatic: false) automatic: Proc.new { |groups| groups.where(automatic: true) },
}, non_automatic: Proc.new { |groups| groups.where(automatic: false) },
automatic: Proc.new { |groups|
groups.where(automatic: true)
},
non_automatic: Proc.new { |groups|
groups.where(automatic: false)
}
} }
ADD_MEMBERS_LIMIT = 1000 ADD_MEMBERS_LIMIT = 1000
@ -47,7 +41,7 @@ class GroupsController < ApplicationController
raise Discourse::InvalidAccess.new(:enable_group_directory) raise Discourse::InvalidAccess.new(:enable_group_directory)
end end
order = %w{name user_count}.delete(params[:order]) order = %w[name user_count].delete(params[:order])
dir = params[:asc].to_s == "true" ? "ASC" : "DESC" dir = params[:asc].to_s == "true" ? "ASC" : "DESC"
sort = order ? "#{order} #{dir}" : nil sort = order ? "#{order} #{dir}" : nil
groups = Group.visible_groups(current_user, sort) groups = Group.visible_groups(current_user, sort)
@ -56,7 +50,7 @@ class GroupsController < ApplicationController
if (username = params[:username]).present? if (username = params[:username]).present?
raise Discourse::NotFound unless user = User.find_by_username(username) raise Discourse::NotFound unless user = User.find_by_username(username)
groups = TYPE_FILTERS[:my].call(groups.members_visible_groups(current_user, sort), user) groups = TYPE_FILTERS[:my].call(groups.members_visible_groups(current_user, sort), user)
type_filters = type_filters - [:my, :owner] type_filters = type_filters - %i[my owner]
end end
if (filter = params[:filter]).present? if (filter = params[:filter]).present?
@ -83,7 +77,7 @@ class GroupsController < ApplicationController
user_group_ids = group_users.pluck(:group_id) user_group_ids = group_users.pluck(:group_id)
owner_group_ids = group_users.where(owner: true).pluck(:group_id) owner_group_ids = group_users.where(owner: true).pluck(:group_id)
else else
type_filters = type_filters - [:my, :owner] type_filters = type_filters - %i[my owner]
end end
type_filters.delete(:non_automatic) type_filters.delete(:non_automatic)
@ -96,22 +90,19 @@ class GroupsController < ApplicationController
groups = groups.offset(page * page_size).limit(page_size) groups = groups.offset(page * page_size).limit(page_size)
render_json_dump( render_json_dump(
groups: serialize_data(groups, groups:
BasicGroupSerializer, serialize_data(
user_group_ids: user_group_ids || [], groups,
owner_group_ids: owner_group_ids || [] BasicGroupSerializer,
), user_group_ids: user_group_ids || [],
owner_group_ids: owner_group_ids || [],
),
extras: { extras: {
type_filters: type_filters type_filters: type_filters,
}, },
total_rows_groups: total, total_rows_groups: total,
load_more_groups: groups_path( load_more_groups:
page: page + 1, groups_path(page: page + 1, type: type, order: order, asc: params[:asc], filter: filter),
type: type,
order: order,
asc: params[:asc],
filter: filter
)
) )
end end
@ -122,21 +113,23 @@ class GroupsController < ApplicationController
format.html do format.html do
@title = group.full_name.present? ? group.full_name.capitalize : group.name @title = group.full_name.present? ? group.full_name.capitalize : group.name
@full_title = "#{@title} - #{SiteSetting.title}" @full_title = "#{@title} - #{SiteSetting.title}"
@description_meta = group.bio_cooked.present? ? PrettyText.excerpt(group.bio_cooked, 300) : @title @description_meta =
group.bio_cooked.present? ? PrettyText.excerpt(group.bio_cooked, 300) : @title
render :show render :show
end end
format.json do format.json do
groups = Group.visible_groups(current_user) groups = Group.visible_groups(current_user)
if !guardian.is_staff? if !guardian.is_staff?
groups = groups.where("automatic IS FALSE OR groups.id = ?", Group::AUTO_GROUPS[:moderators]) groups =
groups.where("automatic IS FALSE OR groups.id = ?", Group::AUTO_GROUPS[:moderators])
end end
render_json_dump( render_json_dump(
group: serialize_data(group, GroupShowSerializer, root: nil), group: serialize_data(group, GroupShowSerializer, root: nil),
extras: { extras: {
visible_group_names: groups.pluck(:name) visible_group_names: groups.pluck(:name),
} },
) )
end end
end end
@ -161,7 +154,15 @@ class GroupsController < ApplicationController
if params[:update_existing_users].blank? if params[:update_existing_users].blank?
user_count = count_existing_users(group.group_users, notification_level, categories, tags) user_count = count_existing_users(group.group_users, notification_level, categories, tags)
return render status: 422, json: { user_count: user_count, errors: [I18n.t('invalid_params', message: :update_existing_users)] } if user_count > 0 if user_count > 0
return(
render status: 422,
json: {
user_count: user_count,
errors: [I18n.t("invalid_params", message: :update_existing_users)],
}
)
end
end end
end end
@ -169,7 +170,9 @@ class GroupsController < ApplicationController
GroupActionLogger.new(current_user, group).log_change_group_settings GroupActionLogger.new(current_user, group).log_change_group_settings
group.record_email_setting_changes!(current_user) group.record_email_setting_changes!(current_user)
group.expire_imap_mailbox_cache group.expire_imap_mailbox_cache
update_existing_users(group.group_users, notification_level, categories, tags) if params[:update_existing_users] == "true" if params[:update_existing_users] == "true"
update_existing_users(group.group_users, notification_level, categories, tags)
end
AdminDashboardData.clear_found_problem("group_#{group.id}_email_credentials") AdminDashboardData.clear_found_problem("group_#{group.id}_email_credentials")
# Redirect user to groups index page if they can no longer see the group # Redirect user to groups index page if they can no longer see the group
@ -185,10 +188,7 @@ class GroupsController < ApplicationController
group = find_group(:group_id) group = find_group(:group_id)
guardian.ensure_can_see_group_members!(group) guardian.ensure_can_see_group_members!(group)
posts = group.posts_for( posts = group.posts_for(guardian, params.permit(:before_post_id, :category_id)).limit(20)
guardian,
params.permit(:before_post_id, :category_id)
).limit(20)
render_serialized posts.to_a, GroupPostSerializer render_serialized posts.to_a, GroupPostSerializer
end end
@ -196,37 +196,32 @@ class GroupsController < ApplicationController
group = find_group(:group_id) group = find_group(:group_id)
guardian.ensure_can_see_group_members!(group) guardian.ensure_can_see_group_members!(group)
@posts = group.posts_for( @posts = group.posts_for(guardian, params.permit(:before_post_id, :category_id)).limit(50)
guardian, @title =
params.permit(:before_post_id, :category_id) "#{SiteSetting.title} - #{I18n.t("rss_description.group_posts", group_name: group.name)}"
).limit(50)
@title = "#{SiteSetting.title} - #{I18n.t("rss_description.group_posts", group_name: group.name)}"
@link = Discourse.base_url @link = Discourse.base_url
@description = I18n.t("rss_description.group_posts", group_name: group.name) @description = I18n.t("rss_description.group_posts", group_name: group.name)
render 'posts/latest', formats: [:rss] render "posts/latest", formats: [:rss]
end end
def mentions def mentions
raise Discourse::NotFound unless SiteSetting.enable_mentions? raise Discourse::NotFound unless SiteSetting.enable_mentions?
group = find_group(:group_id) group = find_group(:group_id)
posts = group.mentioned_posts_for( posts =
guardian, group.mentioned_posts_for(guardian, params.permit(:before_post_id, :category_id)).limit(20)
params.permit(:before_post_id, :category_id)
).limit(20)
render_serialized posts.to_a, GroupPostSerializer render_serialized posts.to_a, GroupPostSerializer
end end
def mentions_feed def mentions_feed
raise Discourse::NotFound unless SiteSetting.enable_mentions? raise Discourse::NotFound unless SiteSetting.enable_mentions?
group = find_group(:group_id) group = find_group(:group_id)
@posts = group.mentioned_posts_for( @posts =
guardian, group.mentioned_posts_for(guardian, params.permit(:before_post_id, :category_id)).limit(50)
params.permit(:before_post_id, :category_id) @title =
).limit(50) "#{SiteSetting.title} - #{I18n.t("rss_description.group_mentions", group_name: group.name)}"
@title = "#{SiteSetting.title} - #{I18n.t("rss_description.group_mentions", group_name: group.name)}"
@link = Discourse.base_url @link = Discourse.base_url
@description = I18n.t("rss_description.group_mentions", group_name: group.name) @description = I18n.t("rss_description.group_mentions", group_name: group.name)
render 'posts/latest', formats: [:rss] render "posts/latest", formats: [:rss]
end end
def members def members
@ -240,10 +235,14 @@ class GroupsController < ApplicationController
raise Discourse::InvalidParameters.new(:limit) if limit < 0 || limit > 1000 raise Discourse::InvalidParameters.new(:limit) if limit < 0 || limit > 1000
raise Discourse::InvalidParameters.new(:offset) if offset < 0 raise Discourse::InvalidParameters.new(:offset) if offset < 0
dir = (params[:asc] && params[:asc].present?) ? 'ASC' : 'DESC' dir = (params[:asc] && params[:asc].present?) ? "ASC" : "DESC"
if params[:desc] if params[:desc]
Discourse.deprecate(":desc is deprecated please use :asc instead", output_in_test: true, drop_from: '2.9.0') Discourse.deprecate(
dir = (params[:desc] && params[:desc].present?) ? 'DESC' : 'ASC' ":desc is deprecated please use :asc instead",
output_in_test: true,
drop_from: "2.9.0",
)
dir = (params[:desc] && params[:desc].present?) ? "DESC" : "ASC"
end end
order = "NOT group_users.owner" order = "NOT group_users.owner"
@ -254,7 +253,7 @@ class GroupsController < ApplicationController
total = users.count total = users.count
if (filter = params[:filter]).present? if (filter = params[:filter]).present?
filter = filter.split(',') if filter.include?(',') filter = filter.split(",") if filter.include?(",")
if current_user&.admin if current_user&.admin
users = users.filter_by_username_or_email(filter) users = users.filter_by_username_or_email(filter)
@ -263,26 +262,29 @@ class GroupsController < ApplicationController
end end
end end
users = users users =
.select("users.*, group_requests.reason, group_requests.created_at requested_at") users
.order(params[:order] == 'requested_at' ? "group_requests.created_at #{dir}" : "") .select("users.*, group_requests.reason, group_requests.created_at requested_at")
.order(username_lower: dir) .order(params[:order] == "requested_at" ? "group_requests.created_at #{dir}" : "")
.limit(limit) .order(username_lower: dir)
.offset(offset) .limit(limit)
.offset(offset)
return render json: { return(
members: serialize_data(users, GroupRequesterSerializer), render json: {
meta: { members: serialize_data(users, GroupRequesterSerializer),
total: total, meta: {
limit: limit, total: total,
offset: offset limit: limit,
} offset: offset,
} },
}
)
end end
if params[:order] && %w{last_posted_at last_seen_at}.include?(params[:order]) if params[:order] && %w[last_posted_at last_seen_at].include?(params[:order])
order = "#{params[:order]} #{dir} NULLS LAST" order = "#{params[:order]} #{dir} NULLS LAST"
elsif params[:order] == 'added_at' elsif params[:order] == "added_at"
order = "group_users.created_at #{dir}" order = "group_users.created_at #{dir}"
end end
@ -290,7 +292,7 @@ class GroupsController < ApplicationController
total = users.count total = users.count
if (filter = params[:filter]).present? if (filter = params[:filter]).present?
filter = filter.split(',') if filter.include?(',') filter = filter.split(",") if filter.include?(",")
if current_user&.admin if current_user&.admin
users = users.filter_by_username_or_email(filter) users = users.filter_by_username_or_email(filter)
@ -299,25 +301,26 @@ class GroupsController < ApplicationController
end end
end end
users = users users =
.includes(:primary_group) users
.includes(:user_option) .includes(:primary_group)
.select('users.*, group_users.created_at as added_at') .includes(:user_option)
.order(order) .select("users.*, group_users.created_at as added_at")
.order(username_lower: dir) .order(order)
.order(username_lower: dir)
members = users.limit(limit).offset(offset) members = users.limit(limit).offset(offset)
owners = users.where('group_users.owner') owners = users.where("group_users.owner")
render json: { render json: {
members: serialize_data(members, GroupUserSerializer), members: serialize_data(members, GroupUserSerializer),
owners: serialize_data(owners, GroupUserSerializer), owners: serialize_data(owners, GroupUserSerializer),
meta: { meta: {
total: total, total: total,
limit: limit, limit: limit,
offset: offset offset: offset,
} },
} }
end end
def add_members def add_members
@ -327,10 +330,12 @@ class GroupsController < ApplicationController
users = users_from_params.to_a users = users_from_params.to_a
emails = [] emails = []
if params[:emails] if params[:emails]
params[:emails].split(",").each do |email| params[:emails]
existing_user = User.find_by_email(email) .split(",")
existing_user.present? ? users.push(existing_user) : emails.push(email) .each do |email|
end existing_user = User.find_by_email(email)
existing_user.present? ? users.push(existing_user) : emails.push(email)
end
end end
guardian.ensure_can_invite_to_forum!([group]) if emails.present? guardian.ensure_can_invite_to_forum!([group]) if emails.present?
@ -340,43 +345,43 @@ class GroupsController < ApplicationController
end end
if users.length > ADD_MEMBERS_LIMIT if users.length > ADD_MEMBERS_LIMIT
return render_json_error( return(
I18n.t("groups.errors.adding_too_many_users", count: ADD_MEMBERS_LIMIT) render_json_error(I18n.t("groups.errors.adding_too_many_users", count: ADD_MEMBERS_LIMIT))
) )
end end
usernames_already_in_group = group.users.where(id: users.map(&:id)).pluck(:username) usernames_already_in_group = group.users.where(id: users.map(&:id)).pluck(:username)
if usernames_already_in_group.present? && if usernames_already_in_group.present? && usernames_already_in_group.length == users.length &&
usernames_already_in_group.length == users.length && emails.blank?
emails.blank? render_json_error(
render_json_error(I18n.t( I18n.t(
"groups.errors.member_already_exist", "groups.errors.member_already_exist",
username: usernames_already_in_group.sort.join(", "), username: usernames_already_in_group.sort.join(", "),
count: usernames_already_in_group.size count: usernames_already_in_group.size,
)) ),
)
else else
notify = params[:notify_users]&.to_s == "true" notify = params[:notify_users]&.to_s == "true"
uniq_users = users.uniq uniq_users = users.uniq
uniq_users.each do |user| uniq_users.each { |user| add_user_to_group(group, user, notify) }
add_user_to_group(group, user, notify)
end
emails.each do |email| emails.each do |email|
begin begin
Invite.generate(current_user, email: email, group_ids: [group.id]) Invite.generate(current_user, email: email, group_ids: [group.id])
rescue RateLimiter::LimitExceeded => e rescue RateLimiter::LimitExceeded => e
return render_json_error(I18n.t( return(
"invite.rate_limit", render_json_error(
count: SiteSetting.max_invites_per_day, I18n.t(
time_left: e.time_left "invite.rate_limit",
)) count: SiteSetting.max_invites_per_day,
time_left: e.time_left,
),
)
)
end end
end end
render json: success_json.merge!( render json: success_json.merge!(usernames: uniq_users.map(&:username), emails: emails)
usernames: uniq_users.map(&:username),
emails: emails
)
end end
end end
@ -412,12 +417,13 @@ class GroupsController < ApplicationController
end end
if params[:accept] if params[:accept]
PostCreator.new(current_user, PostCreator.new(
title: I18n.t('groups.request_accepted_pm.title', group_name: group.name), current_user,
raw: I18n.t('groups.request_accepted_pm.body', group_name: group.name), title: I18n.t("groups.request_accepted_pm.title", group_name: group.name),
raw: I18n.t("groups.request_accepted_pm.body", group_name: group.name),
archetype: Archetype.private_message, archetype: Archetype.private_message,
target_usernames: user.username, target_usernames: user.username,
skip_validations: true skip_validations: true,
).create! ).create!
end end
@ -460,9 +466,9 @@ class GroupsController < ApplicationController
params[:user_emails] = params[:user_email] if params[:user_email].present? params[:user_emails] = params[:user_email] if params[:user_email].present?
users = users_from_params users = users_from_params
raise Discourse::InvalidParameters.new( if users.empty?
'user_ids or usernames or user_emails must be present' raise Discourse::InvalidParameters.new("user_ids or usernames or user_emails must be present")
) if users.empty? end
removed_users = [] removed_users = []
skipped_users = [] skipped_users = []
@ -480,10 +486,7 @@ class GroupsController < ApplicationController
end end
end end
render json: success_json.merge!( render json: success_json.merge!(usernames: removed_users, skipped_usernames: skipped_users)
usernames: removed_users,
skipped_usernames: skipped_users
)
end end
def leave def leave
@ -511,24 +514,35 @@ class GroupsController < ApplicationController
begin begin
GroupRequest.create!(group: group, user: current_user, reason: params[:reason]) GroupRequest.create!(group: group, user: current_user, reason: params[:reason])
rescue ActiveRecord::RecordNotUnique rescue ActiveRecord::RecordNotUnique
return render json: failed_json.merge(error: I18n.t("groups.errors.already_requested_membership")), status: 409 return(
render json: failed_json.merge(error: I18n.t("groups.errors.already_requested_membership")),
status: 409
)
end end
usernames = [current_user.username].concat( usernames = [current_user.username].concat(
group.users.where('group_users.owner') group
.users
.where("group_users.owner")
.order("users.last_seen_at DESC") .order("users.last_seen_at DESC")
.limit(MAX_NOTIFIED_OWNERS) .limit(MAX_NOTIFIED_OWNERS)
.pluck("users.username") .pluck("users.username"),
) )
post = PostCreator.new(current_user, post =
title: I18n.t('groups.request_membership_pm.title', group_name: group.name), PostCreator.new(
raw: params[:reason], current_user,
archetype: Archetype.private_message, title: I18n.t("groups.request_membership_pm.title", group_name: group.name),
target_usernames: usernames.join(','), raw: params[:reason],
topic_opts: { custom_fields: { requested_group_id: group.id } }, archetype: Archetype.private_message,
skip_validations: true target_usernames: usernames.join(","),
).create! topic_opts: {
custom_fields: {
requested_group_id: group.id,
},
},
skip_validations: true,
).create!
render json: success_json.merge(relative_url: post.topic.relative_url) render json: success_json.merge(relative_url: post.topic.relative_url)
end end
@ -538,11 +552,10 @@ class GroupsController < ApplicationController
notification_level = params.require(:notification_level) notification_level = params.require(:notification_level)
user_id = current_user.id user_id = current_user.id
if guardian.is_staff? user_id = params[:user_id] || user_id if guardian.is_staff?
user_id = params[:user_id] || user_id
end
GroupUser.where(group_id: group.id) GroupUser
.where(group_id: group.id)
.where(user_id: user_id) .where(user_id: user_id)
.update_all(notification_level: notification_level) .update_all(notification_level: notification_level)
@ -556,29 +569,28 @@ class GroupsController < ApplicationController
page_size = 25 page_size = 25
offset = (params[:offset] && params[:offset].to_i) || 0 offset = (params[:offset] && params[:offset].to_i) || 0
group_histories = GroupHistory.with_filters(group, params[:filters]) group_histories =
.limit(page_size) GroupHistory.with_filters(group, params[:filters]).limit(page_size).offset(offset * page_size)
.offset(offset * page_size)
render_json_dump( render_json_dump(
logs: serialize_data(group_histories, BasicGroupHistorySerializer), logs: serialize_data(group_histories, BasicGroupHistorySerializer),
all_loaded: group_histories.count < page_size all_loaded: group_histories.count < page_size,
) )
end end
def search def search
groups = Group.visible_groups(current_user) groups =
.where("groups.id <> ?", Group::AUTO_GROUPS[:everyone]) Group
.includes(:flair_upload) .visible_groups(current_user)
.order(:name) .where("groups.id <> ?", Group::AUTO_GROUPS[:everyone])
.includes(:flair_upload)
.order(:name)
if (term = params[:term]).present? if (term = params[:term]).present?
groups = groups.where("name ILIKE :term OR full_name ILIKE :term", term: "%#{term}%") groups = groups.where("name ILIKE :term OR full_name ILIKE :term", term: "%#{term}%")
end end
if params[:ignore_automatic].to_s == "true" groups = groups.where(automatic: false) if params[:ignore_automatic].to_s == "true"
groups = groups.where(automatic: false)
end
if Group.preloaded_custom_field_names.present? if Group.preloaded_custom_field_names.present?
Group.preload_custom_fields(groups, Group.preloaded_custom_field_names) Group.preload_custom_fields(groups, Group.preloaded_custom_field_names)
@ -589,8 +601,14 @@ class GroupsController < ApplicationController
def permissions def permissions
group = find_group(:id) group = find_group(:id)
category_groups = group.category_groups.select { |category_group| guardian.can_see_category?(category_group.category) } category_groups =
render_serialized(category_groups.sort_by { |category_group| category_group.category.name }, CategoryGroupSerializer) group.category_groups.select do |category_group|
guardian.can_see_category?(category_group.category)
end
render_serialized(
category_groups.sort_by { |category_group| category_group.category.name },
CategoryGroupSerializer,
)
end end
def test_email_settings def test_email_settings
@ -611,7 +629,7 @@ class GroupsController < ApplicationController
enable_tls = settings[:ssl] == "true" enable_tls = settings[:ssl] == "true"
email_host = params[:host] email_host = params[:host]
if !["smtp", "imap"].include?(params[:protocol]) if !%w[smtp imap].include?(params[:protocol])
raise Discourse::InvalidParameters.new("Valid protocols to test are smtp and imap") raise Discourse::InvalidParameters.new("Valid protocols to test are smtp and imap")
end end
@ -622,20 +640,29 @@ class GroupsController < ApplicationController
enable_starttls_auto = false enable_starttls_auto = false
settings.delete(:ssl) settings.delete(:ssl)
final_settings = settings.merge(enable_tls: enable_tls, enable_starttls_auto: enable_starttls_auto) final_settings =
.permit(:host, :port, :username, :password, :enable_tls, :enable_starttls_auto, :debug) settings.merge(
EmailSettingsValidator.validate_as_user(current_user, "smtp", **final_settings.to_h.symbolize_keys) enable_tls: enable_tls,
enable_starttls_auto: enable_starttls_auto,
).permit(:host, :port, :username, :password, :enable_tls, :enable_starttls_auto, :debug)
EmailSettingsValidator.validate_as_user(
current_user,
"smtp",
**final_settings.to_h.symbolize_keys,
)
when "imap" when "imap"
final_settings = settings.merge(ssl: enable_tls) final_settings =
.permit(:host, :port, :username, :password, :ssl, :debug) settings.merge(ssl: enable_tls).permit(:host, :port, :username, :password, :ssl, :debug)
EmailSettingsValidator.validate_as_user(current_user, "imap", **final_settings.to_h.symbolize_keys) EmailSettingsValidator.validate_as_user(
current_user,
"imap",
**final_settings.to_h.symbolize_keys,
)
end end
render json: success_json render json: success_json
rescue *EmailSettingsExceptionHandler::EXPECTED_EXCEPTIONS, StandardError => err rescue *EmailSettingsExceptionHandler::EXPECTED_EXCEPTIONS, StandardError => err
render_json_error( render_json_error(EmailSettingsExceptionHandler.friendly_exception_message(err, email_host))
EmailSettingsExceptionHandler.friendly_exception_message(err, email_host)
)
end end
end end
end end
@ -653,7 +680,7 @@ class GroupsController < ApplicationController
end end
def group_params(automatic: false) def group_params(automatic: false)
attributes = %i{ attributes = %i[
bio_raw bio_raw
default_notification_level default_notification_level
messageable_level messageable_level
@ -662,7 +689,7 @@ class GroupsController < ApplicationController
flair_color flair_color
flair_icon flair_icon
flair_upload_id flair_upload_id
} ]
if automatic if automatic
attributes.push(:visibility_level) attributes.push(:visibility_level)
@ -673,7 +700,7 @@ class GroupsController < ApplicationController
:full_name, :full_name,
:public_exit, :public_exit,
:public_admission, :public_admission,
:membership_request_template :membership_request_template,
) )
end end
@ -703,7 +730,7 @@ class GroupsController < ApplicationController
:grant_trust_level, :grant_trust_level,
:automatic_membership_email_domains, :automatic_membership_email_domains,
:publish_read_state, :publish_read_state,
:allow_unknown_sender_topic_replies :allow_unknown_sender_topic_replies,
) )
custom_fields = DiscoursePluginRegistry.editable_group_custom_fields custom_fields = DiscoursePluginRegistry.editable_group_custom_fields
@ -711,7 +738,7 @@ class GroupsController < ApplicationController
end end
if !automatic || current_user.admin if !automatic || current_user.admin
[:muted, :regular, :tracking, :watching, :watching_first_post].each do |level| %i[muted regular tracking watching watching_first_post].each do |level|
attributes << { "#{level}_category_ids" => [] } attributes << { "#{level}_category_ids" => [] }
attributes << { "#{level}_tags" => [] } attributes << { "#{level}_tags" => [] }
end end
@ -770,8 +797,10 @@ class GroupsController < ApplicationController
end end
def user_default_notifications(group, params) def user_default_notifications(group, params)
category_notifications = group.group_category_notification_defaults.pluck(:category_id, :notification_level).to_h category_notifications =
tag_notifications = group.group_tag_notification_defaults.pluck(:tag_id, :notification_level).to_h group.group_category_notification_defaults.pluck(:category_id, :notification_level).to_h
tag_notifications =
group.group_tag_notification_defaults.pluck(:tag_id, :notification_level).to_h
categories = {} categories = {}
tags = {} tags = {}
@ -782,10 +811,7 @@ class GroupsController < ApplicationController
category_id = category_id.to_i category_id = category_id.to_i
old_value = category_notifications[category_id] old_value = category_notifications[category_id]
metadata = { metadata = { old_value: old_value, new_value: value }
old_value: old_value,
new_value: value
}
if old_value.blank? if old_value.blank?
metadata[:action] = :create metadata[:action] = :create
@ -805,10 +831,7 @@ class GroupsController < ApplicationController
tag_ids.each do |tag_id| tag_ids.each do |tag_id|
old_value = tag_notifications[tag_id] old_value = tag_notifications[tag_id]
metadata = { metadata = { old_value: old_value, new_value: value }
old_value: old_value,
new_value: value
}
if old_value.blank? if old_value.blank?
metadata[:action] = :create metadata[:action] = :create
@ -834,20 +857,18 @@ class GroupsController < ApplicationController
notification_level = nil notification_level = nil
default_notification_level = params[:default_notification_level]&.to_i default_notification_level = params[:default_notification_level]&.to_i
if default_notification_level.present? && group.default_notification_level != default_notification_level if default_notification_level.present? &&
group.default_notification_level != default_notification_level
notification_level = { notification_level = {
old_value: group.default_notification_level, old_value: group.default_notification_level,
new_value: default_notification_level new_value: default_notification_level,
} }
end end
[notification_level, categories, tags] [notification_level, categories, tags]
end end
%i{ %i[count update].each do |action|
count
update
}.each do |action|
define_method("#{action}_existing_users") do |group_users, notification_level, categories, tags| define_method("#{action}_existing_users") do |group_users, notification_level, categories, tags|
return 0 if notification_level.blank? && categories.blank? && tags.blank? return 0 if notification_level.blank? && categories.blank? && tags.blank?
@ -865,7 +886,12 @@ class GroupsController < ApplicationController
categories.each do |category_id, data| categories.each do |category_id, data|
if data[:action] == :update || data[:action] == :delete if data[:action] == :update || data[:action] == :delete
category_users = CategoryUser.where(category_id: category_id, notification_level: data[:old_value], user_id: group_users.select(:user_id)) category_users =
CategoryUser.where(
category_id: category_id,
notification_level: data[:old_value],
user_id: group_users.select(:user_id),
)
if action == :update if action == :update
category_users.delete_all category_users.delete_all
@ -879,7 +905,12 @@ class GroupsController < ApplicationController
tags.each do |tag_id, data| tags.each do |tag_id, data|
if data[:action] == :update || data[:action] == :delete if data[:action] == :update || data[:action] == :delete
tag_users = TagUser.where(tag_id: tag_id, notification_level: data[:old_value], user_id: group_users.select(:user_id)) tag_users =
TagUser.where(
tag_id: tag_id,
notification_level: data[:old_value],
user_id: group_users.select(:user_id),
)
if action == :update if action == :update
tag_users.delete_all tag_users.delete_all
@ -892,47 +923,65 @@ class GroupsController < ApplicationController
end end
if categories.present? || tags.present? if categories.present? || tags.present?
group_users.select(:id, :user_id).find_in_batches do |batch| group_users
user_ids = batch.pluck(:user_id) .select(:id, :user_id)
.find_in_batches do |batch|
user_ids = batch.pluck(:user_id)
categories.each do |category_id, data| categories.each do |category_id, data|
category_users = [] category_users = []
existing_users = CategoryUser.where(category_id: category_id, user_id: user_ids).where("notification_level IS NOT NULL") existing_users =
skip_user_ids = existing_users.pluck(:user_id) CategoryUser.where(category_id: category_id, user_id: user_ids).where(
"notification_level IS NOT NULL",
)
skip_user_ids = existing_users.pluck(:user_id)
batch.each do |group_user| batch.each do |group_user|
next if skip_user_ids.include?(group_user.user_id) next if skip_user_ids.include?(group_user.user_id)
category_users << { category_id: category_id, user_id: group_user.user_id, notification_level: data[:new_value] } category_users << {
category_id: category_id,
user_id: group_user.user_id,
notification_level: data[:new_value],
}
end
next if category_users.blank?
if action == :update
CategoryUser.insert_all!(category_users)
else
ids += category_users.pluck(:user_id)
end
end end
next if category_users.blank? tags.each do |tag_id, data|
tag_users = []
existing_users =
TagUser.where(tag_id: tag_id, user_id: user_ids).where(
"notification_level IS NOT NULL",
)
skip_user_ids = existing_users.pluck(:user_id)
if action == :update batch.each do |group_user|
CategoryUser.insert_all!(category_users) next if skip_user_ids.include?(group_user.user_id)
else tag_users << {
ids += category_users.pluck(:user_id) tag_id: tag_id,
user_id: group_user.user_id,
notification_level: data[:new_value],
created_at: Time.now,
updated_at: Time.now,
}
end
next if tag_users.blank?
if action == :update
TagUser.insert_all!(tag_users)
else
ids += tag_users.pluck(:user_id)
end
end end
end end
tags.each do |tag_id, data|
tag_users = []
existing_users = TagUser.where(tag_id: tag_id, user_id: user_ids).where("notification_level IS NOT NULL")
skip_user_ids = existing_users.pluck(:user_id)
batch.each do |group_user|
next if skip_user_ids.include?(group_user.user_id)
tag_users << { tag_id: tag_id, user_id: group_user.user_id, notification_level: data[:new_value], created_at: Time.now, updated_at: Time.now }
end
next if tag_users.blank?
if action == :update
TagUser.insert_all!(tag_users)
else
ids += tag_users.pluck(:user_id)
end
end
end
end end
ids.uniq.count ids.uniq.count

View File

@ -1,26 +1,26 @@
# frozen_string_literal: true # frozen_string_literal: true
class HighlightJsController < ApplicationController class HighlightJsController < ApplicationController
skip_before_action :preload_json, :redirect_to_login_if_required, :check_xhr, :verify_authenticity_token, only: [:show] skip_before_action :preload_json,
:redirect_to_login_if_required,
:check_xhr,
:verify_authenticity_token,
only: [:show]
before_action :apply_cdn_headers, only: [:show] before_action :apply_cdn_headers, only: [:show]
def show def show
no_cookies no_cookies
RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do
current_version = HighlightJs.version(SiteSetting.highlighted_languages) current_version = HighlightJs.version(SiteSetting.highlighted_languages)
if current_version != params[:version] return redirect_to path(HighlightJs.path) if current_version != params[:version]
return redirect_to path(HighlightJs.path)
end
# note, this can be slightly optimised by caching the bundled file, it cuts down on N reads # note, this can be slightly optimised by caching the bundled file, it cuts down on N reads
# our nginx config caches this so in practical terms it does not really matter and keeps # our nginx config caches this so in practical terms it does not really matter and keeps
# code simpler # code simpler
languages = SiteSetting.highlighted_languages.split('|') languages = SiteSetting.highlighted_languages.split("|")
highlight_js = HighlightJs.bundle(languages) highlight_js = HighlightJs.bundle(languages)
@ -28,7 +28,7 @@ class HighlightJsController < ApplicationController
response.headers["Content-Length"] = highlight_js.bytesize.to_s response.headers["Content-Length"] = highlight_js.bytesize.to_s
immutable_for 1.year immutable_for 1.year
render plain: highlight_js, disposition: nil, content_type: 'application/javascript' render plain: highlight_js, disposition: nil, content_type: "application/javascript"
end end
end end
end end

View File

@ -5,12 +5,13 @@ class InlineOneboxController < ApplicationController
def show def show
hijack do hijack do
oneboxes = InlineOneboxer.new( oneboxes =
params[:urls] || [], InlineOneboxer.new(
user_id: current_user.id, params[:urls] || [],
category_id: params[:category_id].to_i, user_id: current_user.id,
topic_id: params[:topic_id].to_i category_id: params[:category_id].to_i,
).process topic_id: params[:topic_id].to_i,
).process
render json: { "inline-oneboxes" => oneboxes } render json: { "inline-oneboxes" => oneboxes }
end end
end end

View File

@ -1,17 +1,24 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'csv' require "csv"
class InvitesController < ApplicationController class InvitesController < ApplicationController
requires_login only: %i[
requires_login only: [:create, :retrieve, :destroy, :destroy_all_expired, :resend_invite, :resend_all_invites, :upload_csv] create
retrieve
destroy
destroy_all_expired
resend_invite
resend_all_invites
upload_csv
]
skip_before_action :check_xhr, except: [:perform_accept_invitation] skip_before_action :check_xhr, except: [:perform_accept_invitation]
skip_before_action :preload_json, except: [:show] skip_before_action :preload_json, except: [:show]
skip_before_action :redirect_to_login_if_required skip_before_action :redirect_to_login_if_required
before_action :ensure_invites_allowed, only: [:show, :perform_accept_invitation] before_action :ensure_invites_allowed, only: %i[show perform_accept_invitation]
before_action :ensure_new_registrations_allowed, only: [:show, :perform_accept_invitation] before_action :ensure_new_registrations_allowed, only: %i[show perform_accept_invitation]
def show def show
expires_now expires_now
@ -27,7 +34,7 @@ class InvitesController < ApplicationController
end end
rescue RateLimiter::LimitExceeded => e rescue RateLimiter::LimitExceeded => e
flash.now[:error] = e.description flash.now[:error] = e.description
render layout: 'no_ember' render layout: "no_ember"
end end
def create def create
@ -45,24 +52,37 @@ class InvitesController < ApplicationController
if !groups_can_see_topic?(groups, topic) if !groups_can_see_topic?(groups, topic)
editable_topic_groups = topic.category.groups.filter { |g| guardian.can_edit_group?(g) } editable_topic_groups = topic.category.groups.filter { |g| guardian.can_edit_group?(g) }
return render_json_error(I18n.t("invite.requires_groups", groups: editable_topic_groups.pluck(:name).join(", "))) return(
render_json_error(
I18n.t("invite.requires_groups", groups: editable_topic_groups.pluck(:name).join(", ")),
)
)
end end
invite = Invite.generate(current_user, invite =
email: params[:email], Invite.generate(
domain: params[:domain], current_user,
skip_email: params[:skip_email], email: params[:email],
invited_by: current_user, domain: params[:domain],
custom_message: params[:custom_message], skip_email: params[:skip_email],
max_redemptions_allowed: params[:max_redemptions_allowed], invited_by: current_user,
topic_id: topic&.id, custom_message: params[:custom_message],
group_ids: groups&.map(&:id), max_redemptions_allowed: params[:max_redemptions_allowed],
expires_at: params[:expires_at], topic_id: topic&.id,
invite_to_topic: params[:invite_to_topic] group_ids: groups&.map(&:id),
) expires_at: params[:expires_at],
invite_to_topic: params[:invite_to_topic],
)
if invite.present? if invite.present?
render_serialized(invite, InviteSerializer, scope: guardian, root: nil, show_emails: params.has_key?(:email), show_warnings: true) render_serialized(
invite,
InviteSerializer,
scope: guardian,
root: nil,
show_emails: params.has_key?(:email),
show_warnings: true,
)
else else
render json: failed_json, status: 422 render json: failed_json, status: 422
end end
@ -81,7 +101,14 @@ class InvitesController < ApplicationController
guardian.ensure_can_invite_to_forum!(nil) guardian.ensure_can_invite_to_forum!(nil)
render_serialized(invite, InviteSerializer, scope: guardian, root: nil, show_emails: params.has_key?(:email), show_warnings: true) render_serialized(
invite,
InviteSerializer,
scope: guardian,
root: nil,
show_emails: params.has_key?(:email),
show_warnings: true,
)
end end
def update def update
@ -108,12 +135,19 @@ class InvitesController < ApplicationController
if params.has_key?(:group_ids) || params.has_key?(:group_names) if params.has_key?(:group_ids) || params.has_key?(:group_names)
invite.invited_groups.destroy_all invite.invited_groups.destroy_all
groups.each { |group| invite.invited_groups.find_or_create_by!(group_id: group.id) } if groups.present? if groups.present?
groups.each { |group| invite.invited_groups.find_or_create_by!(group_id: group.id) }
end
end end
if !groups_can_see_topic?(invite.groups, invite.topics.first) if !groups_can_see_topic?(invite.groups, invite.topics.first)
editable_topic_groups = invite.topics.first.category.groups.filter { |g| guardian.can_edit_group?(g) } editable_topic_groups =
return render_json_error(I18n.t("invite.requires_groups", groups: editable_topic_groups.pluck(:name).join(", "))) invite.topics.first.category.groups.filter { |g| guardian.can_edit_group?(g) }
return(
render_json_error(
I18n.t("invite.requires_groups", groups: editable_topic_groups.pluck(:name).join(", ")),
)
)
end end
if params.has_key?(:email) if params.has_key?(:email)
@ -121,20 +155,26 @@ class InvitesController < ApplicationController
new_email = params[:email].presence new_email = params[:email].presence
if new_email if new_email
if Invite.where.not(id: invite.id).find_by(email: new_email.downcase, invited_by_id: current_user.id)&.redeemable? if Invite
return render_json_error( .where.not(id: invite.id)
I18n.t("invite.invite_exists", email: CGI.escapeHTML(new_email)), .find_by(email: new_email.downcase, invited_by_id: current_user.id)
status: 409 &.redeemable?
return(
render_json_error(
I18n.t("invite.invite_exists", email: CGI.escapeHTML(new_email)),
status: 409,
)
) )
end end
end end
if old_email != new_email if old_email != new_email
invite.emailed_status = if new_email && !params[:skip_email] invite.emailed_status =
Invite.emailed_status_types[:pending] if new_email && !params[:skip_email]
else Invite.emailed_status_types[:pending]
Invite.emailed_status_types[:not_required] else
end Invite.emailed_status_types[:not_required]
end
end end
invite.domain = nil if invite.email.present? invite.domain = nil if invite.email.present?
@ -162,9 +202,13 @@ class InvitesController < ApplicationController
end end
begin begin
invite.update!(params.permit(:email, :custom_message, :max_redemptions_allowed, :expires_at)) invite.update!(
params.permit(:email, :custom_message, :max_redemptions_allowed, :expires_at),
)
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
return render json: {}, status: 200 if SiteSetting.hide_email_address_taken? && e.record.email_already_exists? if SiteSetting.hide_email_address_taken? && e.record.email_already_exists?
return render json: {}, status: 200
end
return render_json_error(e.record.errors.full_messages.first) return render_json_error(e.record.errors.full_messages.first)
end end
end end
@ -174,7 +218,14 @@ class InvitesController < ApplicationController
Jobs.enqueue(:invite_email, invite_id: invite.id, invite_to_topic: params[:invite_to_topic]) Jobs.enqueue(:invite_email, invite_id: invite.id, invite_to_topic: params[:invite_to_topic])
end end
render_serialized(invite, InviteSerializer, scope: guardian, root: nil, show_emails: params.has_key?(:email), show_warnings: true) render_serialized(
invite,
InviteSerializer,
scope: guardian,
root: nil,
show_emails: params.has_key?(:email),
show_warnings: true,
)
end end
def destroy def destroy
@ -192,17 +243,23 @@ class InvitesController < ApplicationController
# via the SessionController#sso_login route # via the SessionController#sso_login route
def perform_accept_invitation def perform_accept_invitation
params.require(:id) params.require(:id)
params.permit(:email, :username, :name, :password, :timezone, :email_token, user_custom_fields: {}) params.permit(
:email,
:username,
:name,
:password,
:timezone,
:email_token,
user_custom_fields: {
},
)
invite = Invite.find_by(invite_key: params[:id]) invite = Invite.find_by(invite_key: params[:id])
redeeming_user = current_user redeeming_user = current_user
if invite.present? if invite.present?
begin begin
attrs = { attrs = { ip_address: request.remote_ip, session: session }
ip_address: request.remote_ip,
session: session
}
if redeeming_user if redeeming_user
attrs[:redeeming_user] = redeeming_user attrs[:redeeming_user] = redeeming_user
@ -230,12 +287,10 @@ class InvitesController < ApplicationController
end end
if user.blank? if user.blank?
return render json: failed_json.merge(message: I18n.t('invite.not_found_json')), status: 404 return render json: failed_json.merge(message: I18n.t("invite.not_found_json")), status: 404
end end
if !redeeming_user && user.active? && user.guardian.can_access_forum? log_on_user(user) if !redeeming_user && user.active? && user.guardian.can_access_forum?
log_on_user(user)
end
user.update_timezone_if_missing(params[:timezone]) user.update_timezone_if_missing(params[:timezone])
post_process_invite(user) post_process_invite(user)
@ -246,9 +301,7 @@ class InvitesController < ApplicationController
if user.present? if user.present?
if user.active? && user.guardian.can_access_forum? if user.active? && user.guardian.can_access_forum?
if redeeming_user response[:message] = I18n.t("invite.existing_user_success") if redeeming_user
response[:message] = I18n.t("invite.existing_user_success")
end
if user.guardian.can_see?(topic) if user.guardian.can_see?(topic)
response[:redirect_to] = path(topic.relative_url) response[:redirect_to] = path(topic.relative_url)
@ -257,20 +310,18 @@ class InvitesController < ApplicationController
end end
else else
response[:message] = if user.active? response[:message] = if user.active?
I18n.t('activation.approval_required') I18n.t("activation.approval_required")
else else
I18n.t('invite.confirm_email') I18n.t("invite.confirm_email")
end end
if user.guardian.can_see?(topic) cookies[:destination_url] = path(topic.relative_url) if user.guardian.can_see?(topic)
cookies[:destination_url] = path(topic.relative_url)
end
end end
end end
render json: success_json.merge(response) render json: success_json.merge(response)
else else
render json: failed_json.merge(message: I18n.t('invite.not_found_json')), status: 404 render json: failed_json.merge(message: I18n.t("invite.not_found_json")), status: 404
end end
end end
@ -279,7 +330,7 @@ class InvitesController < ApplicationController
Invite Invite
.where(invited_by: current_user) .where(invited_by: current_user)
.where('expires_at < ?', Time.zone.now) .where("expires_at < ?", Time.zone.now)
.find_each { |invite| invite.trash!(current_user) } .find_each { |invite| invite.trash!(current_user) }
render json: success_json render json: success_json
@ -301,13 +352,20 @@ class InvitesController < ApplicationController
guardian.ensure_can_resend_all_invites!(current_user) guardian.ensure_can_resend_all_invites!(current_user)
begin begin
RateLimiter.new(current_user, "bulk-reinvite-per-day", 1, 1.day, apply_limit_to_staff: true).performed! RateLimiter.new(
current_user,
"bulk-reinvite-per-day",
1,
1.day,
apply_limit_to_staff: true,
).performed!
rescue RateLimiter::LimitExceeded rescue RateLimiter::LimitExceeded
return render_json_error(I18n.t("rate_limiter.slow_down")) return render_json_error(I18n.t("rate_limiter.slow_down"))
end end
Invite.pending(current_user) Invite
.where('invites.email IS NOT NULL') .pending(current_user)
.where("invites.email IS NOT NULL")
.find_each { |invite| invite.resend_invite } .find_each { |invite| invite.resend_invite }
render json: success_json render json: success_json
@ -326,17 +384,15 @@ class InvitesController < ApplicationController
CSV.foreach(file.tempfile, encoding: "bom|utf-8") do |row| CSV.foreach(file.tempfile, encoding: "bom|utf-8") do |row|
# Try to extract a CSV header, if it exists # Try to extract a CSV header, if it exists
if csv_header.nil? if csv_header.nil?
if row[0] == 'email' if row[0] == "email"
csv_header = row csv_header = row
next next
else else
csv_header = ["email", "groups", "topic_id"] csv_header = %w[email groups topic_id]
end end
end end
if row[0].present? invites.push(csv_header.zip(row).map.to_h.filter { |k, v| v.present? }) if row[0].present?
invites.push(csv_header.zip(row).map.to_h.filter { |k, v| v.present? })
end
break if invites.count >= SiteSetting.max_bulk_invites break if invites.count >= SiteSetting.max_bulk_invites
end end
@ -345,7 +401,16 @@ class InvitesController < ApplicationController
Jobs.enqueue(:bulk_invite, invites: invites, current_user_id: current_user.id) Jobs.enqueue(:bulk_invite, invites: invites, current_user_id: current_user.id)
if invites.count >= SiteSetting.max_bulk_invites if invites.count >= SiteSetting.max_bulk_invites
render json: failed_json.merge(errors: [I18n.t("bulk_invite.max_rows", max_bulk_invites: SiteSetting.max_bulk_invites)]), status: 422 render json:
failed_json.merge(
errors: [
I18n.t(
"bulk_invite.max_rows",
max_bulk_invites: SiteSetting.max_bulk_invites,
),
],
),
status: 422
else else
render json: success_json render json: success_json
end end
@ -375,9 +440,7 @@ class InvitesController < ApplicationController
email_verified_by_link = invite.email_token.present? && params[:t] == invite.email_token email_verified_by_link = invite.email_token.present? && params[:t] == invite.email_token
if email_verified_by_link email = invite.email if email_verified_by_link
email = invite.email
end
hidden_email = email != invite.email hidden_email = email != invite.email
@ -393,12 +456,10 @@ class InvitesController < ApplicationController
hidden_email: hidden_email, hidden_email: hidden_email,
username: username, username: username,
is_invite_link: invite.is_invite_link?, is_invite_link: invite.is_invite_link?,
email_verified_by_link: email_verified_by_link email_verified_by_link: email_verified_by_link,
} }
if different_external_email info[:different_external_email] = true if different_external_email
info[:different_external_email] = true
end
if staged_user = User.where(staged: true).with_email(invite.email).first if staged_user = User.where(staged: true).with_email(invite.email).first
info[:username] = staged_user.username info[:username] = staged_user.username
@ -417,36 +478,46 @@ class InvitesController < ApplicationController
secure_session["invite-key"] = invite.invite_key secure_session["invite-key"] = invite.invite_key
render layout: 'application' render layout: "application"
end end
def show_irredeemable_invite(invite) def show_irredeemable_invite(invite)
flash.now[:error] = \ flash.now[:error] = if invite.blank?
if invite.blank? I18n.t("invite.not_found", base_url: Discourse.base_url)
I18n.t('invite.not_found', base_url: Discourse.base_url) elsif invite.redeemed?
elsif invite.redeemed? if invite.is_invite_link?
if invite.is_invite_link? I18n.t(
I18n.t('invite.not_found_template_link', site_name: SiteSetting.title, base_url: Discourse.base_url) "invite.not_found_template_link",
else site_name: SiteSetting.title,
I18n.t('invite.not_found_template', site_name: SiteSetting.title, base_url: Discourse.base_url) base_url: Discourse.base_url,
end )
elsif invite.expired? else
I18n.t('invite.expired', base_url: Discourse.base_url) I18n.t(
"invite.not_found_template",
site_name: SiteSetting.title,
base_url: Discourse.base_url,
)
end end
elsif invite.expired?
I18n.t("invite.expired", base_url: Discourse.base_url)
end
render layout: 'no_ember' render layout: "no_ember"
end end
def ensure_invites_allowed def ensure_invites_allowed
if (!SiteSetting.enable_local_logins && Discourse.enabled_auth_providers.count == 0 && !SiteSetting.enable_discourse_connect) if (
!SiteSetting.enable_local_logins && Discourse.enabled_auth_providers.count == 0 &&
!SiteSetting.enable_discourse_connect
)
raise Discourse::NotFound raise Discourse::NotFound
end end
end end
def ensure_new_registrations_allowed def ensure_new_registrations_allowed
unless SiteSetting.allow_new_registrations unless SiteSetting.allow_new_registrations
flash[:error] = I18n.t('login.new_registrations_disabled') flash[:error] = I18n.t("login.new_registrations_disabled")
render layout: 'no_ember' render layout: "no_ember"
false false
end end
end end
@ -461,13 +532,14 @@ class InvitesController < ApplicationController
end end
def post_process_invite(user) def post_process_invite(user)
user.enqueue_welcome_message('welcome_invite') if user.send_welcome_message user.enqueue_welcome_message("welcome_invite") if user.send_welcome_message
Group.refresh_automatic_groups!(:admins, :moderators, :staff) if user.staff? Group.refresh_automatic_groups!(:admins, :moderators, :staff) if user.staff?
if user.has_password? if user.has_password?
if !user.active if !user.active
email_token = user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:signup]) email_token =
user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:signup])
EmailToken.enqueue_signup_email(email_token) EmailToken.enqueue_signup_email(email_token)
end end
elsif !SiteSetting.enable_discourse_connect && SiteSetting.enable_local_logins elsif !SiteSetting.enable_discourse_connect && SiteSetting.enable_local_logins
@ -478,17 +550,19 @@ class InvitesController < ApplicationController
def create_topic_invite_notifications(invite, user) def create_topic_invite_notifications(invite, user)
invite.topics.each do |topic| invite.topics.each do |topic|
if user.guardian.can_see?(topic) if user.guardian.can_see?(topic)
last_notification = user.notifications last_notification =
.where(notification_type: Notification.types[:invited_to_topic]) user
.where(topic_id: topic.id) .notifications
.where(post_number: 1) .where(notification_type: Notification.types[:invited_to_topic])
.where('created_at > ?', 1.hour.ago) .where(topic_id: topic.id)
.where(post_number: 1)
.where("created_at > ?", 1.hour.ago)
if !last_notification.exists? if !last_notification.exists?
topic.create_invite_notification!( topic.create_invite_notification!(
user, user,
Notification.types[:invited_to_topic], Notification.types[:invited_to_topic],
invite.invited_by invite.invited_by,
) )
end end
end end

View File

@ -6,45 +6,47 @@ class ListController < ApplicationController
skip_before_action :check_xhr skip_before_action :check_xhr
before_action :set_category, only: [ before_action :set_category,
:category_default, only: [
# filtered topics lists :category_default,
Discourse.filters.map { |f| :"category_#{f}" }, # filtered topics lists
Discourse.filters.map { |f| :"category_none_#{f}" }, Discourse.filters.map { |f| :"category_#{f}" },
# top summaries Discourse.filters.map { |f| :"category_none_#{f}" },
:category_top, # top summaries
:category_none_top, :category_top,
# top pages (ie. with a period) :category_none_top,
TopTopic.periods.map { |p| :"category_top_#{p}" }, # top pages (ie. with a period)
TopTopic.periods.map { |p| :"category_none_top_#{p}" }, TopTopic.periods.map { |p| :"category_top_#{p}" },
# category feeds TopTopic.periods.map { |p| :"category_none_top_#{p}" },
:category_feed, # category feeds
].flatten :category_feed,
].flatten
before_action :ensure_logged_in, except: [ before_action :ensure_logged_in,
:topics_by, except: [
# anonymous filters :topics_by,
Discourse.anonymous_filters, # anonymous filters
Discourse.anonymous_filters.map { |f| "#{f}_feed" }, Discourse.anonymous_filters,
# anonymous categorized filters Discourse.anonymous_filters.map { |f| "#{f}_feed" },
:category_default, # anonymous categorized filters
Discourse.anonymous_filters.map { |f| :"category_#{f}" }, :category_default,
Discourse.anonymous_filters.map { |f| :"category_none_#{f}" }, Discourse.anonymous_filters.map { |f| :"category_#{f}" },
# category feeds Discourse.anonymous_filters.map { |f| :"category_none_#{f}" },
:category_feed, # category feeds
# user topics feed :category_feed,
:user_topics_feed, # user topics feed
# top summaries :user_topics_feed,
:top, # top summaries
:category_top, :top,
:category_none_top, :category_top,
# top pages (ie. with a period) :category_none_top,
TopTopic.periods.map { |p| :"top_#{p}" }, # top pages (ie. with a period)
TopTopic.periods.map { |p| :"top_#{p}_feed" }, TopTopic.periods.map { |p| :"top_#{p}" },
TopTopic.periods.map { |p| :"category_top_#{p}" }, TopTopic.periods.map { |p| :"top_#{p}_feed" },
TopTopic.periods.map { |p| :"category_none_top_#{p}" }, TopTopic.periods.map { |p| :"category_top_#{p}" },
:group_topics TopTopic.periods.map { |p| :"category_none_top_#{p}" },
].flatten :group_topics,
].flatten
# Create our filters # Create our filters
Discourse.filters.each do |filter| Discourse.filters.each do |filter|
@ -52,7 +54,8 @@ class ListController < ApplicationController
list_opts = build_topic_list_options list_opts = build_topic_list_options
list_opts.merge!(options) if options list_opts.merge!(options) if options
user = list_target_user user = list_target_user
if params[:category].blank? && filter == :latest && !SiteSetting.show_category_definitions_in_topic_lists if params[:category].blank? && filter == :latest &&
!SiteSetting.show_category_definitions_in_topic_lists
list_opts[:no_definitions] = true list_opts[:no_definitions] = true
end end
@ -61,17 +64,16 @@ class ListController < ApplicationController
if guardian.can_create_shared_draft? && @category.present? if guardian.can_create_shared_draft? && @category.present?
if @category.id == SiteSetting.shared_drafts_category.to_i if @category.id == SiteSetting.shared_drafts_category.to_i
# On shared drafts, show the destination category # On shared drafts, show the destination category
list.topics.each do |t| list.topics.each { |t| t.includes_destination_category = t.shared_draft.present? }
t.includes_destination_category = t.shared_draft.present?
end
else else
# When viewing a non-shared draft category, find topics whose # When viewing a non-shared draft category, find topics whose
# destination are this category # destination are this category
shared_drafts = TopicQuery.new( shared_drafts =
user, TopicQuery.new(
category: SiteSetting.shared_drafts_category, user,
destination_category_id: list_opts[:category] category: SiteSetting.shared_drafts_category,
).list_latest destination_category_id: list_opts[:category],
).list_latest
if shared_drafts.present? && shared_drafts.topics.present? if shared_drafts.present? && shared_drafts.topics.present?
list.shared_drafts = shared_drafts.topics list.shared_drafts = shared_drafts.topics
@ -90,12 +92,14 @@ class ListController < ApplicationController
if (filter.to_s != current_homepage) && use_crawler_layout? if (filter.to_s != current_homepage) && use_crawler_layout?
filter_title = I18n.t("js.filters.#{filter.to_s}.title", count: 0) filter_title = I18n.t("js.filters.#{filter.to_s}.title", count: 0)
if list_opts[:category] && @category if list_opts[:category] && @category
@title = I18n.t('js.filters.with_category', filter: filter_title, category: @category.name) @title =
I18n.t("js.filters.with_category", filter: filter_title, category: @category.name)
else else
@title = I18n.t('js.filters.with_topics', filter: filter_title) @title = I18n.t("js.filters.with_topics", filter: filter_title)
end end
@title << " - #{SiteSetting.title}" @title << " - #{SiteSetting.title}"
elsif @category.blank? && (filter.to_s == current_homepage) && SiteSetting.short_site_description.present? elsif @category.blank? && (filter.to_s == current_homepage) &&
SiteSetting.short_site_description.present?
@title = "#{SiteSetting.title} - #{SiteSetting.short_site_description}" @title = "#{SiteSetting.title} - #{SiteSetting.short_site_description}"
end end
end end
@ -116,14 +120,21 @@ class ListController < ApplicationController
def category_default def category_default
canonical_url "#{Discourse.base_url_no_prefix}#{@category.url}" canonical_url "#{Discourse.base_url_no_prefix}#{@category.url}"
view_method = @category.default_view view_method = @category.default_view
view_method = 'latest' unless %w(latest top).include?(view_method) view_method = "latest" unless %w[latest top].include?(view_method)
self.public_send(view_method, category: @category.id) self.public_send(view_method, category: @category.id)
end end
def topics_by def topics_by
list_opts = build_topic_list_options 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]) target_user =
fetch_user_from_params(
{
include_inactive:
current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts),
},
%i[user_stat user_option],
)
ensure_can_see_profile!(target_user) ensure_can_see_profile!(target_user)
list = generate_list_for("topics_by", target_user, list_opts) list = generate_list_for("topics_by", target_user, list_opts)
@ -152,14 +163,15 @@ class ListController < ApplicationController
end end
def message_route(action) def message_route(action)
target_user = fetch_user_from_params({ include_inactive: current_user.try(:staff?) }, [:user_stat, :user_option]) target_user =
fetch_user_from_params(
{ include_inactive: current_user.try(:staff?) },
%i[user_stat user_option],
)
case action case action
when :private_messages_unread, when :private_messages_unread, :private_messages_new, :private_messages_group_new,
:private_messages_new,
:private_messages_group_new,
:private_messages_group_unread :private_messages_group_unread
raise Discourse::NotFound if target_user.id != current_user.id raise Discourse::NotFound if target_user.id != current_user.id
when :private_messages_tag when :private_messages_tag
raise Discourse::NotFound if !guardian.can_tag_pms? raise Discourse::NotFound if !guardian.can_tag_pms?
@ -181,7 +193,7 @@ class ListController < ApplicationController
respond_with_list(list) respond_with_list(list)
end end
%i{ %i[
private_messages private_messages
private_messages_sent private_messages_sent
private_messages_unread private_messages_unread
@ -193,14 +205,12 @@ class ListController < ApplicationController
private_messages_group_archive private_messages_group_archive
private_messages_warnings private_messages_warnings
private_messages_tag private_messages_tag
}.each do |action| ].each { |action| generate_message_route(action) }
generate_message_route(action)
end
def latest_feed def latest_feed
discourse_expires_in 1.minute discourse_expires_in 1.minute
options = { order: 'created' }.merge(build_topic_list_options) options = { order: "created" }.merge(build_topic_list_options)
@title = "#{SiteSetting.title} - #{I18n.t("rss_description.latest")}" @title = "#{SiteSetting.title} - #{I18n.t("rss_description.latest")}"
@link = "#{Discourse.base_url}/latest" @link = "#{Discourse.base_url}/latest"
@ -208,7 +218,7 @@ class ListController < ApplicationController
@description = I18n.t("rss_description.latest") @description = I18n.t("rss_description.latest")
@topic_list = TopicQuery.new(nil, options).list_latest @topic_list = TopicQuery.new(nil, options).list_latest
render 'list', formats: [:rss] render "list", formats: [:rss]
end end
def top_feed def top_feed
@ -223,7 +233,7 @@ class ListController < ApplicationController
@topic_list = TopicQuery.new(nil).list_top_for(period) @topic_list = TopicQuery.new(nil).list_top_for(period)
render 'list', formats: [:rss] render "list", formats: [:rss]
end end
def category_feed def category_feed
@ -233,10 +243,11 @@ class ListController < ApplicationController
@title = "#{@category.name} - #{SiteSetting.title}" @title = "#{@category.name} - #{SiteSetting.title}"
@link = "#{Discourse.base_url_no_prefix}#{@category.url}" @link = "#{Discourse.base_url_no_prefix}#{@category.url}"
@atom_link = "#{Discourse.base_url_no_prefix}#{@category.url}.rss" @atom_link = "#{Discourse.base_url_no_prefix}#{@category.url}.rss"
@description = "#{I18n.t('topics_in_category', category: @category.name)} #{@category.description}" @description =
"#{I18n.t("topics_in_category", category: @category.name)} #{@category.description}"
@topic_list = TopicQuery.new(current_user).list_new_in_category(@category) @topic_list = TopicQuery.new(current_user).list_new_in_category(@category)
render 'list', formats: [:rss] render "list", formats: [:rss]
end end
def user_topics_feed def user_topics_feed
@ -244,22 +255,22 @@ class ListController < ApplicationController
target_user = fetch_user_from_params target_user = fetch_user_from_params
ensure_can_see_profile!(target_user) ensure_can_see_profile!(target_user)
@title = "#{SiteSetting.title} - #{I18n.t("rss_description.user_topics", username: target_user.username)}" @title =
"#{SiteSetting.title} - #{I18n.t("rss_description.user_topics", username: target_user.username)}"
@link = "#{target_user.full_url}/activity/topics" @link = "#{target_user.full_url}/activity/topics"
@atom_link = "#{target_user.full_url}/activity/topics.rss" @atom_link = "#{target_user.full_url}/activity/topics.rss"
@description = I18n.t("rss_description.user_topics", username: target_user.username) @description = I18n.t("rss_description.user_topics", username: target_user.username)
@topic_list = TopicQuery @topic_list = TopicQuery.new(nil, order: "created").public_send("list_topics_by", target_user)
.new(nil, order: 'created')
.public_send("list_topics_by", target_user)
render 'list', formats: [:rss] render "list", formats: [:rss]
end end
def top(options = nil) def top(options = nil)
options ||= {} options ||= {}
period = params[:period] period = params[:period]
period ||= ListController.best_period_for(current_user.try(:previous_visit_at), options[:category]) period ||=
ListController.best_period_for(current_user.try(:previous_visit_at), options[:category])
TopTopic.validate_period(period) TopTopic.validate_period(period)
public_send("top_#{period}", options) public_send("top_#{period}", options)
end end
@ -299,10 +310,7 @@ class ListController < ApplicationController
end end
define_method("category_none_top_#{period}") do define_method("category_none_top_#{period}") do
self.public_send("top_#{period}", self.public_send("top_#{period}", category: @category.id, no_subcategories: true)
category: @category.id,
no_subcategories: true
)
end end
# rss feed # rss feed
@ -315,7 +323,7 @@ class ListController < ApplicationController
@atom_link = "#{Discourse.base_url}/top.rss?period=#{period}" @atom_link = "#{Discourse.base_url}/top.rss?period=#{period}"
@topic_list = TopicQuery.new(nil).list_top_for(period) @topic_list = TopicQuery.new(nil).list_top_for(period)
render 'list', formats: [:rss] render "list", formats: [:rss]
end end
end end
@ -337,16 +345,17 @@ class ListController < ApplicationController
private private
def page_params def page_params
route_params = { format: 'json' } route_params = { format: "json" }
if @category.present? if @category.present?
slug_path = @category.slug_path slug_path = @category.slug_path
route_params[:category_slug_path_with_id] = route_params[:category_slug_path_with_id] = (slug_path + [@category.id.to_s]).join("/")
(slug_path + [@category.id.to_s]).join("/")
end end
route_params[:username] = UrlHelper.encode_component(params[:username]) if params[:username].present? route_params[:username] = UrlHelper.encode_component(params[:username]) if params[
:username
].present?
route_params[:period] = params[:period] if params[:period].present? route_params[:period] = params[:period] if params[:period].present?
route_params route_params
end end
@ -355,9 +364,7 @@ class ListController < ApplicationController
category_slug_path_with_id = params.require(:category_slug_path_with_id) category_slug_path_with_id = params.require(:category_slug_path_with_id)
@category = Category.find_by_slug_path_with_id(category_slug_path_with_id) @category = Category.find_by_slug_path_with_id(category_slug_path_with_id)
if @category.nil? raise Discourse::NotFound.new("category not found", check_permalinks: true) if @category.nil?
raise Discourse::NotFound.new("category not found", check_permalinks: true)
end
params[:category] = @category.id.to_s params[:category] = @category.id.to_s
@ -385,13 +392,14 @@ class ListController < ApplicationController
return redirect_to path(url), status: 301 return redirect_to path(url), status: 301
end end
@description_meta = if @category.uncategorized? @description_meta =
I18n.t("category.uncategorized_description", locale: SiteSetting.default_locale) if @category.uncategorized?
elsif @category.description_text.present? I18n.t("category.uncategorized_description", locale: SiteSetting.default_locale)
@category.description_text elsif @category.description_text.present?
else @category.description_text
SiteSetting.site_description else
end SiteSetting.site_description
end
if use_crawler_layout? if use_crawler_layout?
@subcategories = @category.subcategories.select { |c| guardian.can_see?(c) } @subcategories = @category.subcategories.select { |c| guardian.can_see?(c) }
@ -431,7 +439,7 @@ class ListController < ApplicationController
opts.delete(:category) if page_params.include?(:category_slug_path_with_id) opts.delete(:category) if page_params.include?(:category_slug_path_with_id)
url = public_send(method, opts.merge(page_params)).sub('.json?', '?') url = public_send(method, opts.merge(page_params)).sub(".json?", "?")
# Unicode usernames need to be encoded when calling Rails' path helper. However, it means that the already # Unicode usernames need to be encoded when calling Rails' path helper. However, it means that the already
# encoded username are encoded again which we do not want. As such, we unencode the url once when unicode usernames # encoded username are encoded again which we do not want. As such, we unencode the url once when unicode usernames
@ -446,16 +454,24 @@ class ListController < ApplicationController
end end
def self.best_period_for(previous_visit_at, category_id = nil) def self.best_period_for(previous_visit_at, category_id = nil)
default_period = ((category_id && Category.where(id: category_id).pluck_first(:default_top_period)) || default_period =
SiteSetting.top_page_default_timeframe).to_sym (
(category_id && Category.where(id: category_id).pluck_first(:default_top_period)) ||
SiteSetting.top_page_default_timeframe
).to_sym
best_period_with_topics_for(previous_visit_at, category_id, default_period) || default_period best_period_with_topics_for(previous_visit_at, category_id, default_period) || default_period
end end
def self.best_period_with_topics_for(previous_visit_at, category_id = nil, default_period = SiteSetting.top_page_default_timeframe) 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| best_periods_for(previous_visit_at, default_period.to_sym).find do |period|
top_topics = TopTopic.where("#{period}_score > 0") 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.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 = top_topics.limit(SiteSetting.topics_per_period_in_top_page)
top_topics.count == SiteSetting.topics_per_period_in_top_page top_topics.count == SiteSetting.topics_per_period_in_top_page
end end
@ -465,13 +481,12 @@ class ListController < ApplicationController
return [default_period, :all].uniq unless date return [default_period, :all].uniq unless date
periods = [] periods = []
periods << :daily if date > (1.week + 1.day).ago periods << :daily if date > (1.week + 1.day).ago
periods << :weekly if date > (1.month + 1.week).ago periods << :weekly if date > (1.month + 1.week).ago
periods << :monthly if date > (3.months + 3.weeks).ago periods << :monthly if date > (3.months + 3.weeks).ago
periods << :quarterly if date > (1.year + 1.month).ago periods << :quarterly if date > (1.year + 1.month).ago
periods << :yearly if date > 3.years.ago periods << :yearly if date > 3.years.ago
periods << :all periods << :all
periods periods
end end
end end

View File

@ -6,7 +6,7 @@ class MetadataController < ApplicationController
def manifest def manifest
expires_in 1.minutes expires_in 1.minutes
render json: default_manifest.to_json, content_type: 'application/manifest+json' render json: default_manifest.to_json, content_type: "application/manifest+json"
end end
def opensearch def opensearch
@ -17,13 +17,13 @@ class MetadataController < ApplicationController
def app_association_android def app_association_android
raise Discourse::NotFound unless SiteSetting.app_association_android.present? raise Discourse::NotFound unless SiteSetting.app_association_android.present?
expires_in 1.minutes expires_in 1.minutes
render plain: SiteSetting.app_association_android, content_type: 'application/json' render plain: SiteSetting.app_association_android, content_type: "application/json"
end end
def app_association_ios def app_association_ios
raise Discourse::NotFound unless SiteSetting.app_association_ios.present? raise Discourse::NotFound unless SiteSetting.app_association_ios.present?
expires_in 1.minutes expires_in 1.minutes
render plain: SiteSetting.app_association_ios, content_type: 'application/json' render plain: SiteSetting.app_association_ios, content_type: "application/json"
end end
private private
@ -32,56 +32,56 @@ class MetadataController < ApplicationController
display = "standalone" display = "standalone"
if request.user_agent if request.user_agent
regex = Regexp.new(SiteSetting.pwa_display_browser_regex) regex = Regexp.new(SiteSetting.pwa_display_browser_regex)
if regex.match(request.user_agent) display = "browser" if regex.match(request.user_agent)
display = "browser"
end
end end
scheme_id = view_context.scheme_id scheme_id = view_context.scheme_id
primary_color = ColorScheme.hex_for_name('primary', scheme_id) primary_color = ColorScheme.hex_for_name("primary", scheme_id)
icon_url_base = UrlHelper.absolute("/svg-sprite/#{Discourse.current_hostname}/icon/#{primary_color}") icon_url_base =
UrlHelper.absolute("/svg-sprite/#{Discourse.current_hostname}/icon/#{primary_color}")
manifest = { manifest = {
name: SiteSetting.title, name: SiteSetting.title,
short_name: SiteSetting.short_title.presence || SiteSetting.title.truncate(12, separator: ' ', omission: ''), short_name:
SiteSetting.short_title.presence ||
SiteSetting.title.truncate(12, separator: " ", omission: ""),
description: SiteSetting.site_description, description: SiteSetting.site_description,
display: display, display: display,
start_url: Discourse.base_path.present? ? "#{Discourse.base_path}/" : '.', start_url: Discourse.base_path.present? ? "#{Discourse.base_path}/" : ".",
background_color: "##{ColorScheme.hex_for_name('secondary', scheme_id)}", background_color: "##{ColorScheme.hex_for_name("secondary", scheme_id)}",
theme_color: "##{ColorScheme.hex_for_name('header_background', scheme_id)}", theme_color: "##{ColorScheme.hex_for_name("header_background", scheme_id)}",
icons: [ icons: [],
],
share_target: { share_target: {
action: "#{Discourse.base_path}/new-topic", action: "#{Discourse.base_path}/new-topic",
method: "GET", method: "GET",
enctype: "application/x-www-form-urlencoded", enctype: "application/x-www-form-urlencoded",
params: { params: {
title: "title", title: "title",
text: "body" text: "body",
} },
}, },
shortcuts: [ shortcuts: [
{ {
name: I18n.t('js.topic.create_long'), name: I18n.t("js.topic.create_long"),
short_name: I18n.t('js.topic.create'), short_name: I18n.t("js.topic.create"),
url: "#{Discourse.base_path}/new-topic", url: "#{Discourse.base_path}/new-topic",
}, },
{ {
name: I18n.t('js.user.messages.inbox'), name: I18n.t("js.user.messages.inbox"),
short_name: I18n.t('js.user.messages.inbox'), short_name: I18n.t("js.user.messages.inbox"),
url: "#{Discourse.base_path}/my/messages", url: "#{Discourse.base_path}/my/messages",
}, },
{ {
name: I18n.t('js.user.bookmarks'), name: I18n.t("js.user.bookmarks"),
short_name: I18n.t('js.user.bookmarks'), short_name: I18n.t("js.user.bookmarks"),
url: "#{Discourse.base_path}/my/activity/bookmarks", url: "#{Discourse.base_path}/my/activity/bookmarks",
}, },
{ {
name: I18n.t('js.filters.top.title'), name: I18n.t("js.filters.top.title"),
short_name: I18n.t('js.filters.top.title'), short_name: I18n.t("js.filters.top.title"),
url: "#{Discourse.base_path}/top", url: "#{Discourse.base_path}/top",
} },
] ],
} }
logo = SiteSetting.site_manifest_icon_url logo = SiteSetting.site_manifest_icon_url
@ -89,41 +89,40 @@ class MetadataController < ApplicationController
icon_entry = { icon_entry = {
src: UrlHelper.absolute(logo), src: UrlHelper.absolute(logo),
sizes: "512x512", sizes: "512x512",
type: MiniMime.lookup_by_filename(logo)&.content_type || "image/png" type: MiniMime.lookup_by_filename(logo)&.content_type || "image/png",
} }
manifest[:icons] << icon_entry.dup manifest[:icons] << icon_entry.dup
icon_entry[:purpose] = "maskable" icon_entry[:purpose] = "maskable"
manifest[:icons] << icon_entry manifest[:icons] << icon_entry
end end
SiteSetting.manifest_screenshots.split('|').each do |image| SiteSetting
next unless Discourse.store.has_been_uploaded?(image) .manifest_screenshots
.split("|")
.each do |image|
next unless Discourse.store.has_been_uploaded?(image)
upload = Upload.find_by(sha1: Upload.extract_sha1(image)) upload = Upload.find_by(sha1: Upload.extract_sha1(image))
next if upload.nil? next if upload.nil?
manifest[:screenshots] = [] if manifest.dig(:screenshots).nil? manifest[:screenshots] = [] if manifest.dig(:screenshots).nil?
manifest[:screenshots] << { manifest[:screenshots] << {
src: UrlHelper.absolute(image), src: UrlHelper.absolute(image),
sizes: "#{upload.width}x#{upload.height}", sizes: "#{upload.width}x#{upload.height}",
type: "image/#{upload.extension}" type: "image/#{upload.extension}",
} }
end end
if current_user && current_user.trust_level >= 1 && SiteSetting.native_app_install_banner_android if current_user && current_user.trust_level >= 1 &&
manifest = manifest.merge( SiteSetting.native_app_install_banner_android
prefer_related_applications: true, manifest =
related_applications: [ manifest.merge(
{ prefer_related_applications: true,
platform: "play", related_applications: [{ platform: "play", id: SiteSetting.android_app_id }],
id: SiteSetting.android_app_id )
}
]
)
end end
manifest manifest
end end
end end

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class NewTopicController < ApplicationController class NewTopicController < ApplicationController
def index; end def index
end
end end

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class NotificationsController < ApplicationController class NotificationsController < ApplicationController
requires_login requires_login
before_action :ensure_admin, only: [:create, :update, :destroy] before_action :ensure_admin, only: %i[create update destroy]
before_action :set_notification, only: [:update, :destroy] before_action :set_notification, only: %i[update destroy]
def index def index
user = user =
@ -20,9 +19,8 @@ class NotificationsController < ApplicationController
if notification_types = params[:filter_by_types]&.split(",").presence if notification_types = params[:filter_by_types]&.split(",").presence
notification_types.map! do |type| notification_types.map! do |type|
Notification.types[type.to_sym] || ( Notification.types[type.to_sym] ||
raise Discourse::InvalidParameters.new("invalid notification type: #{type}") (raise Discourse::InvalidParameters.new("invalid notification type: #{type}"))
)
end end
end end
@ -35,7 +33,8 @@ class NotificationsController < ApplicationController
if SiteSetting.legacy_navigation_menu? if SiteSetting.legacy_navigation_menu?
notifications = Notification.recent_report(current_user, limit, notification_types) notifications = Notification.recent_report(current_user, limit, notification_types)
else else
notifications = Notification.prioritized_list(current_user, count: limit, types: notification_types) notifications =
Notification.prioritized_list(current_user, count: limit, types: notification_types)
# notification_types is blank for the "all notifications" user menu tab # notification_types is blank for the "all notifications" user menu tab
include_reviewables = notification_types.blank? && guardian.can_see_review_queue? include_reviewables = notification_types.blank? && guardian.can_see_review_queue?
end end
@ -47,7 +46,8 @@ class NotificationsController < ApplicationController
end end
end end
if !params.has_key?(:silent) && params[:bump_last_seen_reviewable] && !@readonly_mode && include_reviewables if !params.has_key?(:silent) && params[:bump_last_seen_reviewable] && !@readonly_mode &&
include_reviewables
current_user_id = current_user.id current_user_id = current_user.id
Scheduler::Defer.later "bump last seen reviewable for user" do Scheduler::Defer.later "bump last seen reviewable for user" do
# we lookup current_user again in the background thread to avoid # we lookup current_user again in the background thread to avoid
@ -62,13 +62,13 @@ class NotificationsController < ApplicationController
json = { json = {
notifications: serialize_data(notifications, NotificationSerializer), notifications: serialize_data(notifications, NotificationSerializer),
seen_notification_id: current_user.seen_notification_id seen_notification_id: current_user.seen_notification_id,
} }
if include_reviewables if include_reviewables
json[:pending_reviewables] = Reviewable.basic_serializers_for_list( json[:pending_reviewables] = Reviewable.basic_serializers_for_list(
Reviewable.user_menu_list_for(current_user), Reviewable.user_menu_list_for(current_user),
current_user current_user,
).as_json ).as_json
end end
@ -76,10 +76,8 @@ class NotificationsController < ApplicationController
else else
offset = params[:offset].to_i offset = params[:offset].to_i
notifications = Notification.where(user_id: user.id) notifications =
.visible Notification.where(user_id: user.id).visible.includes(:topic).order(created_at: :desc)
.includes(:topic)
.order(created_at: :desc)
notifications = notifications.where(read: true) if params[:filter] == "read" notifications = notifications.where(read: true) if params[:filter] == "read"
@ -88,12 +86,14 @@ class NotificationsController < ApplicationController
total_rows = notifications.dup.count total_rows = notifications.dup.count
notifications = notifications.offset(offset).limit(60) notifications = notifications.offset(offset).limit(60)
notifications = filter_inaccessible_notifications(notifications) notifications = filter_inaccessible_notifications(notifications)
render_json_dump(notifications: serialize_data(notifications, NotificationSerializer), render_json_dump(
total_rows_notifications: total_rows, notifications: serialize_data(notifications, NotificationSerializer),
seen_notification_id: user.seen_notification_id, total_rows_notifications: total_rows,
load_more_notifications: notifications_path(username: user.username, offset: offset + 60, filter: params[:filter])) seen_notification_id: user.seen_notification_id,
load_more_notifications:
notifications_path(username: user.username, offset: offset + 60, filter: params[:filter]),
)
end end
end end
def mark_read def mark_read
@ -144,7 +144,15 @@ class NotificationsController < ApplicationController
end end
def notification_params def notification_params
params.permit(:notification_type, :user_id, :data, :read, :topic_id, :post_number, :post_action_id) params.permit(
:notification_type,
:user_id,
:data,
:read,
:topic_id,
:post_number,
:post_action_id,
)
end end
def render_notification def render_notification

View File

@ -5,6 +5,6 @@ class OfflineController < ApplicationController
skip_before_action :preload_json, :check_xhr, :redirect_to_login_if_required skip_before_action :preload_json, :check_xhr, :redirect_to_login_if_required
def index def index
render :offline, content_type: 'text/html' render :offline, content_type: "text/html"
end end
end end

View File

@ -4,7 +4,7 @@ class OneboxController < ApplicationController
requires_login requires_login
def show def show
unless params[:refresh] == 'true' unless params[:refresh] == "true"
preview = Oneboxer.cached_preview(params[:url]) preview = Oneboxer.cached_preview(params[:url])
preview = preview.strip if preview.present? preview = preview.strip if preview.present?
return render(plain: preview) if preview.present? return render(plain: preview) if preview.present?
@ -16,7 +16,7 @@ class OneboxController < ApplicationController
user_id = current_user.id user_id = current_user.id
category_id = params[:category_id].to_i category_id = params[:category_id].to_i
topic_id = params[:topic_id].to_i topic_id = params[:topic_id].to_i
invalidate = params[:refresh] == 'true' invalidate = params[:refresh] == "true"
url = params[:url] url = params[:url]
return render(body: nil, status: 404) if Oneboxer.recently_failed?(url) return render(body: nil, status: 404) if Oneboxer.recently_failed?(url)
@ -24,12 +24,14 @@ class OneboxController < ApplicationController
hijack(info: "#{url} topic_id: #{topic_id} user_id: #{user_id}") do hijack(info: "#{url} topic_id: #{topic_id} user_id: #{user_id}") do
Oneboxer.preview_onebox!(user_id) Oneboxer.preview_onebox!(user_id)
preview = Oneboxer.preview(url, preview =
invalidate_oneboxes: invalidate, Oneboxer.preview(
user_id: user_id, url,
category_id: category_id, invalidate_oneboxes: invalidate,
topic_id: topic_id user_id: user_id,
) category_id: category_id,
topic_id: topic_id,
)
preview = preview.strip if preview.present? preview = preview.strip if preview.present?
@ -43,5 +45,4 @@ class OneboxController < ApplicationController
end end
end end
end end
end end

View File

@ -33,12 +33,8 @@ class PermalinksController < ApplicationController
render json: MultiJson.dump(data) render json: MultiJson.dump(data)
rescue Discourse::NotFound rescue Discourse::NotFound
data = { data = { found: false, html: build_not_found_page(status: 200) }
found: false,
html: build_not_found_page(status: 200),
}
render json: MultiJson.dump(data) render json: MultiJson.dump(data)
end end
end end
end end

View File

@ -23,11 +23,14 @@ class PostActionUsersController < ApplicationController
unknown_user_ids.merge(result) unknown_user_ids.merge(result)
end end
post_actions = post.post_actions.where(post_action_type_id: post_action_type_id) post_actions =
.includes(:user) post
.offset(page * page_size) .post_actions
.order('post_actions.created_at ASC') .where(post_action_type_id: post_action_type_id)
.limit(page_size) .includes(:user)
.offset(page * page_size)
.order("post_actions.created_at ASC")
.limit(page_size)
if !guardian.can_see_post_actors?(post.topic, post_action_type_id) if !guardian.can_see_post_actors?(post.topic, post_action_type_id)
raise Discourse::InvalidAccess unless current_user raise Discourse::InvalidAccess unless current_user
@ -38,16 +41,15 @@ class PostActionUsersController < ApplicationController
total_count = post["#{action_type}_count"].to_i total_count = post["#{action_type}_count"].to_i
data = { data = {
post_action_users: serialize_data( post_action_users:
post_actions.to_a, serialize_data(
PostActionUserSerializer, post_actions.to_a,
unknown_user_ids: unknown_user_ids PostActionUserSerializer,
) unknown_user_ids: unknown_user_ids,
),
} }
if total_count > page_size data[:total_rows_post_action_users] = total_count if total_count > page_size
data[:total_rows_post_action_users] = total_count
end
render_json_dump(data) render_json_dump(data)
end end

View File

@ -9,16 +9,17 @@ class PostActionsController < ApplicationController
def create def create
raise Discourse::NotFound if @post.blank? raise Discourse::NotFound if @post.blank?
creator = PostActionCreator.new( creator =
current_user, PostActionCreator.new(
@post, current_user,
@post_action_type_id, @post,
is_warning: params[:is_warning], @post_action_type_id,
message: params[:message], is_warning: params[:is_warning],
take_action: params[:take_action] == 'true', message: params[:message],
flag_topic: params[:flag_topic] == 'true', take_action: params[:take_action] == "true",
queue_for_review: params[:queue_for_review] == 'true' flag_topic: params[:flag_topic] == "true",
) queue_for_review: params[:queue_for_review] == "true",
)
result = creator.perform result = creator.perform
if result.failed? if result.failed?
@ -29,19 +30,20 @@ class PostActionsController < ApplicationController
if @post_action_type_id == PostActionType.types[:like] if @post_action_type_id == PostActionType.types[:like]
limiter = result.post_action.post_action_rate_limiter limiter = result.post_action.post_action_rate_limiter
response.headers['Discourse-Actions-Remaining'] = limiter.remaining.to_s response.headers["Discourse-Actions-Remaining"] = limiter.remaining.to_s
response.headers['Discourse-Actions-Max'] = limiter.max.to_s response.headers["Discourse-Actions-Max"] = limiter.max.to_s
end end
render_post_json(@post, add_raw: false) render_post_json(@post, add_raw: false)
end end
end end
def destroy def destroy
result = PostActionDestroyer.new( result =
current_user, PostActionDestroyer.new(
Post.find_by(id: params[:id].to_i), current_user,
@post_action_type_id Post.find_by(id: params[:id].to_i),
).perform @post_action_type_id,
).perform
if result.failed? if result.failed?
render_json_error(result) render_json_error(result)
@ -58,15 +60,16 @@ class PostActionsController < ApplicationController
flag_topic = params[:flag_topic] flag_topic = params[:flag_topic]
flag_topic = flag_topic && (flag_topic == true || flag_topic == "true") flag_topic = flag_topic && (flag_topic == true || flag_topic == "true")
post_id = if flag_topic post_id =
begin if flag_topic
Topic.find(params[:id]).posts.first.id begin
rescue Topic.find(params[:id]).posts.first.id
raise Discourse::NotFound rescue StandardError
raise Discourse::NotFound
end
else
params[:id]
end end
else
params[:id]
end
finder = Post.where(id: post_id) finder = Post.where(id: post_id)

View File

@ -7,23 +7,30 @@ class PostReadersController < ApplicationController
post = Post.includes(topic: %i[topic_allowed_groups topic_allowed_users]).find(params[:id]) post = Post.includes(topic: %i[topic_allowed_groups topic_allowed_users]).find(params[:id])
ensure_can_see_readers!(post) ensure_can_see_readers!(post)
readers = User readers =
.real User
.where(staged: false) .real
.where.not(id: post.user_id) .where(staged: false)
.joins(:topic_users) .where.not(id: post.user_id)
.where.not(topic_users: { last_read_post_number: nil }) .joins(:topic_users)
.where('topic_users.topic_id = ? AND topic_users.last_read_post_number >= ?', post.topic_id, post.post_number) .where.not(topic_users: { last_read_post_number: nil })
.where(
"topic_users.topic_id = ? AND topic_users.last_read_post_number >= ?",
post.topic_id,
post.post_number,
)
readers = readers.where('admin OR moderator') if post.whisper? readers = readers.where("admin OR moderator") if post.whisper?
readers = readers.map do |r| readers =
{ readers.map do |r|
id: r.id, avatar_template: r.avatar_template, {
username: r.username, id: r.id,
username_lower: r.username_lower avatar_template: r.avatar_template,
} username: r.username,
end username_lower: r.username_lower,
}
end
render_json_dump(post_readers: readers) render_json_dump(post_readers: readers)
end end
@ -31,10 +38,17 @@ class PostReadersController < ApplicationController
private private
def ensure_can_see_readers!(post) def ensure_can_see_readers!(post)
show_readers = GroupUser show_readers =
.where(user: current_user) GroupUser
.joins(:group) .where(user: current_user)
.where(groups: { id: post.topic.topic_allowed_groups.map(&:group_id), publish_read_state: true }).exists? .joins(:group)
.where(
groups: {
id: post.topic.topic_allowed_groups.map(&:group_id),
publish_read_state: true,
},
)
.exists?
raise Discourse::InvalidAccess unless show_readers raise Discourse::InvalidAccess unless show_readers
end end

View File

@ -5,31 +5,27 @@ class PostsController < ApplicationController
# see https://github.com/rails/rails/issues/44867 # see https://github.com/rails/rails/issues/44867
self._flash_types -= [:notice] self._flash_types -= [:notice]
requires_login except: [ requires_login except: %i[
:show, show
:replies, replies
:by_number, by_number
:by_date, by_date
:short_link, short_link
:reply_history, reply_history
:reply_ids, reply_ids
:revisions, revisions
:latest_revision, latest_revision
:expand_embed, expand_embed
:markdown_id, markdown_id
:markdown_num, markdown_num
:cooked, cooked
:latest, latest
:user_posts_feed user_posts_feed
] ]
skip_before_action :preload_json, :check_xhr, only: [ skip_before_action :preload_json,
:markdown_id, :check_xhr,
:markdown_num, only: %i[markdown_id markdown_num short_link latest user_posts_feed]
:short_link,
:latest,
:user_posts_feed
]
MARKDOWN_TOPIC_PAGE_SIZE ||= 100 MARKDOWN_TOPIC_PAGE_SIZE ||= 100
@ -42,13 +38,15 @@ class PostsController < ApplicationController
post_revision = find_post_revision_from_topic_id post_revision = find_post_revision_from_topic_id
render plain: post_revision.modifications[:raw].last render plain: post_revision.modifications[:raw].last
elsif params[:post_number].present? elsif params[:post_number].present?
markdown Post.find_by(topic_id: params[:topic_id].to_i, post_number: params[:post_number].to_i) markdown Post.find_by(
topic_id: params[:topic_id].to_i,
post_number: params[:post_number].to_i,
)
else else
opts = params.slice(:page) opts = params.slice(:page)
opts[:limit] = MARKDOWN_TOPIC_PAGE_SIZE opts[:limit] = MARKDOWN_TOPIC_PAGE_SIZE
topic_view = TopicView.new(params[:topic_id], current_user, opts) topic_view = TopicView.new(params[:topic_id], current_user, opts)
content = topic_view.posts.map do |p| content = topic_view.posts.map { |p| <<~MD }
<<~MD
#{p.user.username} | #{p.updated_at} | ##{p.post_number} #{p.user.username} | #{p.updated_at} | ##{p.post_number}
#{p.raw} #{p.raw}
@ -56,7 +54,6 @@ class PostsController < ApplicationController
------------------------- -------------------------
MD MD
end
render plain: content.join render plain: content.join
end end
end end
@ -68,26 +65,30 @@ class PostsController < ApplicationController
if params[:id] == "private_posts" if params[:id] == "private_posts"
raise Discourse::NotFound if current_user.nil? raise Discourse::NotFound if current_user.nil?
posts = Post.private_posts posts =
.order(created_at: :desc) Post
.where('posts.id <= ?', last_post_id) .private_posts
.where('posts.id > ?', last_post_id - 50) .order(created_at: :desc)
.includes(topic: :category) .where("posts.id <= ?", last_post_id)
.includes(user: [:primary_group, :flair_group]) .where("posts.id > ?", last_post_id - 50)
.includes(:reply_to_user) .includes(topic: :category)
.limit(50) .includes(user: %i[primary_group flair_group])
.includes(:reply_to_user)
.limit(50)
rss_description = I18n.t("rss_description.private_posts") rss_description = I18n.t("rss_description.private_posts")
else else
posts = Post.public_posts posts =
.visible Post
.where(post_type: Post.types[:regular]) .public_posts
.order(created_at: :desc) .visible
.where('posts.id <= ?', last_post_id) .where(post_type: Post.types[:regular])
.where('posts.id > ?', last_post_id - 50) .order(created_at: :desc)
.includes(topic: :category) .where("posts.id <= ?", last_post_id)
.includes(user: [:primary_group, :flair_group]) .where("posts.id > ?", last_post_id - 50)
.includes(:reply_to_user) .includes(topic: :category)
.limit(50) .includes(user: %i[primary_group flair_group])
.includes(:reply_to_user)
.limit(50)
rss_description = I18n.t("rss_description.posts") rss_description = I18n.t("rss_description.posts")
@use_canonical = true @use_canonical = true
end end
@ -103,17 +104,20 @@ class PostsController < ApplicationController
@title = "#{SiteSetting.title} - #{rss_description}" @title = "#{SiteSetting.title} - #{rss_description}"
@link = Discourse.base_url @link = Discourse.base_url
@description = rss_description @description = rss_description
render 'posts/latest', formats: [:rss] render "posts/latest", formats: [:rss]
end end
format.json do format.json do
render_json_dump(serialize_data(posts, render_json_dump(
PostSerializer, serialize_data(
scope: guardian, posts,
root: params[:id], PostSerializer,
add_raw: true, scope: guardian,
add_title: true, root: params[:id],
all_post_actions: counts) add_raw: true,
) add_title: true,
all_post_actions: counts,
),
)
end end
end end
end end
@ -123,35 +127,33 @@ class PostsController < ApplicationController
user = fetch_user_from_params user = fetch_user_from_params
raise Discourse::NotFound unless guardian.can_see_profile?(user) raise Discourse::NotFound unless guardian.can_see_profile?(user)
posts = Post.public_posts posts =
.visible Post
.where(user_id: user.id) .public_posts
.where(post_type: Post.types[:regular]) .visible
.order(created_at: :desc) .where(user_id: user.id)
.includes(:user) .where(post_type: Post.types[:regular])
.includes(topic: :category) .order(created_at: :desc)
.limit(50) .includes(:user)
.includes(topic: :category)
.limit(50)
posts = posts.reject { |post| !guardian.can_see?(post) || post.topic.blank? } posts = posts.reject { |post| !guardian.can_see?(post) || post.topic.blank? }
respond_to do |format| respond_to do |format|
format.rss do format.rss do
@posts = posts @posts = posts
@title = "#{SiteSetting.title} - #{I18n.t("rss_description.user_posts", username: user.username)}" @title =
"#{SiteSetting.title} - #{I18n.t("rss_description.user_posts", username: user.username)}"
@link = "#{user.full_url}/activity" @link = "#{user.full_url}/activity"
@description = I18n.t("rss_description.user_posts", username: user.username) @description = I18n.t("rss_description.user_posts", username: user.username)
render 'posts/latest', formats: [:rss] render "posts/latest", formats: [:rss]
end end
format.json do format.json do
render_json_dump(serialize_data(posts, render_json_dump(serialize_data(posts, PostSerializer, scope: guardian, add_excerpt: true))
PostSerializer,
scope: guardian,
add_excerpt: true)
)
end end
end end
end end
def cooked def cooked
@ -173,7 +175,7 @@ class PostsController < ApplicationController
# Stuff the user in the request object, because that's what IncomingLink wants # Stuff the user in the request object, because that's what IncomingLink wants
if params[:user_id] if params[:user_id]
user = User.find_by(id: params[:user_id].to_i) user = User.find_by(id: params[:user_id].to_i)
request['u'] = user.username_lower if user request["u"] = user.username_lower if user
end end
guardian.ensure_can_see!(post) guardian.ensure_can_see!(post)
@ -188,13 +190,14 @@ class PostsController < ApplicationController
manager = NewPostManager.new(current_user, @manager_params) manager = NewPostManager.new(current_user, @manager_params)
if is_api? if is_api?
memoized_payload = DistributedMemoizer.memoize(signature_for(@manager_params), 120) do memoized_payload =
result = manager.perform DistributedMemoizer.memoize(signature_for(@manager_params), 120) do
MultiJson.dump(serialize_data(result, NewPostResultSerializer, root: false)) result = manager.perform
end MultiJson.dump(serialize_data(result, NewPostResultSerializer, root: false))
end
parsed_payload = JSON.parse(memoized_payload) parsed_payload = JSON.parse(memoized_payload)
backwards_compatible_json(parsed_payload, parsed_payload['success']) backwards_compatible_json(parsed_payload, parsed_payload["success"])
else else
result = manager.perform result = manager.perform
json = serialize_data(result, NewPostResultSerializer, root: false) json = serialize_data(result, NewPostResultSerializer, root: false)
@ -213,27 +216,20 @@ class PostsController < ApplicationController
post.image_sizes = params[:image_sizes] if params[:image_sizes].present? post.image_sizes = params[:image_sizes] if params[:image_sizes].present?
if !guardian.public_send("can_edit?", post) && if !guardian.public_send("can_edit?", post) && post.user_id == current_user.id &&
post.user_id == current_user.id && post.edit_time_limit_expired?(current_user)
post.edit_time_limit_expired?(current_user) return render_json_error(I18n.t("too_late_to_edit"))
return render_json_error(I18n.t('too_late_to_edit'))
end end
guardian.ensure_can_edit!(post) guardian.ensure_can_edit!(post)
changes = { changes = { raw: params[:post][:raw], edit_reason: params[:post][:edit_reason] }
raw: params[:post][:raw],
edit_reason: params[:post][:edit_reason]
}
Post.plugin_permitted_update_params.keys.each do |param| Post.plugin_permitted_update_params.keys.each { |param| changes[param] = params[:post][param] }
changes[param] = params[:post][param]
end
raw_old = params[:post][:raw_old] raw_old = params[:post][:raw_old]
if raw_old.present? && raw_old != post.raw if raw_old.present? && raw_old != post.raw
return render_json_error(I18n.t('edit_conflict'), status: 409) return render_json_error(I18n.t("edit_conflict"), status: 409)
end end
# to stay consistent with the create api, we allow for title & category changes here # to stay consistent with the create api, we allow for title & category changes here
@ -246,7 +242,7 @@ class PostsController < ApplicationController
if category || (changes[:category_id].to_i == 0) if category || (changes[:category_id].to_i == 0)
guardian.ensure_can_move_topic_to_category!(category) guardian.ensure_can_move_topic_to_category!(category)
else else
return render_json_error(I18n.t('category.errors.not_found')) return render_json_error(I18n.t("category.errors.not_found"))
end end
end end
end end
@ -273,7 +269,11 @@ class PostsController < ApplicationController
result = { post: post_serializer.as_json } result = { post: post_serializer.as_json }
if revisor.category_changed.present? if revisor.category_changed.present?
result[:category] = BasicCategorySerializer.new(revisor.category_changed, scope: guardian, root: false).as_json result[:category] = BasicCategorySerializer.new(
revisor.category_changed,
scope: guardian,
root: false,
).as_json
end end
render_json_dump(result) render_json_dump(result)
@ -303,11 +303,7 @@ class PostsController < ApplicationController
user_custom_fields = User.custom_fields_for_ids(reply_history.pluck(:user_id), added_fields) user_custom_fields = User.custom_fields_for_ids(reply_history.pluck(:user_id), added_fields)
end end
render_serialized( render_serialized(reply_history, PostSerializer, user_custom_fields: user_custom_fields)
reply_history,
PostSerializer,
user_custom_fields: user_custom_fields
)
end end
def reply_ids def reply_ids
@ -335,15 +331,25 @@ class PostsController < ApplicationController
end end
unless guardian.can_moderate_topic?(post.topic) unless guardian.can_moderate_topic?(post.topic)
RateLimiter.new(current_user, "delete_post_per_min", SiteSetting.max_post_deletions_per_minute, 1.minute).performed! RateLimiter.new(
RateLimiter.new(current_user, "delete_post_per_day", SiteSetting.max_post_deletions_per_day, 1.day).performed! current_user,
"delete_post_per_min",
SiteSetting.max_post_deletions_per_minute,
1.minute,
).performed!
RateLimiter.new(
current_user,
"delete_post_per_day",
SiteSetting.max_post_deletions_per_day,
1.day,
).performed!
end end
PostDestroyer.new( PostDestroyer.new(
current_user, current_user,
post, post,
context: params[:context], context: params[:context],
force_destroy: force_destroy force_destroy: force_destroy,
).destroy ).destroy
render body: nil render body: nil
@ -351,8 +357,8 @@ class PostsController < ApplicationController
def expand_embed def expand_embed
render json: { cooked: TopicEmbed.expanded_for(find_post_from_params) } render json: { cooked: TopicEmbed.expanded_for(find_post_from_params) }
rescue rescue StandardError
render_json_error I18n.t('errors.embed.load_from_remote') render_json_error I18n.t("errors.embed.load_from_remote")
end end
def recover def recover
@ -360,8 +366,18 @@ class PostsController < ApplicationController
guardian.ensure_can_recover_post!(post) guardian.ensure_can_recover_post!(post)
unless guardian.can_moderate_topic?(post.topic) unless guardian.can_moderate_topic?(post.topic)
RateLimiter.new(current_user, "delete_post_per_min", SiteSetting.max_post_deletions_per_minute, 1.minute).performed! RateLimiter.new(
RateLimiter.new(current_user, "delete_post_per_day", SiteSetting.max_post_deletions_per_day, 1.day).performed! current_user,
"delete_post_per_min",
SiteSetting.max_post_deletions_per_minute,
1.minute,
).performed!
RateLimiter.new(
current_user,
"delete_post_per_day",
SiteSetting.max_post_deletions_per_day,
1.day,
).performed!
end end
destroyer = PostDestroyer.new(current_user, post) destroyer = PostDestroyer.new(current_user, post)
@ -383,7 +399,11 @@ class PostsController < ApplicationController
Post.transaction do Post.transaction do
posts.each_with_index do |p, i| posts.each_with_index do |p, i|
PostDestroyer.new(current_user, p, defer_flags: !(agree_with_first_reply_flag && i == 0)).destroy PostDestroyer.new(
current_user,
p,
defer_flags: !(agree_with_first_reply_flag && i == 0),
).destroy
end end
end end
@ -418,7 +438,8 @@ class PostsController < ApplicationController
raise Discourse::NotFound if post.hidden && !guardian.can_view_hidden_post_revisions? raise Discourse::NotFound if post.hidden && !guardian.can_view_hidden_post_revisions?
post_revision = find_post_revision_from_params post_revision = find_post_revision_from_params
post_revision_serializer = PostRevisionSerializer.new(post_revision, scope: guardian, root: false) post_revision_serializer =
PostRevisionSerializer.new(post_revision, scope: guardian, root: false)
render_json_dump(post_revision_serializer) render_json_dump(post_revision_serializer)
end end
@ -427,7 +448,8 @@ class PostsController < ApplicationController
raise Discourse::NotFound if post.hidden && !guardian.can_view_hidden_post_revisions? raise Discourse::NotFound if post.hidden && !guardian.can_view_hidden_post_revisions?
post_revision = find_latest_post_revision_from_params post_revision = find_latest_post_revision_from_params
post_revision_serializer = PostRevisionSerializer.new(post_revision, scope: guardian, root: false) post_revision_serializer =
PostRevisionSerializer.new(post_revision, scope: guardian, root: false)
render_json_dump(post_revision_serializer) render_json_dump(post_revision_serializer)
end end
@ -473,17 +495,27 @@ class PostsController < ApplicationController
post_revision.post = post post_revision.post = post
guardian.ensure_can_see!(post_revision) guardian.ensure_can_see!(post_revision)
guardian.ensure_can_edit!(post) guardian.ensure_can_edit!(post)
return render_json_error(I18n.t('revert_version_same')) if post_revision.modifications["raw"].blank? && post_revision.modifications["title"].blank? && post_revision.modifications["category_id"].blank? if post_revision.modifications["raw"].blank? && post_revision.modifications["title"].blank? &&
post_revision.modifications["category_id"].blank?
return render_json_error(I18n.t("revert_version_same"))
end
topic = Topic.with_deleted.find(post.topic_id) topic = Topic.with_deleted.find(post.topic_id)
changes = {} changes = {}
changes[:raw] = post_revision.modifications["raw"][0] if post_revision.modifications["raw"].present? && post_revision.modifications["raw"][0] != post.raw changes[:raw] = post_revision.modifications["raw"][0] if post_revision.modifications[
"raw"
].present? && post_revision.modifications["raw"][0] != post.raw
if post.is_first_post? if post.is_first_post?
changes[:title] = post_revision.modifications["title"][0] if post_revision.modifications["title"].present? && post_revision.modifications["title"][0] != topic.title changes[:title] = post_revision.modifications["title"][0] if post_revision.modifications[
changes[:category_id] = post_revision.modifications["category_id"][0] if post_revision.modifications["category_id"].present? && post_revision.modifications["category_id"][0] != topic.category.id "title"
].present? && post_revision.modifications["title"][0] != topic.title
changes[:category_id] = post_revision.modifications["category_id"][
0
] if post_revision.modifications["category_id"].present? &&
post_revision.modifications["category_id"][0] != topic.category.id
end end
return render_json_error(I18n.t('revert_version_same')) unless changes.length > 0 return render_json_error(I18n.t("revert_version_same")) unless changes.length > 0
changes[:edit_reason] = "reverted to version ##{post_revision.number.to_i - 1}" changes[:edit_reason] = "reverted to version ##{post_revision.number.to_i - 1}"
revisor = PostRevisor.new(post, topic) revisor = PostRevisor.new(post, topic)
@ -500,8 +532,14 @@ class PostsController < ApplicationController
result = { post: post_serializer.as_json } result = { post: post_serializer.as_json }
if post.is_first_post? if post.is_first_post?
result[:topic] = BasicTopicSerializer.new(topic, scope: guardian, root: false).as_json if post_revision.modifications["title"].present? result[:topic] = BasicTopicSerializer.new(
result[:category_id] = post_revision.modifications["category_id"][0] if post_revision.modifications["category_id"].present? topic,
scope: guardian,
root: false,
).as_json if post_revision.modifications["title"].present?
result[:category_id] = post_revision.modifications["category_id"][
0
] if post_revision.modifications["category_id"].present?
end end
render_json_dump(result) render_json_dump(result)
@ -524,7 +562,7 @@ class PostsController < ApplicationController
post.custom_fields[Post::NOTICE] = { post.custom_fields[Post::NOTICE] = {
type: Post.notices[:custom], type: Post.notices[:custom],
raw: params[:notice], raw: params[:notice],
cooked: PrettyText.cook(params[:notice], features: { onebox: false }) cooked: PrettyText.cook(params[:notice], features: { onebox: false }),
} }
else else
post.custom_fields.delete(Post::NOTICE) post.custom_fields.delete(Post::NOTICE)
@ -535,7 +573,7 @@ class PostsController < ApplicationController
StaffActionLogger.new(current_user).log_post_staff_note( StaffActionLogger.new(current_user).log_post_staff_note(
post, post,
old_value: old_notice&.[]("raw"), old_value: old_notice&.[]("raw"),
new_value: params[:notice] new_value: params[:notice],
) )
render body: nil render body: nil
@ -544,14 +582,16 @@ class PostsController < ApplicationController
def destroy_bookmark def destroy_bookmark
params.require(:post_id) params.require(:post_id)
bookmark_id = Bookmark.where( bookmark_id =
bookmarkable_id: params[:post_id], Bookmark.where(
bookmarkable_type: "Post", bookmarkable_id: params[:post_id],
user_id: current_user.id bookmarkable_type: "Post",
).pluck_first(:id) user_id: current_user.id,
).pluck_first(:id)
destroyed_bookmark = BookmarkManager.new(current_user).destroy(bookmark_id) destroyed_bookmark = BookmarkManager.new(current_user).destroy(bookmark_id)
render json: success_json.merge(BookmarkManager.bookmark_metadata(destroyed_bookmark, current_user)) render json:
success_json.merge(BookmarkManager.bookmark_metadata(destroyed_bookmark, current_user))
end end
def wiki def wiki
@ -596,8 +636,9 @@ class PostsController < ApplicationController
def flagged_posts def flagged_posts
Discourse.deprecate( Discourse.deprecate(
'PostsController#flagged_posts is deprecated. Please use /review instead.', "PostsController#flagged_posts is deprecated. Please use /review instead.",
since: '2.8.0.beta4', drop_from: '2.9' since: "2.8.0.beta4",
drop_from: "2.9",
) )
params.permit(:offset, :limit) params.permit(:offset, :limit)
@ -607,10 +648,14 @@ class PostsController < ApplicationController
offset = [params[:offset].to_i, 0].max offset = [params[:offset].to_i, 0].max
limit = [(params[:limit] || 60).to_i, 100].min limit = [(params[:limit] || 60).to_i, 100].min
posts = user_posts(guardian, user.id, offset: offset, limit: limit) posts =
.where(id: PostAction.where(post_action_type_id: PostActionType.notify_flag_type_ids) user_posts(guardian, user.id, offset: offset, limit: limit).where(
.where(disagreed_at: nil) id:
.select(:post_id)) PostAction
.where(post_action_type_id: PostActionType.notify_flag_type_ids)
.where(disagreed_at: nil)
.select(:post_id),
)
render_serialized(posts, AdminUserActionSerializer) render_serialized(posts, AdminUserActionSerializer)
end end
@ -633,7 +678,11 @@ class PostsController < ApplicationController
user = fetch_user_from_params user = fetch_user_from_params
raise Discourse::NotFound unless guardian.can_edit_user?(user) raise Discourse::NotFound unless guardian.can_edit_user?(user)
render_serialized(user.pending_posts.order(created_at: :desc), PendingPostSerializer, root: :pending_posts) render_serialized(
user.pending_posts.order(created_at: :desc),
PendingPostSerializer,
root: :pending_posts,
)
end end
protected protected
@ -692,7 +741,8 @@ class PostsController < ApplicationController
end end
def find_post_revision_from_topic_id def find_post_revision_from_topic_id
post = Post.find_by(topic_id: params[:topic_id].to_i, post_number: (params[:post_number] || 1).to_i) post =
Post.find_by(topic_id: params[:topic_id].to_i, post_number: (params[:post_number] || 1).to_i)
raise Discourse::NotFound unless guardian.can_see?(post) raise Discourse::NotFound unless guardian.can_see?(post)
revision = params[:revision].to_i revision = params[:revision].to_i
@ -711,26 +761,26 @@ class PostsController < ApplicationController
def user_posts(guardian, user_id, opts) def user_posts(guardian, user_id, opts)
# Topic.unscoped is necessary to remove the default deleted_at: nil scope # Topic.unscoped is necessary to remove the default deleted_at: nil scope
posts = Topic.unscoped do posts =
Post.includes(:user, :topic, :deleted_by, :user_actions) Topic.unscoped do
.where(user_id: user_id) Post
.with_deleted .includes(:user, :topic, :deleted_by, :user_actions)
.order(created_at: :desc) .where(user_id: user_id)
end .with_deleted
.order(created_at: :desc)
end
if guardian.user.moderator? if guardian.user.moderator?
# Awful hack, but you can't seem to remove the `default_scope` when joining # Awful hack, but you can't seem to remove the `default_scope` when joining
# So instead I grab the topics separately # So instead I grab the topics separately
topic_ids = posts.dup.pluck(:topic_id) topic_ids = posts.dup.pluck(:topic_id)
topics = Topic.where(id: topic_ids).with_deleted.where.not(archetype: 'private_message') topics = Topic.where(id: topic_ids).with_deleted.where.not(archetype: "private_message")
topics = topics.secured(guardian) topics = topics.secured(guardian)
posts = posts.where(topic_id: topics.pluck(:id)) posts = posts.where(topic_id: topics.pluck(:id))
end end
posts.offset(opts[:offset]) posts.offset(opts[:offset]).limit(opts[:limit])
.limit(opts[:limit])
end end
def create_params def create_params
@ -747,18 +797,18 @@ class PostsController < ApplicationController
:typing_duration_msecs, :typing_duration_msecs,
:composer_open_duration_msecs, :composer_open_duration_msecs,
:visible, :visible,
:draft_key :draft_key,
] ]
Post.plugin_permitted_create_params.each do |key, value| Post.plugin_permitted_create_params.each do |key, value|
if value[:plugin].enabled? if value[:plugin].enabled?
permitted << case value[:type] permitted << case value[:type]
when :string when :string
key.to_sym key.to_sym
when :array when :array
{ key => [] } { key => [] }
when :hash when :hash
{ key => {} } { key => {} }
end end
end end
end end
@ -785,11 +835,14 @@ class PostsController < ApplicationController
permitted << :external_id permitted << :external_id
end end
result = params.permit(*permitted).tap do |allowed| result =
allowed[:image_sizes] = params[:image_sizes] params
# TODO this does not feel right, we should name what meta_data is allowed .permit(*permitted)
allowed[:meta_data] = params[:meta_data] .tap do |allowed|
end allowed[:image_sizes] = params[:image_sizes]
# TODO this does not feel right, we should name what meta_data is allowed
allowed[:meta_data] = params[:meta_data]
end
# Staff are allowed to pass `is_warning` # Staff are allowed to pass `is_warning`
if current_user.staff? if current_user.staff?
@ -804,14 +857,20 @@ class PostsController < ApplicationController
result[:no_bump] = true result[:no_bump] = true
end end
if params[:shared_draft] == 'true' if params[:shared_draft] == "true"
raise Discourse::InvalidParameters.new(:shared_draft) unless guardian.can_create_shared_draft? raise Discourse::InvalidParameters.new(:shared_draft) unless guardian.can_create_shared_draft?
result[:shared_draft] = true result[:shared_draft] = true
end end
if params[:whisper] == "true" if params[:whisper] == "true"
raise Discourse::InvalidAccess.new("invalid_whisper_access", nil, custom_message: "invalid_whisper_access") unless guardian.can_create_whisper? unless guardian.can_create_whisper?
raise Discourse::InvalidAccess.new(
"invalid_whisper_access",
nil,
custom_message: "invalid_whisper_access",
)
end
result[:post_type] = Post.types[:whisper] result[:post_type] = Post.types[:whisper]
end end
@ -827,14 +886,19 @@ class PostsController < ApplicationController
result[:referrer] = request.env["HTTP_REFERER"] result[:referrer] = request.env["HTTP_REFERER"]
if recipients = result[:target_usernames] if recipients = result[:target_usernames]
Discourse.deprecate("`target_usernames` is deprecated, use `target_recipients` instead.", output_in_test: true, drop_from: '2.9.0') Discourse.deprecate(
"`target_usernames` is deprecated, use `target_recipients` instead.",
output_in_test: true,
drop_from: "2.9.0",
)
else else
recipients = result[:target_recipients] recipients = result[:target_recipients]
end end
if recipients if recipients
recipients = recipients.split(",").map(&:downcase) recipients = recipients.split(",").map(&:downcase)
groups = Group.messageable(current_user).where('lower(name) in (?)', recipients).pluck('lower(name)') groups =
Group.messageable(current_user).where("lower(name) in (?)", recipients).pluck("lower(name)")
recipients -= groups recipients -= groups
emails = recipients.select { |user| user.match(/@/) } emails = recipients.select { |user| user.match(/@/) }
recipients -= emails recipients -= emails
@ -848,13 +912,14 @@ class PostsController < ApplicationController
end end
def signature_for(args) def signature_for(args)
+"post##" << Digest::SHA1.hexdigest(args +"post##" << Digest::SHA1.hexdigest(
.to_h args
.to_a .to_h
.concat([["user", current_user.id]]) .to_a
.sort { |x, y| x[0] <=> y[0] }.join do |x, y| .concat([["user", current_user.id]])
"#{x}:#{y}" .sort { |x, y| x[0] <=> y[0] }
end) .join { |x, y| "#{x}:#{y}" },
)
end end
def display_post(post) def display_post(post)
@ -873,11 +938,13 @@ class PostsController < ApplicationController
end end
def find_post_from_params_by_date def find_post_from_params_by_date
by_date_finder = TopicView.new(params[:topic_id], current_user) by_date_finder =
.filtered_posts TopicView
.where("created_at >= ?", Time.zone.parse(params[:date])) .new(params[:topic_id], current_user)
.order("created_at ASC") .filtered_posts
.limit(1) .where("created_at >= ?", Time.zone.parse(params[:date]))
.order("created_at ASC")
.limit(1)
find_post_using(by_date_finder) find_post_using(by_date_finder)
end end
@ -892,15 +959,14 @@ class PostsController < ApplicationController
post.topic = Topic.with_deleted.find_by(id: post.topic_id) post.topic = Topic.with_deleted.find_by(id: post.topic_id)
if !post.topic || if !post.topic ||
( (
(post.deleted_at.present? || post.topic.deleted_at.present?) && (post.deleted_at.present? || post.topic.deleted_at.present?) &&
!guardian.can_moderate_topic?(post.topic) !guardian.can_moderate_topic?(post.topic)
) )
raise Discourse::NotFound raise Discourse::NotFound
end end
guardian.ensure_can_see!(post) guardian.ensure_can_see!(post)
post post
end end
end end

View File

@ -9,18 +9,23 @@ class PresenceController < ApplicationController
def get def get
names = params.require(:channels) names = params.require(:channels)
raise Discourse::InvalidParameters.new(:channels) if !(names.is_a?(Array) && names.all? { |n| n.is_a? String }) if !(names.is_a?(Array) && names.all? { |n| n.is_a? String })
raise Discourse::InvalidParameters.new(:channels)
end
names.uniq! names.uniq!
raise Discourse::InvalidParameters.new("Too many channels") if names.length > MAX_CHANNELS_PER_REQUEST if names.length > MAX_CHANNELS_PER_REQUEST
raise Discourse::InvalidParameters.new("Too many channels")
user_group_ids = if current_user
GroupUser.where(user_id: current_user.id).pluck("group_id")
else
[]
end end
user_group_ids =
if current_user
GroupUser.where(user_id: current_user.id).pluck("group_id")
else
[]
end
result = {} result = {}
names.each do |name| names.each do |name|
channel = PresenceChannel.new(name) channel = PresenceChannel.new(name)
@ -38,19 +43,23 @@ class PresenceController < ApplicationController
def update def update
client_id = params[:client_id] client_id = params[:client_id]
raise Discourse::InvalidParameters.new(:client_id) if !client_id.is_a?(String) || client_id.blank? if !client_id.is_a?(String) || client_id.blank?
raise Discourse::InvalidParameters.new(:client_id)
end
# JS client is designed to throttle to one request per second # JS client is designed to throttle to one request per second
# When no changes are being made, it makes one request every 30 seconds # When no changes are being made, it makes one request every 30 seconds
RateLimiter.new(nil, "update-presence-#{current_user.id}", 20, 10.seconds).performed! RateLimiter.new(nil, "update-presence-#{current_user.id}", 20, 10.seconds).performed!
present_channels = params[:present_channels] present_channels = params[:present_channels]
if present_channels && !(present_channels.is_a?(Array) && present_channels.all? { |c| c.is_a? String }) if present_channels &&
!(present_channels.is_a?(Array) && present_channels.all? { |c| c.is_a? String })
raise Discourse::InvalidParameters.new(:present_channels) raise Discourse::InvalidParameters.new(:present_channels)
end end
leave_channels = params[:leave_channels] leave_channels = params[:leave_channels]
if leave_channels && !(leave_channels.is_a?(Array) && leave_channels.all? { |c| c.is_a? String }) if leave_channels &&
!(leave_channels.is_a?(Array) && leave_channels.all? { |c| c.is_a? String })
raise Discourse::InvalidParameters.new(:leave_channels) raise Discourse::InvalidParameters.new(:leave_channels)
end end

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class PublishedPagesController < ApplicationController class PublishedPagesController < ApplicationController
skip_before_action :preload_json skip_before_action :preload_json
skip_before_action :check_xhr, :verify_authenticity_token, only: [:show] skip_before_action :check_xhr, :verify_authenticity_token, only: [:show]
before_action :ensure_publish_enabled before_action :ensure_publish_enabled
@ -19,12 +18,14 @@ class PublishedPagesController < ApplicationController
begin begin
guardian.ensure_can_see!(pp.topic) guardian.ensure_can_see!(pp.topic)
rescue Discourse::InvalidAccess => e rescue Discourse::InvalidAccess => e
return rescue_discourse_actions( return(
:invalid_access, rescue_discourse_actions(
403, :invalid_access,
include_ember: false, 403,
custom_message: e.custom_message, include_ember: false,
group: e.group custom_message: e.custom_message,
group: e.group,
)
) )
end end
end end
@ -37,18 +38,19 @@ class PublishedPagesController < ApplicationController
TopicViewItem.add(pp.topic.id, request.remote_ip, current_user ? current_user.id : nil) TopicViewItem.add(pp.topic.id, request.remote_ip, current_user ? current_user.id : nil)
@body_classes = Set.new([ @body_classes =
'published-page', Set.new(
params[:slug], [
"topic-#{@topic.id}", "published-page",
@topic.tags.pluck(:name) params[:slug],
].flatten.compact) "topic-#{@topic.id}",
@topic.tags.pluck(:name),
].flatten.compact,
)
if @topic.category @body_classes << @topic.category.slug if @topic.category
@body_classes << @topic.category.slug
end
render layout: 'publish' render layout: "publish"
end end
def details def details
@ -60,12 +62,13 @@ class PublishedPagesController < ApplicationController
def upsert def upsert
pp_params = params.require(:published_page) pp_params = params.require(:published_page)
result, pp = PublishedPage.publish!( result, pp =
current_user, PublishedPage.publish!(
fetch_topic, current_user,
pp_params[:slug].strip, fetch_topic,
pp_params.permit(:public) pp_params[:slug].strip,
) pp_params.permit(:public),
)
json_result(pp, serializer: PublishedPageSerializer) { result } json_result(pp, serializer: PublishedPageSerializer) { result }
end end
@ -85,7 +88,7 @@ class PublishedPagesController < ApplicationController
end end
end end
private private
def fetch_topic def fetch_topic
topic = Topic.find_by(id: params[:topic_id]) topic = Topic.find_by(id: params[:topic_id])
@ -94,18 +97,13 @@ private
end end
def ensure_publish_enabled def ensure_publish_enabled
if !SiteSetting.enable_page_publishing? || SiteSetting.secure_uploads raise Discourse::NotFound if !SiteSetting.enable_page_publishing? || SiteSetting.secure_uploads
raise Discourse::NotFound
end
end end
def enforce_login_required! def enforce_login_required!
if SiteSetting.login_required? && if SiteSetting.login_required? && !current_user &&
!current_user && !SiteSetting.show_published_pages_login_required? && redirect_to_login
!SiteSetting.show_published_pages_login_required? &&
redirect_to_login
true true
end end
end end
end end

View File

@ -18,6 +18,6 @@ class PushNotificationController < ApplicationController
private private
def push_params def push_params
params.require(:subscription).permit(:endpoint, keys: [:p256dh, :auth]) params.require(:subscription).permit(:endpoint, keys: %i[p256dh auth])
end end
end end

View File

@ -1,11 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class QunitController < ApplicationController class QunitController < ApplicationController
skip_before_action *%i{ skip_before_action *%i[check_xhr preload_json redirect_to_login_if_required]
check_xhr
preload_json
redirect_to_login_if_required
}
layout false layout false
def theme def theme
@ -25,16 +21,20 @@ class QunitController < ApplicationController
end end
if param_key && theme.blank? if param_key && theme.blank?
return render plain: "Can't find theme with #{param_key} #{get_param(param_key).inspect}", status: :not_found return(
render plain: "Can't find theme with #{param_key} #{get_param(param_key).inspect}",
status: :not_found
)
end end
if !param_key if !param_key
@suggested_themes = Theme @suggested_themes =
.where( Theme
id: ThemeField.where(target_id: Theme.targets[:tests_js]).distinct.pluck(:theme_id) .where(
) id: ThemeField.where(target_id: Theme.targets[:tests_js]).distinct.pluck(:theme_id),
.order(updated_at: :desc) )
.pluck(:id, :name) .order(updated_at: :desc)
.pluck(:id, :name)
return return
end end

View File

@ -10,12 +10,10 @@ class ReviewableClaimedTopicsController < ApplicationController
begin begin
ReviewableClaimedTopic.create!(user_id: current_user.id, topic_id: topic.id) ReviewableClaimedTopic.create!(user_id: current_user.id, topic_id: topic.id)
rescue ActiveRecord::RecordInvalid rescue ActiveRecord::RecordInvalid
return render_json_error(I18n.t('reviewables.conflict'), status: 409) return render_json_error(I18n.t("reviewables.conflict"), status: 409)
end end
topic.reviewables.find_each do |reviewable| topic.reviewables.find_each { |reviewable| reviewable.log_history(:claimed, current_user) }
reviewable.log_history(:claimed, current_user)
end
notify_users(topic, current_user) notify_users(topic, current_user)
render json: success_json render json: success_json
@ -27,9 +25,7 @@ class ReviewableClaimedTopicsController < ApplicationController
guardian.ensure_can_claim_reviewable_topic!(topic) guardian.ensure_can_claim_reviewable_topic!(topic)
ReviewableClaimedTopic.where(topic_id: topic.id).delete_all ReviewableClaimedTopic.where(topic_id: topic.id).delete_all
topic.reviewables.find_each do |reviewable| topic.reviewables.find_each { |reviewable| reviewable.log_history(:unclaimed, current_user) }
reviewable.log_history(:unclaimed, current_user)
end
notify_users(topic, nil) notify_users(topic, nil)
render json: success_json render json: success_json
@ -40,7 +36,8 @@ class ReviewableClaimedTopicsController < ApplicationController
def notify_users(topic, claimed_by) def notify_users(topic, claimed_by)
group_ids = Set.new([Group::AUTO_GROUPS[:staff]]) group_ids = Set.new([Group::AUTO_GROUPS[:staff]])
if SiteSetting.enable_category_group_moderation? && group_id = topic.category&.reviewable_by_group_id.presence if SiteSetting.enable_category_group_moderation? &&
group_id = topic.category&.reviewable_by_group_id.presence
group_ids.add(group_id) group_ids.add(group_id)
end end

View File

@ -5,7 +5,7 @@ class ReviewablesController < ApplicationController
PER_PAGE = 10 PER_PAGE = 10
before_action :version_required, only: [:update, :perform] before_action :version_required, only: %i[update perform]
before_action :ensure_can_see, except: [:destroy] before_action :ensure_can_see, except: [:destroy]
def index def index
@ -15,20 +15,21 @@ class ReviewablesController < ApplicationController
raise Discourse::InvalidParameters.new(:type) unless Reviewable.valid_type?(params[:type]) raise Discourse::InvalidParameters.new(:type) unless Reviewable.valid_type?(params[:type])
end end
status = (params[:status] || 'pending').to_sym status = (params[:status] || "pending").to_sym
raise Discourse::InvalidParameters.new(:status) unless allowed_statuses.include?(status) raise Discourse::InvalidParameters.new(:status) unless allowed_statuses.include?(status)
topic_id = params[:topic_id] ? params[:topic_id].to_i : nil topic_id = params[:topic_id] ? params[:topic_id].to_i : nil
category_id = params[:category_id] ? params[:category_id].to_i : nil category_id = params[:category_id] ? params[:category_id].to_i : nil
custom_keys = Reviewable.custom_filters.map(&:first) custom_keys = Reviewable.custom_filters.map(&:first)
additional_filters = JSON.parse(params.fetch(:additional_filters, {}), symbolize_names: true).slice(*custom_keys) additional_filters =
JSON.parse(params.fetch(:additional_filters, {}), symbolize_names: true).slice(*custom_keys)
filters = { filters = {
ids: params[:ids], ids: params[:ids],
status: status, status: status,
category_id: category_id, category_id: category_id,
topic_id: topic_id, topic_id: topic_id,
additional_filters: additional_filters.reject { |_, v| v.blank? } additional_filters: additional_filters.reject { |_, v| v.blank? },
} }
%i[priority username reviewed_by from_date to_date type sort_order].each do |filter_key| %i[priority username reviewed_by from_date to_date type sort_order].each do |filter_key|
@ -36,7 +37,8 @@ class ReviewablesController < ApplicationController
end end
total_rows = Reviewable.list_for(current_user, **filters).count total_rows = Reviewable.list_for(current_user, **filters).count
reviewables = Reviewable.list_for(current_user, **filters.merge(limit: PER_PAGE, offset: offset)).to_a reviewables =
Reviewable.list_for(current_user, **filters.merge(limit: PER_PAGE, offset: offset)).to_a
claimed_topics = ReviewableClaimedTopic.claimed_hash(reviewables.map { |r| r.topic_id }.uniq) claimed_topics = ReviewableClaimedTopic.claimed_hash(reviewables.map { |r| r.topic_id }.uniq)
@ -44,23 +46,25 @@ class ReviewablesController < ApplicationController
# is mutated by the serializer and contains the side loaded records which must be merged in the end. # is mutated by the serializer and contains the side loaded records which must be merged in the end.
hash = {} hash = {}
json = { json = {
reviewables: reviewables.map! do |r| reviewables:
result = r.serializer.new( reviewables.map! do |r|
r, result =
root: nil, r
hash: hash, .serializer
scope: guardian, .new(r, root: nil, hash: hash, scope: guardian, claimed_topics: claimed_topics)
claimed_topics: claimed_topics .as_json
).as_json hash[:bundled_actions].uniq!
hash[:bundled_actions].uniq! (hash["actions"] || []).uniq!
(hash['actions'] || []).uniq! result
result end,
end, meta:
meta: filters.merge( filters.merge(
total_rows_reviewables: total_rows, types: meta_types, reviewable_types: Reviewable.types, total_rows_reviewables: total_rows,
reviewable_count: current_user.reviewable_count, types: meta_types,
unseen_reviewable_count: Reviewable.unseen_reviewable_count(current_user) reviewable_types: Reviewable.types,
) reviewable_count: current_user.reviewable_count,
unseen_reviewable_count: Reviewable.unseen_reviewable_count(current_user),
),
} }
if (offset + PER_PAGE) < total_rows if (offset + PER_PAGE) < total_rows
json[:meta][:load_more_reviewables] = review_path(filters.merge(offset: offset + PER_PAGE)) json[:meta][:load_more_reviewables] = review_path(filters.merge(offset: offset + PER_PAGE))
@ -72,10 +76,11 @@ class ReviewablesController < ApplicationController
def user_menu_list def user_menu_list
json = { json = {
reviewables: Reviewable.basic_serializers_for_list( reviewables:
Reviewable.user_menu_list_for(current_user), Reviewable.basic_serializers_for_list(
current_user Reviewable.user_menu_list_for(current_user),
).as_json current_user,
).as_json,
} }
render_json_dump(json, rest_serializer: true) render_json_dump(json, rest_serializer: true)
end end
@ -108,17 +113,17 @@ class ReviewablesController < ApplicationController
meta[:unique_users] = users.size meta[:unique_users] = users.size
end end
topics = Topic.where(id: topic_ids).order('reviewable_score DESC') topics = Topic.where(id: topic_ids).order("reviewable_score DESC")
render_serialized( render_serialized(
topics, topics,
ReviewableTopicSerializer, ReviewableTopicSerializer,
root: 'reviewable_topics', root: "reviewable_topics",
stats: stats, stats: stats,
claimed_topics: ReviewableClaimedTopic.claimed_hash(topic_ids), claimed_topics: ReviewableClaimedTopic.claimed_hash(topic_ids),
rest_serializer: true, rest_serializer: true,
meta: { meta: {
types: meta_types types: meta_types,
} },
) )
end end
@ -129,7 +134,7 @@ class ReviewablesController < ApplicationController
{ reviewable: reviewable, scores: reviewable.explain_score }, { reviewable: reviewable, scores: reviewable.explain_score },
ReviewableExplanationSerializer, ReviewableExplanationSerializer,
rest_serializer: true, rest_serializer: true,
root: 'reviewable_explanation' root: "reviewable_explanation",
) )
end end
@ -141,10 +146,10 @@ class ReviewablesController < ApplicationController
reviewable.serializer, reviewable.serializer,
rest_serializer: true, rest_serializer: true,
claimed_topics: ReviewableClaimedTopic.claimed_hash([reviewable.topic_id]), claimed_topics: ReviewableClaimedTopic.claimed_hash([reviewable.topic_id]),
root: 'reviewable', root: "reviewable",
meta: { meta: {
types: meta_types types: meta_types,
} },
) )
end end
@ -186,7 +191,7 @@ class ReviewablesController < ApplicationController
render_json_error(reviewable.errors) render_json_error(reviewable.errors)
end end
rescue Reviewable::UpdateConflict rescue Reviewable::UpdateConflict
render_json_error(I18n.t('reviewables.conflict'), status: 409) render_json_error(I18n.t("reviewables.conflict"), status: 409)
end end
end end
@ -201,23 +206,32 @@ class ReviewablesController < ApplicationController
return render_json_error(error) return render_json_error(error)
end end
args.merge!(reject_reason: params[:reject_reason], send_email: params[:send_email] != "false") if reviewable.type == 'ReviewableUser' if reviewable.type == "ReviewableUser"
args.merge!(
plugin_params = DiscoursePluginRegistry.reviewable_params.select do |reviewable_param| reject_reason: params[:reject_reason],
reviewable.type == reviewable_param[:type].to_s.classify send_email: params[:send_email] != "false",
)
end end
plugin_params =
DiscoursePluginRegistry.reviewable_params.select do |reviewable_param|
reviewable.type == reviewable_param[:type].to_s.classify
end
args.merge!(params.slice(*plugin_params.map { |pp| pp[:param] }).permit!) args.merge!(params.slice(*plugin_params.map { |pp| pp[:param] }).permit!)
result = reviewable.perform(current_user, params[:action_id].to_sym, args) result = reviewable.perform(current_user, params[:action_id].to_sym, args)
rescue Reviewable::InvalidAction => e rescue Reviewable::InvalidAction => e
if reviewable.type == 'ReviewableUser' && !reviewable.pending? && reviewable.target.blank? if reviewable.type == "ReviewableUser" && !reviewable.pending? && reviewable.target.blank?
raise Discourse::NotFound.new(e.message, custom_message: "reviewables.already_handled_and_user_not_exist") raise Discourse::NotFound.new(
e.message,
custom_message: "reviewables.already_handled_and_user_not_exist",
)
else else
# Consider InvalidAction an InvalidAccess # Consider InvalidAction an InvalidAccess
raise Discourse::InvalidAccess.new(e.message) raise Discourse::InvalidAccess.new(e.message)
end end
rescue Reviewable::UpdateConflict rescue Reviewable::UpdateConflict
return render_json_error(I18n.t('reviewables.conflict'), status: 409) return render_json_error(I18n.t("reviewables.conflict"), status: 409)
end end
if result.success? if result.success?
@ -230,7 +244,7 @@ class ReviewablesController < ApplicationController
def settings def settings
raise Discourse::InvalidAccess.new unless current_user.admin? raise Discourse::InvalidAccess.new unless current_user.admin?
post_action_types = PostActionType.where(id: PostActionType.flag_types.values).order('id') post_action_types = PostActionType.where(id: PostActionType.flag_types.values).order("id")
if request.put? if request.put?
params[:reviewable_priorities].each do |id, priority| params[:reviewable_priorities].each do |id, priority|
@ -239,7 +253,7 @@ class ReviewablesController < ApplicationController
# to calculate it a different way. # to calculate it a different way.
PostActionType.where(id: id).update_all( PostActionType.where(id: id).update_all(
reviewable_priority: priority.to_i, reviewable_priority: priority.to_i,
score_bonus: priority.to_f score_bonus: priority.to_f,
) )
end end
end end
@ -249,7 +263,7 @@ class ReviewablesController < ApplicationController
render_serialized(data, ReviewableSettingsSerializer, rest_serializer: true) render_serialized(data, ReviewableSettingsSerializer, rest_serializer: true)
end end
protected protected
def claim_error?(reviewable) def claim_error?(reviewable)
return if SiteSetting.reviewable_claiming == "disabled" || reviewable.topic_id.blank? return if SiteSetting.reviewable_claiming == "disabled" || reviewable.topic_id.blank?
@ -257,9 +271,9 @@ protected
claimed_by_id = ReviewableClaimedTopic.where(topic_id: reviewable.topic_id).pluck(:user_id)[0] claimed_by_id = ReviewableClaimedTopic.where(topic_id: reviewable.topic_id).pluck(:user_id)[0]
if SiteSetting.reviewable_claiming == "required" && claimed_by_id.blank? if SiteSetting.reviewable_claiming == "required" && claimed_by_id.blank?
I18n.t('reviewables.must_claim') I18n.t("reviewables.must_claim")
elsif claimed_by_id.present? && claimed_by_id != current_user.id elsif claimed_by_id.present? && claimed_by_id != current_user.id
I18n.t('reviewables.user_claimed') I18n.t("reviewables.user_claimed")
end end
end end
@ -274,18 +288,11 @@ protected
end end
def version_required def version_required
if params[:version].blank? render_json_error(I18n.t("reviewables.missing_version"), status: 422) if params[:version].blank?
render_json_error(I18n.t('reviewables.missing_version'), status: 422)
end
end end
def meta_types def meta_types
{ { created_by: "user", target_created_by: "user", reviewed_by: "user", claimed_by: "user" }
created_by: 'user',
target_created_by: 'user',
reviewed_by: 'user',
claimed_by: 'user'
}
end end
def ensure_can_see def ensure_can_see

View File

@ -7,7 +7,7 @@ class RobotsTxtController < ApplicationController
OVERRIDDEN_HEADER = "# This robots.txt file has been customized at /admin/customize/robots\n" OVERRIDDEN_HEADER = "# This robots.txt file has been customized at /admin/customize/robots\n"
# NOTE: order is important! # NOTE: order is important!
DISALLOWED_PATHS ||= %w{ DISALLOWED_PATHS ||= %w[
/admin/ /admin/
/auth/ /auth/
/assets/browser-update*.js /assets/browser-update*.js
@ -16,18 +16,9 @@ class RobotsTxtController < ApplicationController
/user-api-key /user-api-key
/*?api_key* /*?api_key*
/*?*api_key* /*?*api_key*
} ]
DISALLOWED_WITH_HEADER_PATHS ||= %w{ DISALLOWED_WITH_HEADER_PATHS ||= %w[/badges /u/ /my /search /tag/*/l /g /t/*/*.rss /c/*.rss]
/badges
/u/
/my
/search
/tag/*/l
/g
/t/*/*.rss
/c/*.rss
}
def index def index
if (overridden = SiteSetting.overridden_robots_txt.dup).present? if (overridden = SiteSetting.overridden_robots_txt.dup).present?
@ -37,9 +28,9 @@ class RobotsTxtController < ApplicationController
end end
if SiteSetting.allow_index_in_robots_txt? if SiteSetting.allow_index_in_robots_txt?
@robots_info = self.class.fetch_default_robots_info @robots_info = self.class.fetch_default_robots_info
render :index, content_type: 'text/plain' render :index, content_type: "text/plain"
else else
render :no_index, content_type: 'text/plain' render :no_index, content_type: "text/plain"
end end
end end
@ -56,32 +47,37 @@ class RobotsTxtController < ApplicationController
def self.fetch_default_robots_info def self.fetch_default_robots_info
deny_paths_googlebot = DISALLOWED_PATHS.map { |p| Discourse.base_path + p } deny_paths_googlebot = DISALLOWED_PATHS.map { |p| Discourse.base_path + p }
deny_paths = deny_paths_googlebot + DISALLOWED_WITH_HEADER_PATHS.map { |p| Discourse.base_path + p } deny_paths =
deny_all = [ "#{Discourse.base_path}/" ] deny_paths_googlebot + DISALLOWED_WITH_HEADER_PATHS.map { |p| Discourse.base_path + p }
deny_all = ["#{Discourse.base_path}/"]
result = { result = {
header: "# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file", header:
agents: [] "# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file",
agents: [],
} }
if SiteSetting.allowed_crawler_user_agents.present? if SiteSetting.allowed_crawler_user_agents.present?
SiteSetting.allowed_crawler_user_agents.split('|').each do |agent| SiteSetting
paths = agent == "Googlebot" ? deny_paths_googlebot : deny_paths .allowed_crawler_user_agents
result[:agents] << { name: agent, disallow: paths } .split("|")
end .each do |agent|
paths = agent == "Googlebot" ? deny_paths_googlebot : deny_paths
result[:agents] << { name: '*', disallow: deny_all } result[:agents] << { name: agent, disallow: paths }
else
if SiteSetting.blocked_crawler_user_agents.present?
SiteSetting.blocked_crawler_user_agents.split('|').each do |agent|
result[:agents] << { name: agent, disallow: deny_all }
end end
result[:agents] << { name: "*", disallow: deny_all }
else
if SiteSetting.blocked_crawler_user_agents.present?
SiteSetting
.blocked_crawler_user_agents
.split("|")
.each { |agent| result[:agents] << { name: agent, disallow: deny_all } }
end end
result[:agents] << { name: '*', disallow: deny_paths } result[:agents] << { name: "*", disallow: deny_paths }
result[:agents] << { name: 'Googlebot', disallow: deny_paths_googlebot } result[:agents] << { name: "Googlebot", disallow: deny_paths_googlebot }
end end
DiscourseEvent.trigger(:robots_info, result) DiscourseEvent.trigger(:robots_info, result)

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class SafeModeController < ApplicationController class SafeModeController < ApplicationController
layout 'no_ember' layout "no_ember"
before_action :ensure_safe_mode_enabled before_action :ensure_safe_mode_enabled
before_action :force_safe_mode_for_route before_action :force_safe_mode_for_route
@ -39,5 +39,4 @@ class SafeModeController < ApplicationController
request.env[ApplicationController::NO_THEMES] = true request.env[ApplicationController::NO_THEMES] = true
request.env[ApplicationController::NO_PLUGINS] = true request.env[ApplicationController::NO_PLUGINS] = true
end end
end end

View File

@ -1,13 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
class SearchController < ApplicationController class SearchController < ApplicationController
before_action :cancel_overloaded_search, only: [:query] before_action :cancel_overloaded_search, only: [:query]
skip_before_action :check_xhr, only: :show skip_before_action :check_xhr, only: :show
after_action :add_noindex_header after_action :add_noindex_header
def self.valid_context_types def self.valid_context_types
%w{user topic category private_messages tag} %w[user topic category private_messages tag]
end end
def show def show
@ -16,12 +15,9 @@ class SearchController < ApplicationController
# a q param has been given but it's not in the correct format # a q param has been given but it's not in the correct format
# eg: ?q[foo]=bar # eg: ?q[foo]=bar
if params[:q].present? && !@search_term.present? raise Discourse::InvalidParameters.new(:q) if params[:q].present? && !@search_term.present?
raise Discourse::InvalidParameters.new(:q)
end
if @search_term.present? && if @search_term.present? && @search_term.length < SiteSetting.min_search_term_length
@search_term.length < SiteSetting.min_search_term_length
raise Discourse::InvalidParameters.new(:q) raise Discourse::InvalidParameters.new(:q)
end end
@ -31,21 +27,17 @@ class SearchController < ApplicationController
page = permitted_params[:page] page = permitted_params[:page]
# check for a malformed page parameter # check for a malformed page parameter
if page && (!page.is_a?(String) || page.to_i.to_s != page) raise Discourse::InvalidParameters if page && (!page.is_a?(String) || page.to_i.to_s != page)
raise Discourse::InvalidParameters
end
rate_limit_errors = rate_limit_search rate_limit_errors = rate_limit_search
discourse_expires_in 1.minute discourse_expires_in 1.minute
search_args = { search_args = {
type_filter: 'topic', type_filter: "topic",
guardian: guardian, guardian: guardian,
blurb_length: 300, blurb_length: 300,
page: if page.to_i <= 10 page: ([page.to_i, 1].max if page.to_i <= 10),
[page.to_i, 1].max
end
} }
context, type = lookup_search_context context, type = lookup_search_context
@ -59,19 +51,21 @@ class SearchController < ApplicationController
search_args[:user_id] = current_user.id if current_user.present? search_args[:user_id] = current_user.id if current_user.present?
if rate_limit_errors if rate_limit_errors
result = Search::GroupedSearchResults.new( result =
type_filter: search_args[:type_filter], Search::GroupedSearchResults.new(
term: @search_term, type_filter: search_args[:type_filter],
search_context: context term: @search_term,
) search_context: context,
)
result.error = I18n.t("rate_limiter.slow_down") result.error = I18n.t("rate_limiter.slow_down")
elsif site_overloaded? elsif site_overloaded?
result = Search::GroupedSearchResults.new( result =
type_filter: search_args[:type_filter], Search::GroupedSearchResults.new(
term: @search_term, type_filter: search_args[:type_filter],
search_context: context term: @search_term,
) search_context: context,
)
result.error = I18n.t("search.extreme_load_error") result.error = I18n.t("search.extreme_load_error")
else else
@ -83,12 +77,8 @@ class SearchController < ApplicationController
serializer = serialize_data(result, GroupedSearchResultSerializer, result: result) serializer = serialize_data(result, GroupedSearchResultSerializer, result: result)
respond_to do |format| respond_to do |format|
format.html do format.html { store_preloaded("search", MultiJson.dump(serializer)) }
store_preloaded("search", MultiJson.dump(serializer)) format.json { render_json_dump(serializer) }
end
format.json do
render_json_dump(serializer)
end
end end
end end
@ -105,8 +95,8 @@ class SearchController < ApplicationController
search_args = { guardian: guardian } search_args = { guardian: guardian }
search_args[:type_filter] = params[:type_filter] if params[:type_filter].present? search_args[:type_filter] = params[:type_filter] if params[:type_filter].present?
search_args[:search_for_id] = true if params[:search_for_id].present? search_args[:search_for_id] = true if params[:search_for_id].present?
context, type = lookup_search_context context, type = lookup_search_context
@ -118,22 +108,26 @@ class SearchController < ApplicationController
search_args[:search_type] = :header search_args[:search_type] = :header
search_args[:ip_address] = request.remote_ip search_args[:ip_address] = request.remote_ip
search_args[:user_id] = current_user.id if current_user.present? search_args[:user_id] = current_user.id if current_user.present?
search_args[:restrict_to_archetype] = params[:restrict_to_archetype] if params[:restrict_to_archetype].present? search_args[:restrict_to_archetype] = params[:restrict_to_archetype] if params[
:restrict_to_archetype
].present?
if rate_limit_errors if rate_limit_errors
result = Search::GroupedSearchResults.new( result =
type_filter: search_args[:type_filter], Search::GroupedSearchResults.new(
term: params[:term], type_filter: search_args[:type_filter],
search_context: context term: params[:term],
) search_context: context,
)
result.error = I18n.t("rate_limiter.slow_down") result.error = I18n.t("rate_limiter.slow_down")
elsif site_overloaded? elsif site_overloaded?
result = GroupedSearchResults.new( result =
type_filter: search_args[:type_filter], GroupedSearchResults.new(
term: params[:term], type_filter: search_args[:type_filter],
search_context: context term: params[:term],
) search_context: context,
)
else else
search = Search.new(params[:term], search_args) search = Search.new(params[:term], search_args)
result = search.execute(readonly_mode: @readonly_mode) result = search.execute(readonly_mode: @readonly_mode)
@ -163,7 +157,7 @@ class SearchController < ApplicationController
SearchLog.where(attributes).update_all( SearchLog.where(attributes).update_all(
search_result_type: SearchLog.search_result_types[search_result_type], search_result_type: SearchLog.search_result_types[search_result_type],
search_result_id: search_result_id search_result_id: search_result_id,
) )
end end
@ -173,7 +167,7 @@ class SearchController < ApplicationController
protected protected
def site_overloaded? def site_overloaded?
queue_time = request.env['REQUEST_QUEUE_SECONDS'] queue_time = request.env["REQUEST_QUEUE_SECONDS"]
if queue_time if queue_time
threshold = GlobalSetting.disable_search_queue_threshold.to_f threshold = GlobalSetting.disable_search_queue_threshold.to_f
threshold > 0 && queue_time > threshold threshold > 0 && queue_time > threshold
@ -185,10 +179,25 @@ class SearchController < ApplicationController
def rate_limit_search def rate_limit_search
begin begin
if current_user.present? if current_user.present?
RateLimiter.new(current_user, "search-min", SiteSetting.rate_limit_search_user, 1.minute).performed! RateLimiter.new(
current_user,
"search-min",
SiteSetting.rate_limit_search_user,
1.minute,
).performed!
else else
RateLimiter.new(nil, "search-min-#{request.remote_ip}", SiteSetting.rate_limit_search_anon_user, 1.minute).performed! RateLimiter.new(
RateLimiter.new(nil, "search-min-anon-global", SiteSetting.rate_limit_search_anon_global, 1.minute).performed! nil,
"search-min-#{request.remote_ip}",
SiteSetting.rate_limit_search_anon_user,
1.minute,
).performed!
RateLimiter.new(
nil,
"search-min-anon-global",
SiteSetting.rate_limit_search_anon_global,
1.minute,
).performed!
end end
rescue RateLimiter::LimitExceeded => e rescue RateLimiter::LimitExceeded => e
return e return e
@ -197,13 +206,10 @@ class SearchController < ApplicationController
end end
def cancel_overloaded_search def cancel_overloaded_search
if site_overloaded? render_json_error I18n.t("search.extreme_load_error"), status: 409 if site_overloaded?
render_json_error I18n.t("search.extreme_load_error"), status: 409
end
end end
def lookup_search_context def lookup_search_context
return if params[:skip_context] == "true" return if params[:skip_context] == "true"
search_context = params[:search_context] search_context = params[:search_context]
@ -214,30 +220,29 @@ class SearchController < ApplicationController
end end
if search_context.present? if search_context.present?
raise Discourse::InvalidParameters.new(:search_context) unless SearchController.valid_context_types.include?(search_context[:type]) unless SearchController.valid_context_types.include?(search_context[:type])
raise Discourse::InvalidParameters.new(:search_context)
end
raise Discourse::InvalidParameters.new(:search_context) if search_context[:id].blank? raise Discourse::InvalidParameters.new(:search_context) if search_context[:id].blank?
# A user is found by username # A user is found by username
context_obj = nil context_obj = nil
if ['user', 'private_messages'].include? search_context[:type] if %w[user private_messages].include? search_context[:type]
context_obj = User.find_by(username_lower: search_context[:id].downcase) context_obj = User.find_by(username_lower: search_context[:id].downcase)
elsif 'category' == search_context[:type] elsif "category" == search_context[:type]
context_obj = Category.find_by(id: search_context[:id].to_i) context_obj = Category.find_by(id: search_context[:id].to_i)
elsif 'topic' == search_context[:type] elsif "topic" == search_context[:type]
context_obj = Topic.find_by(id: search_context[:id].to_i) context_obj = Topic.find_by(id: search_context[:id].to_i)
elsif 'tag' == search_context[:type] elsif "tag" == search_context[:type]
context_obj = Tag.where_name(search_context[:name]).first context_obj = Tag.where_name(search_context[:name]).first
end end
type_filter = nil type_filter = nil
if search_context[:type] == 'private_messages' type_filter = "private_messages" if search_context[:type] == "private_messages"
type_filter = 'private_messages'
end
guardian.ensure_can_see!(context_obj) guardian.ensure_can_see!(context_obj)
[context_obj, type_filter] [context_obj, type_filter]
end end
end end
end end

View File

@ -1,14 +1,16 @@
# frozen_string_literal: true # frozen_string_literal: true
class SessionController < ApplicationController class SessionController < ApplicationController
before_action :check_local_login_allowed, only: %i(create forgot_password) before_action :check_local_login_allowed, only: %i[create forgot_password]
before_action :rate_limit_login, only: %i(create email_login) before_action :rate_limit_login, only: %i[create email_login]
skip_before_action :redirect_to_login_if_required skip_before_action :redirect_to_login_if_required
skip_before_action :preload_json, :check_xhr, only: %i(sso sso_login sso_provider destroy one_time_password) skip_before_action :preload_json,
:check_xhr,
only: %i[sso sso_login sso_provider destroy one_time_password]
skip_before_action :check_xhr, only: %i(second_factor_auth_show) skip_before_action :check_xhr, only: %i[second_factor_auth_show]
requires_login only: [:second_factor_auth_show, :second_factor_auth_perform] requires_login only: %i[second_factor_auth_show second_factor_auth_perform]
allow_in_staff_writes_only_mode :create allow_in_staff_writes_only_mode :create
allow_in_staff_writes_only_mode :email_login allow_in_staff_writes_only_mode :email_login
@ -23,10 +25,10 @@ class SessionController < ApplicationController
raise Discourse::NotFound unless SiteSetting.enable_discourse_connect? raise Discourse::NotFound unless SiteSetting.enable_discourse_connect?
destination_url = cookies[:destination_url] || session[:destination_url] destination_url = cookies[:destination_url] || session[:destination_url]
return_path = params[:return_path] || path('/') return_path = params[:return_path] || path("/")
if destination_url && return_path == path('/') if destination_url && return_path == path("/")
uri = URI::parse(destination_url) uri = URI.parse(destination_url)
return_path = "#{uri.path}#{uri.query ? "?#{uri.query}" : ""}" return_path = "#{uri.path}#{uri.query ? "?#{uri.query}" : ""}"
end end
@ -41,11 +43,12 @@ class SessionController < ApplicationController
def sso_provider(payload = nil, confirmed_2fa_during_login = false) def sso_provider(payload = nil, confirmed_2fa_during_login = false)
raise Discourse::NotFound unless SiteSetting.enable_discourse_connect_provider raise Discourse::NotFound unless SiteSetting.enable_discourse_connect_provider
result = run_second_factor!( result =
SecondFactor::Actions::DiscourseConnectProvider, run_second_factor!(
payload: payload, SecondFactor::Actions::DiscourseConnectProvider,
confirmed_2fa_during_login: confirmed_2fa_during_login payload: payload,
) confirmed_2fa_during_login: confirmed_2fa_during_login,
)
if result.second_factor_auth_skipped? if result.second_factor_auth_skipped?
data = result.data data = result.data
@ -57,7 +60,7 @@ class SessionController < ApplicationController
if data[:no_current_user] if data[:no_current_user]
cookies[:sso_payload] = payload || request.query_string cookies[:sso_payload] = payload || request.query_string
redirect_to path('/login') redirect_to path("/login")
return return
end end
@ -93,12 +96,11 @@ class SessionController < ApplicationController
skip_before_action :check_xhr, only: [:become] skip_before_action :check_xhr, only: [:become]
def become def become
raise Discourse::InvalidAccess if Rails.env.production? raise Discourse::InvalidAccess if Rails.env.production?
raise Discourse::ReadOnly if @readonly_mode raise Discourse::ReadOnly if @readonly_mode
if ENV['DISCOURSE_DEV_ALLOW_ANON_TO_IMPERSONATE'] != "1" if ENV["DISCOURSE_DEV_ALLOW_ANON_TO_IMPERSONATE"] != "1"
render(content_type: 'text/plain', inline: <<~TEXT) render(content_type: "text/plain", inline: <<~TEXT)
To enable impersonating any user without typing passwords set the following ENV var To enable impersonating any user without typing passwords set the following ENV var
export DISCOURSE_DEV_ALLOW_ANON_TO_IMPERSONATE=1 export DISCOURSE_DEV_ALLOW_ANON_TO_IMPERSONATE=1
@ -132,7 +134,9 @@ class SessionController < ApplicationController
begin begin
sso = DiscourseConnect.parse(request.query_string, secure_session: secure_session) sso = DiscourseConnect.parse(request.query_string, secure_session: secure_session)
rescue DiscourseConnect::ParseError => e rescue DiscourseConnect::ParseError => e
connect_verbose_warn { "Verbose SSO log: Signature parse error\n\n#{e.message}\n\n#{sso&.diagnostics}" } connect_verbose_warn do
"Verbose SSO log: Signature parse error\n\n#{e.message}\n\n#{sso&.diagnostics}"
end
# Do NOT pass the error text to the client, it would give them the correct signature # Do NOT pass the error text to the client, it would give them the correct signature
return render_sso_error(text: I18n.t("discourse_connect.login_error"), status: 422) return render_sso_error(text: I18n.t("discourse_connect.login_error"), status: 422)
@ -144,7 +148,9 @@ class SessionController < ApplicationController
end end
if ScreenedIpAddress.should_block?(request.remote_ip) if ScreenedIpAddress.should_block?(request.remote_ip)
connect_verbose_warn { "Verbose SSO log: IP address is blocked #{request.remote_ip}\n\n#{sso.diagnostics}" } connect_verbose_warn do
"Verbose SSO log: IP address is blocked #{request.remote_ip}\n\n#{sso.diagnostics}"
end
return render_sso_error(text: I18n.t("discourse_connect.unknown_error"), status: 500) return render_sso_error(text: I18n.t("discourse_connect.unknown_error"), status: 500)
end end
@ -163,9 +169,7 @@ class SessionController < ApplicationController
end end
if SiteSetting.must_approve_users? && !user.approved? if SiteSetting.must_approve_users? && !user.approved?
if invite.present? && user.invited_user.blank? redeem_invitation(invite, sso, user) if invite.present? && user.invited_user.blank?
redeem_invitation(invite, sso, user)
end
if SiteSetting.discourse_connect_not_approved_url.present? if SiteSetting.discourse_connect_not_approved_url.present?
redirect_to SiteSetting.discourse_connect_not_approved_url, allow_other_host: true redirect_to SiteSetting.discourse_connect_not_approved_url, allow_other_host: true
@ -174,9 +178,9 @@ class SessionController < ApplicationController
end end
return return
# we only want to redeem the invite if # we only want to redeem the invite if
# the user has not already redeemed an invite # the user has not already redeemed an invite
# (covers the same SSO user visiting an invite link) # (covers the same SSO user visiting an invite link)
elsif invite.present? && user.invited_user.blank? elsif invite.present? && user.invited_user.blank?
redeem_invitation(invite, sso, user) redeem_invitation(invite, sso, user)
@ -199,7 +203,7 @@ class SessionController < ApplicationController
end end
# If it's not a relative URL check the host # If it's not a relative URL check the host
if return_path !~ /^\/[^\/]/ if return_path !~ %r{^/[^/]}
begin begin
uri = URI(return_path) uri = URI(return_path)
if (uri.hostname == Discourse.current_hostname) if (uri.hostname == Discourse.current_hostname)
@ -207,7 +211,7 @@ class SessionController < ApplicationController
elsif !SiteSetting.discourse_connect_allows_all_return_paths elsif !SiteSetting.discourse_connect_allows_all_return_paths
return_path = path("/") return_path = path("/")
end end
rescue rescue StandardError
return_path = path("/") return_path = path("/")
end end
end end
@ -215,16 +219,13 @@ class SessionController < ApplicationController
# this can be done more surgically with a regex # this can be done more surgically with a regex
# but it the edge case of never supporting redirects back to # but it the edge case of never supporting redirects back to
# any url with `/session/sso` in it anywhere is reasonable # any url with `/session/sso` in it anywhere is reasonable
if return_path.include?(path("/session/sso")) return_path = path("/") if return_path.include?(path("/session/sso"))
return_path = path("/")
end
redirect_to return_path, allow_other_host: true redirect_to return_path, allow_other_host: true
else else
render_sso_error(text: I18n.t("discourse_connect.not_found"), status: 500) render_sso_error(text: I18n.t("discourse_connect.not_found"), status: 500)
end end
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
connect_verbose_warn { <<~TEXT } connect_verbose_warn { <<~TEXT }
Verbose SSO log: Record was invalid: #{e.record.class.name} #{e.record.id} Verbose SSO log: Record was invalid: #{e.record.class.name} #{e.record.id}
#{e.record.errors.to_h} #{e.record.errors.to_h}
@ -243,7 +244,8 @@ class SessionController < ApplicationController
if e.record.email.blank? if e.record.email.blank?
text = I18n.t("discourse_connect.no_email") text = I18n.t("discourse_connect.no_email")
else else
text = I18n.t("discourse_connect.email_error", email: ERB::Util.html_escape(e.record.email)) text =
I18n.t("discourse_connect.email_error", email: ERB::Util.html_escape(e.record.email))
end end
end end
@ -270,7 +272,9 @@ class SessionController < ApplicationController
end end
def login_sso_user(sso, user) def login_sso_user(sso, user)
connect_verbose_warn { "Verbose SSO log: User was logged on #{user.username}\n\n#{sso.diagnostics}" } connect_verbose_warn do
"Verbose SSO log: User was logged on #{user.username}\n\n#{sso.diagnostics}"
end
log_on_user(user) if user.id != current_user&.id log_on_user(user) if user.id != current_user&.id
end end
@ -287,7 +291,6 @@ class SessionController < ApplicationController
rate_limit_second_factor!(user) rate_limit_second_factor!(user)
if user.present? if user.present?
# If their password is correct # If their password is correct
unless user.confirm_password?(params[:password]) unless user.confirm_password?(params[:password])
invalid_credentials invalid_credentials
@ -313,9 +316,7 @@ class SessionController < ApplicationController
end end
second_factor_auth_result = authenticate_second_factor(user) second_factor_auth_result = authenticate_second_factor(user)
if !second_factor_auth_result.ok return render(json: @second_factor_failure_payload) if !second_factor_auth_result.ok
return render(json: @second_factor_failure_payload)
end
if user.active && user.email_confirmed? if user.active && user.email_confirmed?
login(user, second_factor_auth_result) login(user, second_factor_auth_result)
@ -332,33 +333,31 @@ class SessionController < ApplicationController
check_local_login_allowed(user: user, check_login_via_email: true) check_local_login_allowed(user: user, check_login_via_email: true)
if matched_token if matched_token
response = { response = { can_login: true, token: token, token_email: matched_token.email }
can_login: true,
token: token,
token_email: matched_token.email
}
matched_user = matched_token.user matched_user = matched_token.user
if matched_user&.totp_enabled? if matched_user&.totp_enabled?
response.merge!( response.merge!(
second_factor_required: true, second_factor_required: true,
backup_codes_enabled: matched_user&.backup_codes_enabled? backup_codes_enabled: matched_user&.backup_codes_enabled?,
) )
end end
if matched_user&.security_keys_enabled? if matched_user&.security_keys_enabled?
Webauthn.stage_challenge(matched_user, secure_session) Webauthn.stage_challenge(matched_user, secure_session)
response.merge!( response.merge!(
Webauthn.allowed_credentials(matched_user, secure_session).merge(security_key_required: true) Webauthn.allowed_credentials(matched_user, secure_session).merge(
security_key_required: true,
),
) )
end end
render json: response render json: response
else else
render json: { render json: {
can_login: false, can_login: false,
error: I18n.t('email_login.invalid_token', base_url: Discourse.base_url) error: I18n.t("email_login.invalid_token", base_url: Discourse.base_url),
} }
end end
end end
@ -388,7 +387,7 @@ class SessionController < ApplicationController
end end
end end
render json: { error: I18n.t('email_login.invalid_token', base_url: Discourse.base_url) } render json: { error: I18n.t("email_login.invalid_token", base_url: Discourse.base_url) }
end end
def one_time_password def one_time_password
@ -406,10 +405,10 @@ class SessionController < ApplicationController
# Display the form # Display the form
end end
else else
@error = I18n.t('user_api_key.invalid_token') @error = I18n.t("user_api_key.invalid_token")
end end
render layout: 'no_ember', locals: { hide_auth_buttons: true } render layout: "no_ember", locals: { hide_auth_buttons: true }
end end
def second_factor_auth_show def second_factor_auth_show
@ -431,7 +430,7 @@ class SessionController < ApplicationController
json.merge!( json.merge!(
totp_enabled: user.totp_enabled?, totp_enabled: user.totp_enabled?,
backup_enabled: user.backup_codes_enabled?, backup_enabled: user.backup_codes_enabled?,
allowed_methods: challenge[:allowed_methods] allowed_methods: challenge[:allowed_methods],
) )
if user.security_keys_enabled? if user.security_keys_enabled?
Webauthn.stage_challenge(user, secure_session) Webauthn.stage_challenge(user, secure_session)
@ -440,9 +439,7 @@ class SessionController < ApplicationController
else else
json[:security_keys_enabled] = false json[:security_keys_enabled] = false
end end
if challenge[:description] json[:description] = challenge[:description] if challenge[:description]
json[:description] = challenge[:description]
end
else else
json[:error] = I18n.t(error_key) json[:error] = I18n.t(error_key)
end end
@ -453,9 +450,7 @@ class SessionController < ApplicationController
raise ApplicationController::RenderEmpty.new raise ApplicationController::RenderEmpty.new
end end
format.json do format.json { render json: json, status: status_code }
render json: json, status: status_code
end
end end
end end
@ -472,11 +467,12 @@ class SessionController < ApplicationController
end end
if error_key if error_key
json = failed_json.merge( json =
ok: false, failed_json.merge(
error: I18n.t(error_key), ok: false,
reason: "challenge_not_found_or_expired" error: I18n.t(error_key),
) reason: "challenge_not_found_or_expired",
)
render json: failed_json.merge(json), status: status_code render json: failed_json.merge(json), status: status_code
return return
end end
@ -505,21 +501,23 @@ class SessionController < ApplicationController
challenge[:generated_at] += 1.minute.to_i challenge[:generated_at] += 1.minute.to_i
secure_session["current_second_factor_auth_challenge"] = challenge.to_json secure_session["current_second_factor_auth_challenge"] = challenge.to_json
else else
error_json = second_factor_auth_result error_json =
.to_h second_factor_auth_result
.deep_symbolize_keys .to_h
.slice(:ok, :error, :reason) .deep_symbolize_keys
.merge(failed_json) .slice(:ok, :error, :reason)
.merge(failed_json)
render json: error_json, status: 400 render json: error_json, status: 400
return return
end end
end end
render json: { render json: {
ok: true, ok: true,
callback_method: challenge[:callback_method], callback_method: challenge[:callback_method],
callback_path: challenge[:callback_path], callback_path: challenge[:callback_path],
redirect_url: challenge[:redirect_url] redirect_url: challenge[:redirect_url],
}, status: 200 },
status: 200
end end
def forgot_password def forgot_password
@ -532,19 +530,33 @@ class SessionController < ApplicationController
RateLimiter.new(nil, "forgot-password-hr-#{request.remote_ip}", 6, 1.hour).performed! RateLimiter.new(nil, "forgot-password-hr-#{request.remote_ip}", 6, 1.hour).performed!
RateLimiter.new(nil, "forgot-password-min-#{request.remote_ip}", 3, 1.minute).performed! RateLimiter.new(nil, "forgot-password-min-#{request.remote_ip}", 3, 1.minute).performed!
user = if SiteSetting.hide_email_address_taken && !current_user&.staff? user =
raise Discourse::InvalidParameters.new(:login) if !EmailAddressValidator.valid_value?(normalized_login_param) if SiteSetting.hide_email_address_taken && !current_user&.staff?
User.real.where(staged: false).find_by_email(Email.downcase(normalized_login_param)) if !EmailAddressValidator.valid_value?(normalized_login_param)
else raise Discourse::InvalidParameters.new(:login)
User.real.where(staged: false).find_by_username_or_email(normalized_login_param) end
end User.real.where(staged: false).find_by_email(Email.downcase(normalized_login_param))
else
User.real.where(staged: false).find_by_username_or_email(normalized_login_param)
end
if user if user
RateLimiter.new(nil, "forgot-password-login-day-#{user.username}", 6, 1.day).performed! RateLimiter.new(nil, "forgot-password-login-day-#{user.username}", 6, 1.day).performed!
email_token = user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:password_reset]) email_token =
Jobs.enqueue(:critical_user_email, type: "forgot_password", user_id: user.id, email_token: email_token.token) user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:password_reset])
Jobs.enqueue(
:critical_user_email,
type: "forgot_password",
user_id: user.id,
email_token: email_token.token,
)
else else
RateLimiter.new(nil, "forgot-password-login-hour-#{normalized_login_param}", 5, 1.hour).performed! RateLimiter.new(
nil,
"forgot-password-login-hour-#{normalized_login_param}",
5,
1.hour,
).performed!
end end
json = success_json json = success_json
@ -566,7 +578,8 @@ class SessionController < ApplicationController
redirect_url = params[:return_url].presence || SiteSetting.logout_redirect.presence redirect_url = params[:return_url].presence || SiteSetting.logout_redirect.presence
sso = SiteSetting.enable_discourse_connect sso = SiteSetting.enable_discourse_connect
only_one_authenticator = !SiteSetting.enable_local_logins && Discourse.enabled_authenticators.length == 1 only_one_authenticator =
!SiteSetting.enable_local_logins && Discourse.enabled_authenticators.length == 1
if SiteSetting.login_required && (sso || only_one_authenticator) if SiteSetting.login_required && (sso || only_one_authenticator)
# In this situation visiting most URLs will start the auth process again # In this situation visiting most URLs will start the auth process again
# Go to the `/login` page to avoid an immediate redirect # Go to the `/login` page to avoid an immediate redirect
@ -575,16 +588,19 @@ class SessionController < ApplicationController
redirect_url ||= path("/") redirect_url ||= path("/")
event_data = { redirect_url: redirect_url, user: current_user, client_ip: request&.ip, user_agent: request&.user_agent } event_data = {
redirect_url: redirect_url,
user: current_user,
client_ip: request&.ip,
user_agent: request&.user_agent,
}
DiscourseEvent.trigger(:before_session_destroy, event_data) DiscourseEvent.trigger(:before_session_destroy, event_data)
redirect_url = event_data[:redirect_url] redirect_url = event_data[:redirect_url]
reset_session reset_session
log_off_user log_off_user
if request.xhr? if request.xhr?
render json: { render json: { redirect_url: redirect_url }
redirect_url: redirect_url
}
else else
redirect_to redirect_url, allow_other_host: true redirect_to redirect_url, allow_other_host: true
end end
@ -595,17 +611,17 @@ class SessionController < ApplicationController
secure_session.set(CHALLENGE_KEY, challenge_value, expires: 1.hour) secure_session.set(CHALLENGE_KEY, challenge_value, expires: 1.hour)
render json: { render json: {
value: honeypot_value, value: honeypot_value,
challenge: challenge_value, challenge: challenge_value,
expires_in: SecureSession.expiry expires_in: SecureSession.expiry,
} }
end end
def scopes def scopes
if is_api? if is_api?
key = request.env[Auth::DefaultCurrentUserProvider::HEADER_API_KEY] key = request.env[Auth::DefaultCurrentUserProvider::HEADER_API_KEY]
api_key = ApiKey.active.with_key(key).first api_key = ApiKey.active.with_key(key).first
render_serialized(api_key.api_key_scopes, ApiKeyScopeSerializer, root: 'scopes') render_serialized(api_key.api_key_scopes, ApiKeyScopeSerializer, root: "scopes")
else else
render body: nil, status: 404 render body: nil, status: 404
end end
@ -628,8 +644,7 @@ class SessionController < ApplicationController
return if user&.admin? return if user&.admin?
if (check_login_via_email && !SiteSetting.enable_local_logins_via_email) || if (check_login_via_email && !SiteSetting.enable_local_logins_via_email) ||
SiteSetting.enable_discourse_connect || SiteSetting.enable_discourse_connect || !SiteSetting.enable_local_logins
!SiteSetting.enable_local_logins
raise Discourse::InvalidAccess, "SSO takes over local login or the local login is disallowed." raise Discourse::InvalidAccess, "SSO takes over local login or the local login is disallowed."
end end
end end
@ -637,9 +652,7 @@ class SessionController < ApplicationController
private private
def connect_verbose_warn(&blk) def connect_verbose_warn(&blk)
if SiteSetting.verbose_discourse_connect_logging Rails.logger.warn(blk.call) if SiteSetting.verbose_discourse_connect_logging
Rails.logger.warn(blk.call)
end
end end
def authenticate_second_factor(user) def authenticate_second_factor(user)
@ -660,9 +673,7 @@ class SessionController < ApplicationController
def login_error_check(user) def login_error_check(user)
return failed_to_login(user) if user.suspended? return failed_to_login(user) if user.suspended?
if ScreenedIpAddress.should_block?(request.remote_ip) return not_allowed_from_ip_address(user) if ScreenedIpAddress.should_block?(request.remote_ip)
return not_allowed_from_ip_address(user)
end
if ScreenedIpAddress.block_admin_login?(user, request.remote_ip) if ScreenedIpAddress.block_admin_login?(user, request.remote_ip)
admin_not_allowed_from_ip_address(user) admin_not_allowed_from_ip_address(user)
@ -684,11 +695,11 @@ class SessionController < ApplicationController
def not_activated(user) def not_activated(user)
session[ACTIVATE_USER_KEY] = user.id session[ACTIVATE_USER_KEY] = user.id
render json: { render json: {
error: I18n.t("login.not_activated"), error: I18n.t("login.not_activated"),
reason: 'not_activated', reason: "not_activated",
sent_to_email: user.find_email || user.email, sent_to_email: user.find_email || user.email,
current_email: user.email current_email: user.email,
} }
end end
def not_allowed_from_ip_address(user) def not_allowed_from_ip_address(user)
@ -700,10 +711,7 @@ class SessionController < ApplicationController
end end
def failed_to_login(user) def failed_to_login(user)
{ { error: user.suspended_message, reason: "suspended" }
error: user.suspended_message,
reason: 'suspended'
}
end end
def login(user, second_factor_auth_result) def login(user, second_factor_auth_result)
@ -712,11 +720,11 @@ class SessionController < ApplicationController
log_on_user(user) log_on_user(user)
if payload = cookies.delete(:sso_payload) if payload = cookies.delete(:sso_payload)
confirmed_2fa_during_login = ( confirmed_2fa_during_login =
second_factor_auth_result&.ok && (
second_factor_auth_result.used_2fa_method.present? && second_factor_auth_result&.ok && second_factor_auth_result.used_2fa_method.present? &&
second_factor_auth_result.used_2fa_method != UserSecondFactor.methods[:backup_codes] second_factor_auth_result.used_2fa_method != UserSecondFactor.methods[:backup_codes]
) )
sso_provider(payload, confirmed_2fa_during_login) sso_provider(payload, confirmed_2fa_during_login)
else else
render_serialized(user, UserSerializer) render_serialized(user, UserSerializer)
@ -728,20 +736,20 @@ class SessionController < ApplicationController
nil, nil,
"login-hr-#{request.remote_ip}", "login-hr-#{request.remote_ip}",
SiteSetting.max_logins_per_ip_per_hour, SiteSetting.max_logins_per_ip_per_hour,
1.hour 1.hour,
).performed! ).performed!
RateLimiter.new( RateLimiter.new(
nil, nil,
"login-min-#{request.remote_ip}", "login-min-#{request.remote_ip}",
SiteSetting.max_logins_per_ip_per_minute, SiteSetting.max_logins_per_ip_per_minute,
1.minute 1.minute,
).performed! ).performed!
end end
def render_sso_error(status:, text:) def render_sso_error(status:, text:)
@sso_error = text @sso_error = text
render status: status, layout: 'no_ember' render status: status, layout: "no_ember"
end end
# extension to allow plugins to customize the SSO URL # extension to allow plugins to customize the SSO URL
@ -769,9 +777,15 @@ class SessionController < ApplicationController
raise Invite::ValidationFailed.new(I18n.t("invite.not_matching_email")) raise Invite::ValidationFailed.new(I18n.t("invite.not_matching_email"))
end end
elsif invite.expired? elsif invite.expired?
raise Invite::ValidationFailed.new(I18n.t('invite.expired', base_url: Discourse.base_url)) raise Invite::ValidationFailed.new(I18n.t("invite.expired", base_url: Discourse.base_url))
elsif invite.redeemed? elsif invite.redeemed?
raise Invite::ValidationFailed.new(I18n.t('invite.not_found_template', site_name: SiteSetting.title, base_url: Discourse.base_url)) raise Invite::ValidationFailed.new(
I18n.t(
"invite.not_found_template",
site_name: SiteSetting.title,
base_url: Discourse.base_url,
),
)
end end
invite invite
@ -785,11 +799,11 @@ class SessionController < ApplicationController
ip_address: request.remote_ip, ip_address: request.remote_ip,
session: session, session: session,
email: sso.email, email: sso.email,
redeeming_user: redeeming_user redeeming_user: redeeming_user,
).redeem ).redeem
secure_session["invite-key"] = nil secure_session["invite-key"] = nil
# note - more specific errors are handled in the sso_login method # note - more specific errors are handled in the sso_login method
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
Rails.logger.warn("SSO invite redemption failed: #{e}") Rails.logger.warn("SSO invite redemption failed: #{e}")
raise Invite::RedemptionFailed raise Invite::RedemptionFailed

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class SimilarTopicsController < ApplicationController class SimilarTopicsController < ApplicationController
class SimilarTopic class SimilarTopic
def initialize(topic) def initialize(topic)
@topic = topic @topic = topic
@ -26,5 +25,4 @@ class SimilarTopicsController < ApplicationController
topics.map! { |t| SimilarTopic.new(t) } topics.map! { |t| SimilarTopic.new(t) }
render_serialized(topics, SimilarTopicSerializer, root: :similar_topics, rest_serializer: true) render_serialized(topics, SimilarTopicSerializer, root: :similar_topics, rest_serializer: true)
end end
end end

View File

@ -3,7 +3,7 @@
class SiteController < ApplicationController class SiteController < ApplicationController
layout false layout false
skip_before_action :preload_json, :check_xhr skip_before_action :preload_json, :check_xhr
skip_before_action :redirect_to_login_if_required, only: ['basic_info', 'statistics'] skip_before_action :redirect_to_login_if_required, only: %w[basic_info statistics]
def site def site
render json: Site.json_for(guardian) render json: Site.json_for(guardian)
@ -33,9 +33,9 @@ class SiteController < ApplicationController
favicon_url: UrlHelper.absolute(SiteSetting.site_favicon_url), favicon_url: UrlHelper.absolute(SiteSetting.site_favicon_url),
title: SiteSetting.title, title: SiteSetting.title,
description: SiteSetting.site_description, description: SiteSetting.site_description,
header_primary_color: ColorScheme.hex_for_name('header_primary') || '333333', header_primary_color: ColorScheme.hex_for_name("header_primary") || "333333",
header_background_color: ColorScheme.hex_for_name('header_background') || 'ffffff', header_background_color: ColorScheme.hex_for_name("header_background") || "ffffff",
login_required: SiteSetting.login_required login_required: SiteSetting.login_required,
} }
if mobile_logo_url = SiteSetting.site_mobile_logo_url.presence if mobile_logo_url = SiteSetting.site_mobile_logo_url.presence
@ -49,7 +49,7 @@ class SiteController < ApplicationController
end end
def statistics def statistics
return redirect_to path('/') unless SiteSetting.share_anonymized_statistics? return redirect_to path("/") unless SiteSetting.share_anonymized_statistics?
render json: About.fetch_cached_stats render json: About.fetch_cached_stats
end end
end end

View File

@ -6,9 +6,7 @@ class SitemapController < ApplicationController
before_action :check_sitemap_enabled before_action :check_sitemap_enabled
def index def index
@sitemaps = Sitemap @sitemaps = Sitemap.where(enabled: true).where.not(name: Sitemap::NEWS_SITEMAP_NAME)
.where(enabled: true)
.where.not(name: Sitemap::NEWS_SITEMAP_NAME)
render :index render :index
end end
@ -18,37 +16,46 @@ class SitemapController < ApplicationController
sitemap = Sitemap.find_by(enabled: true, name: index.to_s) sitemap = Sitemap.find_by(enabled: true, name: index.to_s)
raise Discourse::NotFound if sitemap.nil? raise Discourse::NotFound if sitemap.nil?
@output = Rails.cache.fetch("sitemap/#{sitemap.name}/#{sitemap.max_page_size}", expires_in: 24.hours) do @output =
@topics = sitemap.topics Rails
render :page, content_type: 'text/xml; charset=UTF-8' .cache
end .fetch("sitemap/#{sitemap.name}/#{sitemap.max_page_size}", expires_in: 24.hours) do
@topics = sitemap.topics
render :page, content_type: "text/xml; charset=UTF-8"
end
render plain: @output, content_type: 'text/xml; charset=UTF-8' unless performed? render plain: @output, content_type: "text/xml; charset=UTF-8" unless performed?
end end
def recent def recent
sitemap = Sitemap.touch(Sitemap::RECENT_SITEMAP_NAME) sitemap = Sitemap.touch(Sitemap::RECENT_SITEMAP_NAME)
@output = Rails.cache.fetch("sitemap/recent/#{sitemap.last_posted_at.to_i}", expires_in: 1.hour) do @output =
@topics = sitemap.topics Rails
render :page, content_type: 'text/xml; charset=UTF-8' .cache
end .fetch("sitemap/recent/#{sitemap.last_posted_at.to_i}", expires_in: 1.hour) do
@topics = sitemap.topics
render :page, content_type: "text/xml; charset=UTF-8"
end
render plain: @output, content_type: 'text/xml; charset=UTF-8' unless performed? render plain: @output, content_type: "text/xml; charset=UTF-8" unless performed?
end end
def news def news
sitemap = Sitemap.touch(Sitemap::NEWS_SITEMAP_NAME) sitemap = Sitemap.touch(Sitemap::NEWS_SITEMAP_NAME)
@output = Rails.cache.fetch("sitemap/news", expires_in: 5.minutes) do @output =
dlocale = SiteSetting.default_locale.downcase Rails
@locale = dlocale.gsub(/_.*/, '') .cache
@locale = dlocale.sub('_', '-') if @locale === "zh" .fetch("sitemap/news", expires_in: 5.minutes) do
@topics = sitemap.topics dlocale = SiteSetting.default_locale.downcase
render :news, content_type: 'text/xml; charset=UTF-8' @locale = dlocale.gsub(/_.*/, "")
end @locale = dlocale.sub("_", "-") if @locale === "zh"
@topics = sitemap.topics
render :news, content_type: "text/xml; charset=UTF-8"
end
render plain: @output, content_type: 'text/xml; charset=UTF-8' unless performed? render plain: @output, content_type: "text/xml; charset=UTF-8" unless performed?
end end
private private
@ -58,7 +65,7 @@ class SitemapController < ApplicationController
end end
def build_sitemap_topic_url(slug, id, posts_count = nil) def build_sitemap_topic_url(slug, id, posts_count = nil)
base_url = [Discourse.base_url, 't', slug, id].join('/') base_url = [Discourse.base_url, "t", slug, id].join("/")
return base_url if posts_count.nil? return base_url if posts_count.nil?
page, mod = posts_count.divmod(TopicView.chunk_size) page, mod = posts_count.divmod(TopicView.chunk_size)
@ -67,5 +74,4 @@ class SitemapController < ApplicationController
page > 1 ? "#{base_url}?page=#{page}" : base_url page > 1 ? "#{base_url}?page=#{page}" : base_url
end end
helper_method :build_sitemap_topic_url helper_method :build_sitemap_topic_url
end end

View File

@ -2,26 +2,41 @@
class StaticController < ApplicationController class StaticController < ApplicationController
skip_before_action :check_xhr, :redirect_to_login_if_required skip_before_action :check_xhr, :redirect_to_login_if_required
skip_before_action :verify_authenticity_token, only: [:brotli_asset, :cdn_asset, :enter, :favicon, :service_worker_asset] skip_before_action :verify_authenticity_token,
skip_before_action :preload_json, only: [:brotli_asset, :cdn_asset, :enter, :favicon, :service_worker_asset] only: %i[brotli_asset cdn_asset enter favicon service_worker_asset]
skip_before_action :handle_theme, only: [:brotli_asset, :cdn_asset, :enter, :favicon, :service_worker_asset] skip_before_action :preload_json,
only: %i[brotli_asset cdn_asset enter favicon service_worker_asset]
skip_before_action :handle_theme,
only: %i[brotli_asset cdn_asset enter favicon service_worker_asset]
before_action :apply_cdn_headers, only: [:brotli_asset, :cdn_asset, :enter, :favicon, :service_worker_asset] before_action :apply_cdn_headers,
only: %i[brotli_asset cdn_asset enter favicon service_worker_asset]
PAGES_WITH_EMAIL_PARAM = ['login', 'password_reset', 'signup'] PAGES_WITH_EMAIL_PARAM = %w[login password_reset signup]
MODAL_PAGES = ['password_reset', 'signup'] MODAL_PAGES = %w[password_reset signup]
DEFAULT_PAGES = { DEFAULT_PAGES = {
"faq" => { redirect: "faq_url", topic_id: "guidelines_topic_id" }, "faq" => {
"tos" => { redirect: "tos_url", topic_id: "tos_topic_id" }, redirect: "faq_url",
"privacy" => { redirect: "privacy_policy_url", topic_id: "privacy_topic_id" }, topic_id: "guidelines_topic_id",
},
"tos" => {
redirect: "tos_url",
topic_id: "tos_topic_id",
},
"privacy" => {
redirect: "privacy_policy_url",
topic_id: "privacy_topic_id",
},
} }
CUSTOM_PAGES = {} # Add via `#add_topic_static_page` in plugin API CUSTOM_PAGES = {} # Add via `#add_topic_static_page` in plugin API
def show def show
return redirect_to(path '/') if current_user && (params[:id] == 'login' || params[:id] == 'signup') if current_user && (params[:id] == "login" || params[:id] == "signup")
return redirect_to(path "/")
end
if SiteSetting.login_required? && current_user.nil? && ['faq', 'guidelines'].include?(params[:id]) if SiteSetting.login_required? && current_user.nil? && %w[faq guidelines].include?(params[:id])
return redirect_to path('/login') return redirect_to path("/login")
end end
map = DEFAULT_PAGES.deep_merge(CUSTOM_PAGES) map = DEFAULT_PAGES.deep_merge(CUSTOM_PAGES)
@ -34,10 +49,10 @@ class StaticController < ApplicationController
end end
# The /guidelines route ALWAYS shows our FAQ, ignoring the faq_url site setting. # The /guidelines route ALWAYS shows our FAQ, ignoring the faq_url site setting.
@page = 'faq' if @page == 'guidelines' @page = "faq" if @page == "guidelines"
# Don't allow paths like ".." or "/" or anything hacky like that # Don't allow paths like ".." or "/" or anything hacky like that
@page = @page.gsub(/[^a-z0-9\_\-]/, '') @page = @page.gsub(/[^a-z0-9\_\-]/, "")
if map.has_key?(@page) if map.has_key?(@page)
topic_id = map[@page][:topic_id] topic_id = map[@page][:topic_id]
@ -46,11 +61,12 @@ class StaticController < ApplicationController
@topic = Topic.find_by_id(SiteSetting.get(topic_id)) @topic = Topic.find_by_id(SiteSetting.get(topic_id))
raise Discourse::NotFound unless @topic raise Discourse::NotFound unless @topic
title_prefix = if I18n.exists?("js.#{@page}") title_prefix =
I18n.t("js.#{@page}") if I18n.exists?("js.#{@page}")
else I18n.t("js.#{@page}")
@topic.title else
end @topic.title
end
@title = "#{title_prefix} - #{SiteSetting.title}" @title = "#{title_prefix} - #{SiteSetting.title}"
@body = @topic.posts.first.cooked @body = @topic.posts.first.cooked
@faq_overridden = !SiteSetting.faq_url.blank? @faq_overridden = !SiteSetting.faq_url.blank?
@ -104,10 +120,7 @@ class StaticController < ApplicationController
forum_uri = URI(Discourse.base_url) forum_uri = URI(Discourse.base_url)
uri = URI(redirect_location) uri = URI(redirect_location)
if uri.path.present? && if uri.path.present? && (uri.host.blank? || uri.host == forum_uri.host) && uri.path !~ /\./
(uri.host.blank? || uri.host == forum_uri.host) &&
uri.path !~ /\./
destination = "#{uri.path}#{uri.query ? "?#{uri.query}" : ""}" destination = "#{uri.path}#{uri.query ? "?#{uri.query}" : ""}"
end end
rescue URI::Error rescue URI::Error
@ -135,31 +148,33 @@ class StaticController < ApplicationController
is_asset_path is_asset_path
hijack do hijack do
data = DistributedMemoizer.memoize("FAVICON#{SiteIconManager.favicon_url}", 60 * 30) do data =
favicon = SiteIconManager.favicon DistributedMemoizer.memoize("FAVICON#{SiteIconManager.favicon_url}", 60 * 30) do
next "" unless favicon favicon = SiteIconManager.favicon
next "" unless favicon
if Discourse.store.external? if Discourse.store.external?
begin begin
file = FileHelper.download( file =
Discourse.store.cdn_url(favicon.url), FileHelper.download(
max_file_size: favicon.filesize, Discourse.store.cdn_url(favicon.url),
tmp_file_name: FAVICON, max_file_size: favicon.filesize,
follow_redirect: true tmp_file_name: FAVICON,
) follow_redirect: true,
)
file&.read || "" file&.read || ""
rescue => e rescue => e
AdminDashboardData.add_problem_message('dashboard.bad_favicon_url', 1800) AdminDashboardData.add_problem_message("dashboard.bad_favicon_url", 1800)
Rails.logger.debug("Failed to fetch favicon #{favicon.url}: #{e}\n#{e.backtrace}") Rails.logger.debug("Failed to fetch favicon #{favicon.url}: #{e}\n#{e.backtrace}")
"" ""
ensure ensure
file&.unlink file&.unlink
end
else
File.read(Rails.root.join("public", favicon.url[1..-1]))
end end
else
File.read(Rails.root.join("public", favicon.url[1..-1]))
end end
end
if data.bytesize == 0 if data.bytesize == 0
@@default_favicon ||= File.read(Rails.root + "public/images/default-favicon.png") @@default_favicon ||= File.read(Rails.root + "public/images/default-favicon.png")
@ -178,9 +193,7 @@ class StaticController < ApplicationController
def brotli_asset def brotli_asset
is_asset_path is_asset_path
serve_asset(".br") do serve_asset(".br") { response.headers["Content-Encoding"] = "br" }
response.headers["Content-Encoding"] = 'br'
end
end end
def cdn_asset def cdn_asset
@ -199,20 +212,22 @@ class StaticController < ApplicationController
# However, ensure that these may be cached and served for longer on servers. # However, ensure that these may be cached and served for longer on servers.
immutable_for 1.year immutable_for 1.year
if Rails.application.assets_manifest.assets['service-worker.js'] if Rails.application.assets_manifest.assets["service-worker.js"]
path = File.expand_path(Rails.root + "public/assets/#{Rails.application.assets_manifest.assets['service-worker.js']}") path =
File.expand_path(
Rails.root +
"public/assets/#{Rails.application.assets_manifest.assets["service-worker.js"]}",
)
response.headers["Last-Modified"] = File.ctime(path).httpdate response.headers["Last-Modified"] = File.ctime(path).httpdate
end end
content = Rails.application.assets_manifest.find_sources('service-worker.js').first content = Rails.application.assets_manifest.find_sources("service-worker.js").first
base_url = File.dirname(helpers.script_asset_path('service-worker')) base_url = File.dirname(helpers.script_asset_path("service-worker"))
content = content.sub( content =
/^\/\/# sourceMappingURL=(service-worker-.+\.map)$/ content.sub(%r{^//# sourceMappingURL=(service-worker-.+\.map)$}) do
) { "//# sourceMappingURL=#{base_url}/#{Regexp.last_match(1)}" } "//# sourceMappingURL=#{base_url}/#{Regexp.last_match(1)}"
render( end
plain: content, render(plain: content, content_type: "application/javascript")
content_type: 'application/javascript'
)
end end
end end
end end
@ -220,7 +235,6 @@ class StaticController < ApplicationController
protected protected
def serve_asset(suffix = nil) def serve_asset(suffix = nil)
path = File.expand_path(Rails.root + "public/assets/#{params[:path]}#{suffix}") path = File.expand_path(Rails.root + "public/assets/#{params[:path]}#{suffix}")
# SECURITY what if path has /../ # SECURITY what if path has /../
@ -254,12 +268,10 @@ class StaticController < ApplicationController
immutable_for 1.year immutable_for 1.year
# disable NGINX mucking with transfer # disable NGINX mucking with transfer
request.env['sendfile.type'] = '' request.env["sendfile.type"] = ""
opts = { disposition: nil } opts = { disposition: nil }
opts[:type] = "application/javascript" if params[:path] =~ /\.js$/ opts[:type] = "application/javascript" if params[:path] =~ /\.js$/
send_file(path, opts) send_file(path, opts)
end end
end end

View File

@ -12,7 +12,7 @@ class StepsController < ApplicationController
updater.update updater.update
if updater.success? if updater.success?
result = { success: 'OK' } result = { success: "OK" }
result[:refresh_required] = true if updater.refresh_required? result[:refresh_required] = true if updater.refresh_required?
render json: result render json: result
else else
@ -23,5 +23,4 @@ class StepsController < ApplicationController
render json: { errors: errors }, status: 422 render json: { errors: errors }, status: 422
end end
end end
end end

View File

@ -1,9 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
class StylesheetsController < ApplicationController class StylesheetsController < ApplicationController
skip_before_action :preload_json, :redirect_to_login_if_required, :check_xhr, :verify_authenticity_token, only: [:show, :show_source_map, :color_scheme] skip_before_action :preload_json,
:redirect_to_login_if_required,
:check_xhr,
:verify_authenticity_token,
only: %i[show show_source_map color_scheme]
before_action :apply_cdn_headers, only: [:show, :show_source_map, :color_scheme] before_action :apply_cdn_headers, only: %i[show show_source_map color_scheme]
def show_source_map def show_source_map
show_resource(source_map: true) show_resource(source_map: true)
@ -20,14 +24,13 @@ class StylesheetsController < ApplicationController
params.permit("theme_id") params.permit("theme_id")
manager = Stylesheet::Manager.new(theme_id: params[:theme_id]) manager = Stylesheet::Manager.new(theme_id: params[:theme_id])
stylesheet = manager.color_scheme_stylesheet_details(params[:id], 'all') stylesheet = manager.color_scheme_stylesheet_details(params[:id], "all")
render json: stylesheet render json: stylesheet
end end
protected protected
def show_resource(source_map: false) def show_resource(source_map: false)
extension = source_map ? ".css.map" : ".css" extension = source_map ? ".css.map" : ".css"
no_cookies no_cookies
@ -47,7 +50,7 @@ class StylesheetsController < ApplicationController
if digest if digest
query = query.where(digest: digest) query = query.where(digest: digest)
else else
query = query.order('id desc') query = query.order("id desc")
end end
# Security note, safe due to route constraint # Security note, safe due to route constraint
@ -58,9 +61,7 @@ class StylesheetsController < ApplicationController
stylesheet_time = query.pluck_first(:created_at) stylesheet_time = query.pluck_first(:created_at)
if !stylesheet_time handle_missing_cache(location, target, digest) if !stylesheet_time
handle_missing_cache(location, target, digest)
end
if cache_time && stylesheet_time && stylesheet_time <= cache_time if cache_time && stylesheet_time && stylesheet_time <= cache_time
return render body: nil, status: 304 return render body: nil, status: 304
@ -76,10 +77,10 @@ class StylesheetsController < ApplicationController
end end
if Rails.env == "development" if Rails.env == "development"
response.headers['Last-Modified'] = Time.zone.now.httpdate response.headers["Last-Modified"] = Time.zone.now.httpdate
immutable_for(1.second) immutable_for(1.second)
else else
response.headers['Last-Modified'] = stylesheet_time.httpdate if stylesheet_time response.headers["Last-Modified"] = stylesheet_time.httpdate if stylesheet_time
immutable_for(1.year) immutable_for(1.year)
end end
send_file(location, disposition: :inline) send_file(location, disposition: :inline)
@ -104,5 +105,4 @@ class StylesheetsController < ApplicationController
rescue Errno::ENOENT rescue Errno::ENOENT
end end
end end
end end

View File

@ -1,14 +1,17 @@
# frozen_string_literal: true # frozen_string_literal: true
class SvgSpriteController < ApplicationController class SvgSpriteController < ApplicationController
skip_before_action :preload_json, :redirect_to_login_if_required, :check_xhr, :verify_authenticity_token, only: [:show, :search, :svg_icon] skip_before_action :preload_json,
:redirect_to_login_if_required,
:check_xhr,
:verify_authenticity_token,
only: %i[show search svg_icon]
before_action :apply_cdn_headers, only: [:show, :search, :svg_icon] before_action :apply_cdn_headers, only: %i[show search svg_icon]
requires_login except: [:show, :svg_icon] requires_login except: %i[show svg_icon]
def show def show
no_cookies no_cookies
RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do
@ -24,20 +27,19 @@ class SvgSpriteController < ApplicationController
response.headers["Content-Length"] = svg_sprite.bytesize.to_s response.headers["Content-Length"] = svg_sprite.bytesize.to_s
immutable_for 1.year immutable_for 1.year
render plain: svg_sprite, disposition: nil, content_type: 'application/javascript' render plain: svg_sprite, disposition: nil, content_type: "application/javascript"
end end
end end
def search def search
RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do
keyword = params.require(:keyword) keyword = params.require(:keyword)
data = SvgSprite.search(keyword) data = SvgSprite.search(keyword)
if data.blank? if data.blank?
render body: nil, status: 404 render body: nil, status: 404
else else
render plain: data.inspect, disposition: nil, content_type: 'text/plain' render plain: data.inspect, disposition: nil, content_type: "text/plain"
end end
end end
end end
@ -65,14 +67,14 @@ class SvgSpriteController < ApplicationController
else else
doc = Nokogiri.XML(icon) doc = Nokogiri.XML(icon)
doc.at_xpath("symbol").name = "svg" doc.at_xpath("symbol").name = "svg"
doc.at_xpath("svg")['xmlns'] = "http://www.w3.org/2000/svg" doc.at_xpath("svg")["xmlns"] = "http://www.w3.org/2000/svg"
doc.at_xpath("svg")['fill'] = adjust_hex(params[:color]) if params[:color] doc.at_xpath("svg")["fill"] = adjust_hex(params[:color]) if params[:color]
response.headers["Last-Modified"] = 1.years.ago.httpdate response.headers["Last-Modified"] = 1.years.ago.httpdate
response.headers["Content-Length"] = doc.to_s.bytesize.to_s response.headers["Content-Length"] = doc.to_s.bytesize.to_s
immutable_for 1.day immutable_for 1.day
render plain: doc, disposition: nil, content_type: 'image/svg+xml' render plain: doc, disposition: nil, content_type: "image/svg+xml"
end end
end end
end end

View File

@ -4,12 +4,17 @@ class TagGroupsController < ApplicationController
requires_login except: [:search] requires_login except: [:search]
before_action :ensure_staff, except: [:search] before_action :ensure_staff, except: [:search]
skip_before_action :check_xhr, only: [:index, :show, :new] skip_before_action :check_xhr, only: %i[index show new]
before_action :fetch_tag_group, only: [:show, :update, :destroy] before_action :fetch_tag_group, only: %i[show update destroy]
def index def index
tag_groups = TagGroup.order('name ASC').includes(:parent_tag).preload(:tags).all tag_groups = TagGroup.order("name ASC").includes(:parent_tag).preload(:tags).all
serializer = ActiveModel::ArraySerializer.new(tag_groups, each_serializer: TagGroupSerializer, root: 'tag_groups') serializer =
ActiveModel::ArraySerializer.new(
tag_groups,
each_serializer: TagGroupSerializer,
root: "tag_groups",
)
respond_to do |format| respond_to do |format|
format.html do format.html do
store_preloaded "tagGroups", MultiJson.dump(serializer) store_preloaded "tagGroups", MultiJson.dump(serializer)
@ -31,8 +36,13 @@ class TagGroupsController < ApplicationController
end end
def new def new
tag_groups = TagGroup.order('name ASC').includes(:parent_tag).preload(:tags).all tag_groups = TagGroup.order("name ASC").includes(:parent_tag).preload(:tags).all
serializer = ActiveModel::ArraySerializer.new(tag_groups, each_serializer: TagGroupSerializer, root: 'tag_groups') serializer =
ActiveModel::ArraySerializer.new(
tag_groups,
each_serializer: TagGroupSerializer,
root: "tag_groups",
)
store_preloaded "tagGroup", MultiJson.dump(serializer) store_preloaded "tagGroup", MultiJson.dump(serializer)
render "default/empty" render "default/empty"
end end
@ -63,19 +73,18 @@ class TagGroupsController < ApplicationController
def search def search
matches = TagGroup.includes(:tags).visible(guardian).all matches = TagGroup.includes(:tags).visible(guardian).all
if params[:q].present? matches = matches.where("lower(name) ILIKE ?", "%#{params[:q].strip}%") if params[:q].present?
matches = matches.where('lower(name) ILIKE ?', "%#{params[:q].strip}%")
end
if params[:names].present? if params[:names].present?
matches = matches.where('lower(NAME) in (?)', params[:names].map(&:downcase)) matches = matches.where("lower(NAME) in (?)", params[:names].map(&:downcase))
end end
matches = matches.order('name').limit(params[:limit] || 5) matches = matches.order("name").limit(params[:limit] || 5)
render json: { render json: {
results: matches.map { |x| { name: x.name, tag_names: x.tags.base_tags.pluck(:name).sort } } results:
} matches.map { |x| { name: x.name, tag_names: x.tags.base_tags.pluck(:name).sort } },
}
end end
private private
@ -88,14 +97,8 @@ class TagGroupsController < ApplicationController
tag_group = params.delete(:tag_group) tag_group = params.delete(:tag_group)
params.merge!(tag_group.permit!) if tag_group params.merge!(tag_group.permit!) if tag_group
result = params.permit( result =
:id, params.permit(:id, :name, :one_per_topic, tag_names: [], parent_tag_name: [], permissions: {})
:name,
:one_per_topic,
tag_names: [],
parent_tag_name: [],
permissions: {}
)
result[:tag_names] ||= [] result[:tag_names] ||= []
result[:parent_tag_name] ||= [] result[:parent_tag_name] ||= []

View File

@ -5,29 +5,32 @@ class TagsController < ::ApplicationController
include TopicQueryParams include TopicQueryParams
before_action :ensure_tags_enabled before_action :ensure_tags_enabled
before_action :ensure_visible, only: [:show, :info] before_action :ensure_visible, only: %i[show info]
def self.show_methods def self.show_methods
Discourse.anonymous_filters.map { |f| :"show_#{f}" } Discourse.anonymous_filters.map { |f| :"show_#{f}" }
end end
requires_login except: [ requires_login except: [:index, :show, :tag_feed, :search, :info, *show_methods]
:index,
:show,
:tag_feed,
:search,
:info,
*show_methods
]
skip_before_action :check_xhr, only: [:tag_feed, :show, :index, *show_methods] skip_before_action :check_xhr, only: [:tag_feed, :show, :index, *show_methods]
before_action :set_category, except: [:index, :update, :destroy, before_action :set_category,
:tag_feed, :search, :notifications, :update_notifications, :personal_messages, :info] except: %i[
index
update
destroy
tag_feed
search
notifications
update_notifications
personal_messages
info
]
before_action :fetch_tag, only: [:info, :create_synonyms, :destroy_synonym] before_action :fetch_tag, only: %i[info create_synonyms destroy_synonym]
after_action :add_noindex_header, except: [:index, :show] after_action :add_noindex_header, except: %i[index show]
def index def index
@description_meta = I18n.t("tags.title") @description_meta = I18n.t("tags.title")
@ -39,9 +42,22 @@ class TagsController < ::ApplicationController
ungrouped_tags = Tag.where("tags.id NOT IN (SELECT tag_id FROM tag_group_memberships)") ungrouped_tags = Tag.where("tags.id NOT IN (SELECT tag_id FROM tag_group_memberships)")
ungrouped_tags = ungrouped_tags.where("tags.topic_count > 0") unless show_all_tags ungrouped_tags = ungrouped_tags.where("tags.topic_count > 0") unless show_all_tags
grouped_tag_counts = TagGroup.visible(guardian).order('name ASC').includes(:tags).map do |tag_group| grouped_tag_counts =
{ id: tag_group.id, name: tag_group.name, tags: self.class.tag_counts_json(tag_group.tags.where(target_tag_id: nil), show_pm_tags: guardian.can_tag_pms?) } TagGroup
end .visible(guardian)
.order("name ASC")
.includes(:tags)
.map do |tag_group|
{
id: tag_group.id,
name: tag_group.name,
tags:
self.class.tag_counts_json(
tag_group.tags.where(target_tag_id: nil),
show_pm_tags: guardian.can_tag_pms?,
),
}
end
@tags = self.class.tag_counts_json(ungrouped_tags, show_pm_tags: guardian.can_tag_pms?) @tags = self.class.tag_counts_json(ungrouped_tags, show_pm_tags: guardian.can_tag_pms?)
@extras = { tag_groups: grouped_tag_counts } @extras = { tag_groups: grouped_tag_counts }
@ -49,41 +65,40 @@ class TagsController < ::ApplicationController
tags = show_all_tags ? Tag.all : Tag.where("tags.topic_count > 0") tags = show_all_tags ? Tag.all : Tag.where("tags.topic_count > 0")
unrestricted_tags = DiscourseTagging.filter_visible(tags.where(target_tag_id: nil), guardian) unrestricted_tags = DiscourseTagging.filter_visible(tags.where(target_tag_id: nil), guardian)
categories = Category.where("id IN (SELECT category_id FROM category_tags)") categories =
.where("id IN (?)", guardian.allowed_category_ids) Category
.includes(:tags) .where("id IN (SELECT category_id FROM category_tags)")
.where("id IN (?)", guardian.allowed_category_ids)
.includes(:tags)
category_tag_counts = categories.map do |c| category_tag_counts =
category_tags = self.class.tag_counts_json( categories
DiscourseTagging.filter_visible(c.tags.where(target_tag_id: nil), guardian) .map do |c|
) category_tags =
next if category_tags.empty? self.class.tag_counts_json(
{ id: c.id, tags: category_tags } DiscourseTagging.filter_visible(c.tags.where(target_tag_id: nil), guardian),
end.compact )
next if category_tags.empty?
{ id: c.id, tags: category_tags }
end
.compact
@tags = self.class.tag_counts_json(unrestricted_tags, show_pm_tags: guardian.can_tag_pms?) @tags = self.class.tag_counts_json(unrestricted_tags, show_pm_tags: guardian.can_tag_pms?)
@extras = { categories: category_tag_counts } @extras = { categories: category_tag_counts }
end end
respond_to do |format| respond_to do |format|
format.html { render :index }
format.html do format.json { render json: { tags: @tags, extras: @extras } }
render :index
end
format.json do
render json: {
tags: @tags,
extras: @extras
}
end
end end
end end
Discourse.filters.each do |filter| Discourse.filters.each do |filter|
define_method("show_#{filter}") do define_method("show_#{filter}") do
@tag_id = params[:tag_id].force_encoding("UTF-8") @tag_id = params[:tag_id].force_encoding("UTF-8")
@additional_tags = params[:additional_tag_ids].to_s.split('/').map { |t| t.force_encoding("UTF-8") } @additional_tags =
params[:additional_tag_ids].to_s.split("/").map { |t| t.force_encoding("UTF-8") }
list_opts = build_topic_list_options list_opts = build_topic_list_options
@list = nil @list = nil
@ -101,14 +116,14 @@ class TagsController < ::ApplicationController
@list.more_topics_url = construct_url_with(:next, list_opts) @list.more_topics_url = construct_url_with(:next, list_opts)
@list.prev_topics_url = construct_url_with(:prev, list_opts) @list.prev_topics_url = construct_url_with(:prev, list_opts)
@rss = "tag" @rss = "tag"
@description_meta = I18n.t("rss_by_tag", tag: tag_params.join(' & ')) @description_meta = I18n.t("rss_by_tag", tag: tag_params.join(" & "))
@title = @description_meta @title = @description_meta
canonical_params = params.slice(:category_slug_path_with_id, :tag_id) canonical_params = params.slice(:category_slug_path_with_id, :tag_id)
canonical_method = url_method(canonical_params) canonical_method = url_method(canonical_params)
canonical_url "#{Discourse.base_url_no_prefix}#{public_send(canonical_method, *(canonical_params.values.map { |t| t.force_encoding("UTF-8") }))}" canonical_url "#{Discourse.base_url_no_prefix}#{public_send(canonical_method, *(canonical_params.values.map { |t| t.force_encoding("UTF-8") }))}"
if @list.topics.size == 0 && params[:tag_id] != 'none' && !Tag.where_name(@tag_id).exists? if @list.topics.size == 0 && params[:tag_id] != "none" && !Tag.where_name(@tag_id).exists?
raise Discourse::NotFound.new("tag not found", check_permalinks: true) raise Discourse::NotFound.new("tag not found", check_permalinks: true)
else else
respond_with_list(@list) respond_with_list(@list)
@ -121,12 +136,7 @@ class TagsController < ::ApplicationController
end end
def info def info
render_serialized( render_serialized(@tag, DetailedTagSerializer, rest_serializer: true, root: :tag_info)
@tag,
DetailedTagSerializer,
rest_serializer: true,
root: :tag_info
)
end end
def update def update
@ -141,7 +151,11 @@ class TagsController < ::ApplicationController
end end
tag.description = params[:tag][:description] if params[:tag]&.has_key?(:description) tag.description = params[:tag][:description] if params[:tag]&.has_key?(:description)
if tag.save if tag.save
StaffActionLogger.new(current_user).log_custom('renamed_tag', previous_value: params[:tag_id], new_value: new_tag_name) StaffActionLogger.new(current_user).log_custom(
"renamed_tag",
previous_value: params[:tag_id],
new_value: new_tag_name,
)
render json: { tag: { id: tag.name, description: tag.description } } render json: { tag: { id: tag.name, description: tag.description } }
else else
render_json_error tag.errors.full_messages render_json_error tag.errors.full_messages
@ -149,7 +163,7 @@ class TagsController < ::ApplicationController
end end
def upload def upload
require 'csv' require "csv"
guardian.ensure_can_admin_tags! guardian.ensure_can_admin_tags!
@ -159,7 +173,9 @@ class TagsController < ::ApplicationController
begin begin
Tag.transaction do Tag.transaction do
CSV.foreach(file.tempfile) do |row| CSV.foreach(file.tempfile) do |row|
raise Discourse::InvalidParameters.new(I18n.t("tags.upload_row_too_long")) if row.length > 2 if row.length > 2
raise Discourse::InvalidParameters.new(I18n.t("tags.upload_row_too_long"))
end
tag_name = DiscourseTagging.clean_tag(row[0]) tag_name = DiscourseTagging.clean_tag(row[0])
tag_group_name = row[1] || nil tag_group_name = row[1] || nil
@ -167,7 +183,8 @@ class TagsController < ::ApplicationController
tag = Tag.find_by_name(tag_name) || Tag.create!(name: tag_name) tag = Tag.find_by_name(tag_name) || Tag.create!(name: tag_name)
if tag_group_name if tag_group_name
tag_group = TagGroup.find_by(name: tag_group_name) || TagGroup.create!(name: tag_group_name) tag_group =
TagGroup.find_by(name: tag_group_name) || TagGroup.create!(name: tag_group_name)
tag.tag_groups << tag_group unless tag.tag_groups.include?(tag_group) tag.tag_groups << tag_group unless tag.tag_groups.include?(tag_group)
end end
end end
@ -187,7 +204,7 @@ class TagsController < ::ApplicationController
def destroy_unused def destroy_unused
guardian.ensure_can_admin_tags! guardian.ensure_can_admin_tags!
tags = Tag.unused tags = Tag.unused
StaffActionLogger.new(current_user).log_custom('deleted_unused_tags', tags: tags.pluck(:name)) StaffActionLogger.new(current_user).log_custom("deleted_unused_tags", tags: tags.pluck(:name))
tags.destroy_all tags.destroy_all
render json: success_json render json: success_json
end end
@ -200,7 +217,7 @@ class TagsController < ::ApplicationController
TopicCustomField.transaction do TopicCustomField.transaction do
tag.destroy tag.destroy
StaffActionLogger.new(current_user).log_custom('deleted_tag', subject: tag_name) StaffActionLogger.new(current_user).log_custom("deleted_tag", subject: tag_name)
end end
render json: success_json render json: success_json
end end
@ -218,7 +235,7 @@ class TagsController < ::ApplicationController
latest_results = query.latest_results latest_results = query.latest_results
@topic_list = query.create_list(:by_tag, {}, latest_results) @topic_list = query.create_list(:by_tag, {}, latest_results)
render 'list/list', formats: [:rss] render "list/list", formats: [:rss]
end end
def search def search
@ -227,16 +244,14 @@ class TagsController < ::ApplicationController
selected_tags: params[:selected_tags], selected_tags: params[:selected_tags],
limit: params[:limit], limit: params[:limit],
exclude_synonyms: params[:excludeSynonyms], exclude_synonyms: params[:excludeSynonyms],
exclude_has_synonyms: params[:excludeHasSynonyms] exclude_has_synonyms: params[:excludeHasSynonyms],
} }
if filter_params[:limit] && filter_params[:limit].to_i < 0 if filter_params[:limit] && filter_params[:limit].to_i < 0
raise Discourse::InvalidParameters.new(:limit) raise Discourse::InvalidParameters.new(:limit)
end end
if params[:categoryId] filter_params[:category] = Category.find_by_id(params[:categoryId]) if params[:categoryId]
filter_params[:category] = Category.find_by_id(params[:categoryId])
end
if !params[:q].blank? if !params[:q].blank?
clean_name = DiscourseTagging.clean_tag(params[:q]) clean_name = DiscourseTagging.clean_tag(params[:q])
@ -246,27 +261,35 @@ class TagsController < ::ApplicationController
filter_params[:order_popularity] = true filter_params[:order_popularity] = true
end end
tags_with_counts, filter_result_context = DiscourseTagging.filter_allowed_tags( tags_with_counts, filter_result_context =
guardian, DiscourseTagging.filter_allowed_tags(guardian, **filter_params, with_context: true)
**filter_params,
with_context: true
)
tags = self.class.tag_counts_json(tags_with_counts, show_pm_tags: guardian.can_tag_pms?) tags = self.class.tag_counts_json(tags_with_counts, show_pm_tags: guardian.can_tag_pms?)
json_response = { results: tags } json_response = { results: tags }
if clean_name && !tags.find { |h| h[:id].downcase == clean_name.downcase } && tag = Tag.where_name(clean_name).first if clean_name && !tags.find { |h| h[:id].downcase == clean_name.downcase } &&
tag = Tag.where_name(clean_name).first
# filter_allowed_tags determined that the tag entered is not allowed # filter_allowed_tags determined that the tag entered is not allowed
json_response[:forbidden] = params[:q] json_response[:forbidden] = params[:q]
if filter_params[:exclude_synonyms] && tag.synonym? if filter_params[:exclude_synonyms] && tag.synonym?
json_response[:forbidden_message] = I18n.t("tags.forbidden.synonym", tag_name: tag.target_tag.name) json_response[:forbidden_message] = I18n.t(
"tags.forbidden.synonym",
tag_name: tag.target_tag.name,
)
elsif filter_params[:exclude_has_synonyms] && tag.synonyms.exists? elsif filter_params[:exclude_has_synonyms] && tag.synonyms.exists?
json_response[:forbidden_message] = I18n.t("tags.forbidden.has_synonyms", tag_name: tag.name) json_response[:forbidden_message] = I18n.t(
"tags.forbidden.has_synonyms",
tag_name: tag.name,
)
else else
category_names = tag.categories.where(id: guardian.allowed_category_ids).pluck(:name) category_names = tag.categories.where(id: guardian.allowed_category_ids).pluck(:name)
category_names += Category.joins(tag_groups: :tags).where(id: guardian.allowed_category_ids, "tags.id": tag.id).pluck(:name) category_names +=
Category
.joins(tag_groups: :tags)
.where(id: guardian.allowed_category_ids, "tags.id": tag.id)
.pluck(:name)
if category_names.present? if category_names.present?
category_names.uniq! category_names.uniq!
@ -274,10 +297,13 @@ class TagsController < ::ApplicationController
"tags.forbidden.restricted_to", "tags.forbidden.restricted_to",
count: category_names.count, count: category_names.count,
tag_name: tag.name, tag_name: tag.name,
category_names: category_names.join(", ") category_names: category_names.join(", "),
) )
else else
json_response[:forbidden_message] = I18n.t("tags.forbidden.in_this_category", tag_name: tag.name) json_response[:forbidden_message] = I18n.t(
"tags.forbidden.in_this_category",
tag_name: tag.name,
)
end end
end end
end end
@ -292,7 +318,9 @@ class TagsController < ::ApplicationController
def notifications def notifications
tag = Tag.where_name(params[:tag_id]).first tag = Tag.where_name(params[:tag_id]).first
raise Discourse::NotFound unless tag raise Discourse::NotFound unless tag
level = tag.tag_users.where(user: current_user).first.try(:notification_level) || TagUser.notification_levels[:regular] level =
tag.tag_users.where(user: current_user).first.try(:notification_level) ||
TagUser.notification_levels[:regular]
render json: { tag_notification: { id: tag.name, notification_level: level.to_i } } render json: { tag_notification: { id: tag.name, notification_level: level.to_i } }
end end
@ -318,9 +346,14 @@ class TagsController < ::ApplicationController
guardian.ensure_can_admin_tags! guardian.ensure_can_admin_tags!
value = DiscourseTagging.add_or_create_synonyms_by_name(@tag, params[:synonyms]) value = DiscourseTagging.add_or_create_synonyms_by_name(@tag, params[:synonyms])
if value.is_a?(Array) if value.is_a?(Array)
render json: failed_json.merge( render json:
failed_tags: value.inject({}) { |h, t| h[t.name] = t.errors.full_messages.first; h } failed_json.merge(
) failed_tags:
value.inject({}) do |h, t|
h[t.name] = t.errors.full_messages.first
h
end,
)
else else
render json: success_json render json: success_json
end end
@ -350,24 +383,29 @@ class TagsController < ::ApplicationController
end end
def ensure_visible def ensure_visible
raise Discourse::NotFound if DiscourseTagging.hidden_tag_names(guardian).include?(params[:tag_id]) if DiscourseTagging.hidden_tag_names(guardian).include?(params[:tag_id])
raise Discourse::NotFound
end
end end
def self.tag_counts_json(tags, show_pm_tags: true) def self.tag_counts_json(tags, show_pm_tags: true)
target_tags = Tag.where(id: tags.map(&:target_tag_id).compact.uniq).select(:id, :name) target_tags = Tag.where(id: tags.map(&:target_tag_id).compact.uniq).select(:id, :name)
tags.map do |t| tags
next if t.topic_count == 0 && t.pm_topic_count > 0 && !show_pm_tags .map do |t|
next if t.topic_count == 0 && t.pm_topic_count > 0 && !show_pm_tags
{ {
id: t.name, id: t.name,
text: t.name, text: t.name,
name: t.name, name: t.name,
description: t.description, description: t.description,
count: t.topic_count, count: t.topic_count,
pm_count: show_pm_tags ? t.pm_topic_count : 0, pm_count: show_pm_tags ? t.pm_topic_count : 0,
target_tag: t.target_tag_id ? target_tags.find { |x| x.id == t.target_tag_id }&.name : nil target_tag:
} t.target_tag_id ? target_tags.find { |x| x.id == t.target_tag_id }&.name : nil,
end.compact }
end
.compact
end end
def set_category def set_category
@ -383,7 +421,10 @@ class TagsController < ::ApplicationController
if !@filter_on_category if !@filter_on_category
permalink = Permalink.find_by_url("c/#{params[:category_slug_path_with_id]}") permalink = Permalink.find_by_url("c/#{params[:category_slug_path_with_id]}")
if permalink.present? && permalink.category_id if permalink.present? && permalink.category_id
return redirect_to "#{Discourse::base_path}/tags#{permalink.target_url}/#{params[:tag_id]}", status: :moved_permanently return(
redirect_to "#{Discourse.base_path}/tags#{permalink.target_url}/#{params[:tag_id]}",
status: :moved_permanently
)
end end
end end
@ -394,14 +435,15 @@ class TagsController < ::ApplicationController
end end
def page_params def page_params
route_params = { format: 'json' } route_params = { format: "json" }
if @filter_on_category if @filter_on_category
if request.path_parameters.include?(:category_slug_path_with_id) if request.path_parameters.include?(:category_slug_path_with_id)
slug_path = @filter_on_category.slug_path slug_path = @filter_on_category.slug_path
route_params[:category_slug_path_with_id] = route_params[:category_slug_path_with_id] = (
(slug_path + [@filter_on_category.id.to_s]).join("/") slug_path + [@filter_on_category.id.to_s]
).join("/")
else else
route_params[:category] = @filter_on_category.slug_for_url route_params[:category] = @filter_on_category.slug_for_url
end end
@ -453,28 +495,30 @@ class TagsController < ::ApplicationController
raise Discourse::NotFound raise Discourse::NotFound
end end
url.sub('.json?', '?') url.sub(".json?", "?")
end end
def build_topic_list_options def build_topic_list_options
options = super.merge( options =
page: params[:page], super.merge(
topic_ids: param_to_integer_list(:topic_ids), page: params[:page],
category: @filter_on_category ? @filter_on_category.id : params[:category], topic_ids: param_to_integer_list(:topic_ids),
order: params[:order], category: @filter_on_category ? @filter_on_category.id : params[:category],
ascending: params[:ascending], order: params[:order],
min_posts: params[:min_posts], ascending: params[:ascending],
max_posts: params[:max_posts], min_posts: params[:min_posts],
status: params[:status], max_posts: params[:max_posts],
filter: params[:filter], status: params[:status],
state: params[:state], filter: params[:filter],
search: params[:search], state: params[:state],
q: params[:q] search: params[:search],
) q: params[:q],
options[:no_subcategories] = true if params[:no_subcategories] == true || params[:no_subcategories] == 'true' )
options[:no_subcategories] = true if params[:no_subcategories] == true ||
params[:no_subcategories] == "true"
options[:per_page] = params[:per_page].to_i.clamp(1, 30) if params[:per_page].present? options[:per_page] = params[:per_page].to_i.clamp(1, 30) if params[:per_page].present?
if params[:tag_id] == 'none' if params[:tag_id] == "none"
options.delete(:tags) options.delete(:tags)
options[:no_tags] = true options[:no_tags] = true
else else

View File

@ -9,10 +9,10 @@ class ThemeJavascriptsController < ApplicationController
:preload_json, :preload_json,
:redirect_to_login_if_required, :redirect_to_login_if_required,
:verify_authenticity_token, :verify_authenticity_token,
only: [:show, :show_map, :show_tests] only: %i[show show_map show_tests],
) )
before_action :is_asset_path, :no_cookies, :apply_cdn_headers, only: [:show, :show_map, :show_tests] before_action :is_asset_path, :no_cookies, :apply_cdn_headers, only: %i[show show_map show_tests]
def show def show
raise Discourse::NotFound unless last_modified.present? raise Discourse::NotFound unless last_modified.present?
@ -24,7 +24,8 @@ class ThemeJavascriptsController < ApplicationController
write_if_not_cached(cache_file) do write_if_not_cached(cache_file) do
content, has_source_map = query.pluck_first(:content, "source_map IS NOT NULL") content, has_source_map = query.pluck_first(:content, "source_map IS NOT NULL")
if has_source_map if has_source_map
content += "\n//# sourceMappingURL=#{params[:digest]}.map?__ws=#{Discourse.current_hostname}\n" content +=
"\n//# sourceMappingURL=#{params[:digest]}.map?__ws=#{Discourse.current_hostname}\n"
end end
content content
end end
@ -39,9 +40,7 @@ class ThemeJavascriptsController < ApplicationController
# Security: safe due to route constraint # Security: safe due to route constraint
cache_file = "#{DISK_CACHE_PATH}/#{params[:digest]}.map" cache_file = "#{DISK_CACHE_PATH}/#{params[:digest]}.map"
write_if_not_cached(cache_file) do write_if_not_cached(cache_file) { query.pluck_first(:source_map) }
query.pluck_first(:source_map)
end
serve_file(cache_file) serve_file(cache_file)
end end
@ -59,9 +58,7 @@ class ThemeJavascriptsController < ApplicationController
@cache_file = "#{TESTS_DISK_CACHE_PATH}/#{digest}.js" @cache_file = "#{TESTS_DISK_CACHE_PATH}/#{digest}.js"
return render body: nil, status: 304 if not_modified? return render body: nil, status: 304 if not_modified?
write_if_not_cached(@cache_file) do write_if_not_cached(@cache_file) { content }
content
end
serve_file @cache_file serve_file @cache_file
end end
@ -73,13 +70,14 @@ class ThemeJavascriptsController < ApplicationController
end end
def last_modified def last_modified
@last_modified ||= begin @last_modified ||=
if params[:action].to_s == "show_tests" begin
File.exist?(@cache_file) ? File.ctime(@cache_file) : nil if params[:action].to_s == "show_tests"
else File.exist?(@cache_file) ? File.ctime(@cache_file) : nil
query.pluck_first(:updated_at) else
query.pluck_first(:updated_at)
end
end end
end
end end
def not_modified? def not_modified?
@ -95,10 +93,10 @@ class ThemeJavascriptsController < ApplicationController
def set_cache_control_headers def set_cache_control_headers
if Rails.env.development? if Rails.env.development?
response.headers['Last-Modified'] = Time.zone.now.httpdate response.headers["Last-Modified"] = Time.zone.now.httpdate
immutable_for(1.second) immutable_for(1.second)
else else
response.headers['Last-Modified'] = last_modified.httpdate if last_modified response.headers["Last-Modified"] = last_modified.httpdate if last_modified
immutable_for(1.year) immutable_for(1.year)
end end
end end

File diff suppressed because it is too large Load Diff

View File

@ -5,13 +5,18 @@ require "mini_mime"
class UploadsController < ApplicationController class UploadsController < ApplicationController
include ExternalUploadHelpers include ExternalUploadHelpers
requires_login except: [:show, :show_short, :_show_secure_deprecated, :show_secure] requires_login except: %i[show show_short _show_secure_deprecated show_secure]
skip_before_action :preload_json, :check_xhr, :redirect_to_login_if_required, only: [:show, :show_short, :_show_secure_deprecated, :show_secure] skip_before_action :preload_json,
:check_xhr,
:redirect_to_login_if_required,
only: %i[show show_short _show_secure_deprecated show_secure]
protect_from_forgery except: :show protect_from_forgery except: :show
before_action :is_asset_path, :apply_cdn_headers, only: [:show, :show_short, :_show_secure_deprecated, :show_secure] before_action :is_asset_path,
before_action :external_store_check, only: [:_show_secure_deprecated, :show_secure] :apply_cdn_headers,
only: %i[show show_short _show_secure_deprecated show_secure]
before_action :external_store_check, only: %i[_show_secure_deprecated show_secure]
SECURE_REDIRECT_GRACE_SECONDS = 5 SECURE_REDIRECT_GRACE_SECONDS = 5
@ -20,18 +25,21 @@ class UploadsController < ApplicationController
me = current_user me = current_user
params.permit(:type, :upload_type) params.permit(:type, :upload_type)
if params[:type].blank? && params[:upload_type].blank? raise Discourse::InvalidParameters if params[:type].blank? && params[:upload_type].blank?
raise Discourse::InvalidParameters
end
# 50 characters ought to be enough for the upload type # 50 characters ought to be enough for the upload type
type = (params[:upload_type].presence || params[:type].presence).parameterize(separator: "_")[0..50] type =
(params[:upload_type].presence || params[:type].presence).parameterize(separator: "_")[0..50]
if type == "avatar" && !me.admin? && (SiteSetting.discourse_connect_overrides_avatar || !TrustLevelAndStaffAndDisabledSetting.matches?(SiteSetting.allow_uploaded_avatars, me)) if type == "avatar" && !me.admin? &&
(
SiteSetting.discourse_connect_overrides_avatar ||
!TrustLevelAndStaffAndDisabledSetting.matches?(SiteSetting.allow_uploaded_avatars, me)
)
return render json: failed_json, status: 422 return render json: failed_json, status: 422
end end
url = params[:url] url = params[:url]
file = params[:file] || params[:files]&.first file = params[:file] || params[:files]&.first
pasted = params[:pasted] == "true" pasted = params[:pasted] == "true"
for_private_message = params[:for_private_message] == "true" for_private_message = params[:for_private_message] == "true"
for_site_setting = params[:for_site_setting] == "true" for_site_setting = params[:for_site_setting] == "true"
@ -42,17 +50,18 @@ class UploadsController < ApplicationController
# longer term we may change this # longer term we may change this
hijack do hijack do
begin begin
info = UploadsController.create_upload( info =
current_user: me, UploadsController.create_upload(
file: file, current_user: me,
url: url, file: file,
type: type, url: url,
for_private_message: for_private_message, type: type,
for_site_setting: for_site_setting, for_private_message: for_private_message,
pasted: pasted, for_site_setting: for_site_setting,
is_api: is_api, pasted: pasted,
retain_hours: retain_hours is_api: is_api,
) retain_hours: retain_hours,
)
rescue => e rescue => e
render json: failed_json.merge(message: e.message&.split("\n")&.first), status: 422 render json: failed_json.merge(message: e.message&.split("\n")&.first), status: 422
else else
@ -66,13 +75,11 @@ class UploadsController < ApplicationController
uploads = [] uploads = []
if (params[:short_urls] && params[:short_urls].length > 0) if (params[:short_urls] && params[:short_urls].length > 0)
PrettyText::Helpers.lookup_upload_urls(params[:short_urls]).each do |short_url, paths| PrettyText::Helpers
uploads << { .lookup_upload_urls(params[:short_urls])
short_url: short_url, .each do |short_url, paths|
url: paths[:url], uploads << { short_url: short_url, url: paths[:url], short_path: paths[:short_path] }
short_path: paths[:short_path] end
}
end
end end
render json: uploads.to_json render json: uploads.to_json
@ -87,7 +94,9 @@ class UploadsController < ApplicationController
RailsMultisite::ConnectionManagement.with_connection(params[:site]) do |db| RailsMultisite::ConnectionManagement.with_connection(params[:site]) do |db|
return render_404 if SiteSetting.prevent_anons_from_downloading_files && current_user.nil? return render_404 if SiteSetting.prevent_anons_from_downloading_files && current_user.nil?
if upload = Upload.find_by(sha1: params[:sha]) || Upload.find_by(id: params[:id], url: request.env["PATH_INFO"]) if upload =
Upload.find_by(sha1: params[:sha]) ||
Upload.find_by(id: params[:id], url: request.env["PATH_INFO"])
unless Discourse.store.internal? unless Discourse.store.internal?
local_store = FileStore::LocalStore.new local_store = FileStore::LocalStore.new
return render_404 unless local_store.has_been_uploaded?(upload.url) return render_404 unless local_store.has_been_uploaded?(upload.url)
@ -104,21 +113,18 @@ class UploadsController < ApplicationController
# do not serve uploads requested via XHR to prevent XSS # do not serve uploads requested via XHR to prevent XSS
return xhr_not_allowed if request.xhr? return xhr_not_allowed if request.xhr?
if SiteSetting.prevent_anons_from_downloading_files && current_user.nil? return render_404 if SiteSetting.prevent_anons_from_downloading_files && current_user.nil?
return render_404
end
sha1 = Upload.sha1_from_base62_encoded(params[:base62]) sha1 = Upload.sha1_from_base62_encoded(params[:base62])
if upload = Upload.find_by(sha1: sha1) if upload = Upload.find_by(sha1: sha1)
if upload.secure? && SiteSetting.secure_uploads? return handle_secure_upload_request(upload) if upload.secure? && SiteSetting.secure_uploads?
return handle_secure_upload_request(upload)
end
if Discourse.store.internal? if Discourse.store.internal?
send_file_local_upload(upload) send_file_local_upload(upload)
else else
redirect_to Discourse.store.url_for(upload, force_download: force_download?), allow_other_host: true redirect_to Discourse.store.url_for(upload, force_download: force_download?),
allow_other_host: true
end end
else else
render_404 render_404
@ -156,7 +162,8 @@ class UploadsController < ApplicationController
# private, so we don't want to go to the CDN url just yet otherwise we # private, so we don't want to go to the CDN url just yet otherwise we
# will get a 403. if the upload is not secure we assume the ACL is public # will get a 403. if the upload is not secure we assume the ACL is public
signed_secure_url = Discourse.store.signed_url_for_path(path_with_ext) signed_secure_url = Discourse.store.signed_url_for_path(path_with_ext)
redirect_to upload.secure? ? signed_secure_url : Discourse.store.cdn_url(upload.url), allow_other_host: true redirect_to upload.secure? ? signed_secure_url : Discourse.store.cdn_url(upload.url),
allow_other_host: true
end end
def handle_secure_upload_request(upload, path_with_ext = nil) def handle_secure_upload_request(upload, path_with_ext = nil)
@ -167,20 +174,25 @@ class UploadsController < ApplicationController
end end
# defaults to public: false, so only cached by the client browser # defaults to public: false, so only cached by the client browser
cache_seconds = SiteSetting.s3_presigned_get_url_expires_after_seconds - SECURE_REDIRECT_GRACE_SECONDS cache_seconds =
SiteSetting.s3_presigned_get_url_expires_after_seconds - SECURE_REDIRECT_GRACE_SECONDS
expires_in cache_seconds.seconds expires_in cache_seconds.seconds
# url_for figures out the full URL, handling multisite DBs, # url_for figures out the full URL, handling multisite DBs,
# and will return a presigned URL for the upload # and will return a presigned URL for the upload
if path_with_ext.blank? if path_with_ext.blank?
return redirect_to Discourse.store.url_for(upload, force_download: force_download?), allow_other_host: true return(
redirect_to Discourse.store.url_for(upload, force_download: force_download?),
allow_other_host: true
)
end end
redirect_to Discourse.store.signed_url_for_path( redirect_to Discourse.store.signed_url_for_path(
path_with_ext, path_with_ext,
expires_in: SiteSetting.s3_presigned_get_url_expires_after_seconds, expires_in: SiteSetting.s3_presigned_get_url_expires_after_seconds,
force_download: force_download? force_download: force_download?,
), allow_other_host: true ),
allow_other_host: true
end end
def metadata def metadata
@ -189,11 +201,11 @@ class UploadsController < ApplicationController
raise Discourse::NotFound unless upload raise Discourse::NotFound unless upload
render json: { render json: {
original_filename: upload.original_filename, original_filename: upload.original_filename,
width: upload.width, width: upload.width,
height: upload.height, height: upload.height,
human_filesize: upload.human_filesize human_filesize: upload.human_filesize,
} }
end end
protected protected
@ -207,17 +219,18 @@ class UploadsController < ApplicationController
end end
def validate_file_size(file_name:, file_size:) def validate_file_size(file_name:, file_size:)
if file_size.zero? raise ExternalUploadValidationError.new(I18n.t("upload.size_zero_failure")) if file_size.zero?
raise ExternalUploadValidationError.new(I18n.t("upload.size_zero_failure"))
end
if file_size_too_big?(file_name, file_size) if file_size_too_big?(file_name, file_size)
raise ExternalUploadValidationError.new( raise ExternalUploadValidationError.new(
I18n.t( I18n.t(
"upload.attachments.too_large_humanized", "upload.attachments.too_large_humanized",
max_size: ActiveSupport::NumberHelper.number_to_human_size(SiteSetting.max_attachment_size_kb.kilobytes) max_size:
) ActiveSupport::NumberHelper.number_to_human_size(
) SiteSetting.max_attachment_size_kb.kilobytes,
),
),
)
end end
end end
@ -236,25 +249,34 @@ class UploadsController < ApplicationController
serialized ||= (data || {}).as_json serialized ||= (data || {}).as_json
end end
def self.create_upload(current_user:, def self.create_upload(
file:, current_user:,
url:, file:,
type:, url:,
for_private_message:, type:,
for_site_setting:, for_private_message:,
pasted:, for_site_setting:,
is_api:, pasted:,
retain_hours:) is_api:,
retain_hours:
)
if file.nil? if file.nil?
if url.present? && is_api if url.present? && is_api
maximum_upload_size = [SiteSetting.max_image_size_kb, SiteSetting.max_attachment_size_kb].max.kilobytes maximum_upload_size = [
tempfile = FileHelper.download( SiteSetting.max_image_size_kb,
url, SiteSetting.max_attachment_size_kb,
follow_redirect: true, ].max.kilobytes
max_file_size: maximum_upload_size, tempfile =
tmp_file_name: "discourse-upload-#{type}" begin
) rescue nil FileHelper.download(
url,
follow_redirect: true,
max_file_size: maximum_upload_size,
tmp_file_name: "discourse-upload-#{type}",
)
rescue StandardError
nil
end
filename = File.basename(URI.parse(url).path) filename = File.basename(URI.parse(url).path)
end end
else else
@ -288,13 +310,14 @@ class UploadsController < ApplicationController
# as they may be further reduced in size by UploadCreator (at this point # as they may be further reduced in size by UploadCreator (at this point
# they may have already been reduced in size by preprocessors) # they may have already been reduced in size by preprocessors)
def file_size_too_big?(file_name, file_size) def file_size_too_big?(file_name, file_size)
!FileHelper.is_supported_image?(file_name) && file_size >= SiteSetting.max_attachment_size_kb.kilobytes !FileHelper.is_supported_image?(file_name) &&
file_size >= SiteSetting.max_attachment_size_kb.kilobytes
end end
def send_file_local_upload(upload) def send_file_local_upload(upload)
opts = { opts = {
filename: upload.original_filename, filename: upload.original_filename,
content_type: MiniMime.lookup_by_filename(upload.original_filename)&.content_type content_type: MiniMime.lookup_by_filename(upload.original_filename)&.content_type,
} }
if !FileHelper.is_inline_image?(upload.original_filename) if !FileHelper.is_inline_image?(upload.original_filename)
@ -313,7 +336,11 @@ class UploadsController < ApplicationController
begin begin
yield yield
rescue Aws::S3::Errors::ServiceError => err rescue Aws::S3::Errors::ServiceError => err
message = debug_upload_error(err, I18n.t("upload.create_multipart_failure", additional_detail: err.message)) message =
debug_upload_error(
err,
I18n.t("upload.create_multipart_failure", additional_detail: err.message),
)
raise ExternalUploadHelpers::ExternalUploadValidationError.new(message) raise ExternalUploadHelpers::ExternalUploadValidationError.new(message)
end end
end end

View File

@ -4,7 +4,11 @@ class UserActionsController < ApplicationController
def index def index
user_actions_params.require(:username) user_actions_params.require(:username)
user = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts)) user =
fetch_user_from_params(
include_inactive:
current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts),
)
offset = [0, user_actions_params[:offset].to_i].max offset = [0, user_actions_params[:offset].to_i].max
action_types = (user_actions_params[:filter] || "").split(",").map(&:to_i) action_types = (user_actions_params[:filter] || "").split(",").map(&:to_i)
limit = user_actions_params.fetch(:limit, 30).to_i limit = user_actions_params.fetch(:limit, 30).to_i
@ -20,11 +24,11 @@ class UserActionsController < ApplicationController
action_types: action_types, action_types: action_types,
guardian: guardian, guardian: guardian,
ignore_private_messages: params[:filter].blank?, ignore_private_messages: params[:filter].blank?,
acting_username: params[:acting_username] acting_username: params[:acting_username],
} }
stream = UserAction.stream(opts).to_a stream = UserAction.stream(opts).to_a
render_serialized(stream, UserActionSerializer, root: 'user_actions') render_serialized(stream, UserActionSerializer, root: "user_actions")
end end
def show def show

View File

@ -1,17 +1,15 @@
# frozen_string_literal: true # frozen_string_literal: true
class UserApiKeysController < ApplicationController class UserApiKeysController < ApplicationController
layout "no_ember"
layout 'no_ember' requires_login only: %i[create create_otp revoke undo_revoke]
skip_before_action :redirect_to_login_if_required, only: %i[new otp]
requires_login only: [:create, :create_otp, :revoke, :undo_revoke]
skip_before_action :redirect_to_login_if_required, only: [:new, :otp]
skip_before_action :check_xhr, :preload_json skip_before_action :check_xhr, :preload_json
AUTH_API_VERSION ||= 4 AUTH_API_VERSION ||= 4
def new def new
if request.head? if request.head?
head :ok, auth_api_version: AUTH_API_VERSION head :ok, auth_api_version: AUTH_API_VERSION
return return
@ -24,9 +22,9 @@ class UserApiKeysController < ApplicationController
cookies[:destination_url] = request.fullpath cookies[:destination_url] = request.fullpath
if SiteSetting.enable_discourse_connect? if SiteSetting.enable_discourse_connect?
redirect_to path('/session/sso') redirect_to path("/session/sso")
else else
redirect_to path('/login') redirect_to path("/login")
end end
return return
end end
@ -44,13 +42,11 @@ class UserApiKeysController < ApplicationController
@push_url = params[:push_url] @push_url = params[:push_url]
@localized_scopes = params[:scopes].split(",").map { |s| I18n.t("user_api_key.scopes.#{s}") } @localized_scopes = params[:scopes].split(",").map { |s| I18n.t("user_api_key.scopes.#{s}") }
@scopes = params[:scopes] @scopes = params[:scopes]
rescue Discourse::InvalidAccess rescue Discourse::InvalidAccess
@generic_error = true @generic_error = true
end end
def create def create
require_params require_params
if params.key?(:auth_redirect) if params.key?(:auth_redirect)
@ -66,13 +62,14 @@ class UserApiKeysController < ApplicationController
# destroy any old keys we had # destroy any old keys we had
UserApiKey.where(user_id: current_user.id, client_id: params[:client_id]).destroy_all UserApiKey.where(user_id: current_user.id, client_id: params[:client_id]).destroy_all
key = UserApiKey.create!( key =
application_name: @application_name, UserApiKey.create!(
client_id: params[:client_id], application_name: @application_name,
user_id: current_user.id, client_id: params[:client_id],
push_url: params[:push_url], user_id: current_user.id,
scopes: scopes.map { |name| UserApiKeyScope.new(name: name) } push_url: params[:push_url],
) scopes: scopes.map { |name| UserApiKeyScope.new(name: name) },
)
# we keep the payload short so it encrypts easily with public key # we keep the payload short so it encrypts easily with public key
# it is often restricted to 128 chars # it is often restricted to 128 chars
@ -80,7 +77,7 @@ class UserApiKeysController < ApplicationController
key: key.key, key: key.key,
nonce: params[:nonce], nonce: params[:nonce],
push: key.has_push?, push: key.has_push?,
api: AUTH_API_VERSION api: AUTH_API_VERSION,
}.to_json }.to_json
public_key = OpenSSL::PKey::RSA.new(params[:public_key]) public_key = OpenSSL::PKey::RSA.new(params[:public_key])
@ -94,8 +91,10 @@ class UserApiKeysController < ApplicationController
if params[:auth_redirect] if params[:auth_redirect]
uri = URI.parse(params[:auth_redirect]) uri = URI.parse(params[:auth_redirect])
query_attributes = [uri.query, "payload=#{CGI.escape(@payload)}"] query_attributes = [uri.query, "payload=#{CGI.escape(@payload)}"]
query_attributes << "oneTimePassword=#{CGI.escape(otp_payload)}" if scopes.include?("one_time_password") if scopes.include?("one_time_password")
uri.query = query_attributes.compact.join('&') query_attributes << "oneTimePassword=#{CGI.escape(otp_payload)}"
end
uri.query = query_attributes.compact.join("&")
redirect_to(uri.to_s, allow_other_host: true) redirect_to(uri.to_s, allow_other_host: true)
else else
@ -116,9 +115,9 @@ class UserApiKeysController < ApplicationController
cookies[:destination_url] = request.fullpath cookies[:destination_url] = request.fullpath
if SiteSetting.enable_discourse_connect? if SiteSetting.enable_discourse_connect?
redirect_to path('/session/sso') redirect_to path("/session/sso")
else else
redirect_to path('/login') redirect_to path("/login")
end end
return return
end end
@ -144,7 +143,7 @@ class UserApiKeysController < ApplicationController
def revoke def revoke
revoke_key = find_key if params[:id] revoke_key = find_key if params[:id]
if current_key = request.env['HTTP_USER_API_KEY'] if current_key = request.env["HTTP_USER_API_KEY"]
request_key = UserApiKey.with_key(current_key).first request_key = UserApiKey.with_key(current_key).first
revoke_key ||= request_key revoke_key ||= request_key
end end
@ -168,13 +167,7 @@ class UserApiKeysController < ApplicationController
end end
def require_params def require_params
[ %i[public_key nonce scopes client_id application_name].each { |p| params.require(p) }
:public_key,
:nonce,
:scopes,
:client_id,
:application_name
].each { |p| params.require(p) }
end end
def validate_params def validate_params
@ -186,11 +179,7 @@ class UserApiKeysController < ApplicationController
end end
def require_params_otp def require_params_otp
[ %i[public_key auth_redirect application_name].each { |p| params.require(p) }
:public_key,
:auth_redirect,
:application_name
].each { |p| params.require(p) }
end end
def meets_tl? def meets_tl?
@ -198,7 +187,9 @@ class UserApiKeysController < ApplicationController
end end
def one_time_password(public_key, username) def one_time_password(public_key, username)
raise Discourse::InvalidAccess unless UserApiKey.allowed_scopes.superset?(Set.new(["one_time_password"])) unless UserApiKey.allowed_scopes.superset?(Set.new(["one_time_password"]))
raise Discourse::InvalidAccess
end
otp = SecureRandom.hex otp = SecureRandom.hex
Discourse.redis.setex "otp_#{otp}", 10.minutes, username Discourse.redis.setex "otp_#{otp}", 10.minutes, username

View File

@ -1,10 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
class UserAvatarsController < ApplicationController class UserAvatarsController < ApplicationController
skip_before_action :preload_json,
:redirect_to_login_if_required,
:check_xhr,
:verify_authenticity_token,
only: %i[show show_letter show_proxy_letter]
skip_before_action :preload_json, :redirect_to_login_if_required, :check_xhr, :verify_authenticity_token, only: [:show, :show_letter, :show_proxy_letter] before_action :apply_cdn_headers, only: %i[show show_letter show_proxy_letter]
before_action :apply_cdn_headers, only: [:show, :show_letter, :show_proxy_letter]
def refresh_gravatar def refresh_gravatar
user = User.find_by(username_lower: params[:username].downcase) user = User.find_by(username_lower: params[:username].downcase)
@ -15,17 +18,16 @@ class UserAvatarsController < ApplicationController
user.create_user_avatar(user_id: user.id) unless user.user_avatar user.create_user_avatar(user_id: user.id) unless user.user_avatar
user.user_avatar.update_gravatar! user.user_avatar.update_gravatar!
gravatar = if user.user_avatar.gravatar_upload_id gravatar =
{ if user.user_avatar.gravatar_upload_id
gravatar_upload_id: user.user_avatar.gravatar_upload_id, {
gravatar_avatar_template: User.avatar_template(user.username, user.user_avatar.gravatar_upload_id) gravatar_upload_id: user.user_avatar.gravatar_upload_id,
} gravatar_avatar_template:
else User.avatar_template(user.username, user.user_avatar.gravatar_upload_id),
{ }
gravatar_upload_id: nil, else
gravatar_avatar_template: nil { gravatar_upload_id: nil, gravatar_avatar_template: nil }
} end
end
render json: gravatar render json: gravatar
end end
@ -37,7 +39,7 @@ class UserAvatarsController < ApplicationController
def show_proxy_letter def show_proxy_letter
is_asset_path is_asset_path
if SiteSetting.external_system_avatars_url !~ /^\/letter_avatar_proxy/ if SiteSetting.external_system_avatars_url !~ %r{^/letter_avatar_proxy}
raise Discourse::NotFound raise Discourse::NotFound
end end
@ -48,7 +50,10 @@ class UserAvatarsController < ApplicationController
hijack do hijack do
begin begin
proxy_avatar("https://avatars.discourse-cdn.com/#{params[:version]}/letter/#{params[:letter]}/#{params[:color]}/#{params[:size]}.png", Time.new(1990, 01, 01)) proxy_avatar(
"https://avatars.discourse-cdn.com/#{params[:version]}/letter/#{params[:letter]}/#{params[:color]}/#{params[:size]}.png",
Time.new(1990, 01, 01),
)
rescue OpenURI::HTTPError rescue OpenURI::HTTPError
render_blank render_blank
end end
@ -81,16 +86,13 @@ class UserAvatarsController < ApplicationController
# we need multisite support to keep a single origin pull for CDNs # we need multisite support to keep a single origin pull for CDNs
RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do
hijack do hijack { show_in_site(params[:hostname]) }
show_in_site(params[:hostname])
end
end end
end end
protected protected
def show_in_site(hostname) def show_in_site(hostname)
username = params[:username].to_s username = params[:username].to_s
return render_blank unless user = User.find_by(username_lower: username.downcase) return render_blank unless user = User.find_by(username_lower: username.downcase)
@ -99,9 +101,7 @@ class UserAvatarsController < ApplicationController
version = (version || OptimizedImage::VERSION).to_i version = (version || OptimizedImage::VERSION).to_i
# old versions simply get new avatar # old versions simply get new avatar
if version > OptimizedImage::VERSION return render_blank if version > OptimizedImage::VERSION
return render_blank
end
upload_id = upload_id.to_i upload_id = upload_id.to_i
return render_blank unless upload_id > 0 return render_blank unless upload_id > 0
@ -111,7 +111,13 @@ class UserAvatarsController < ApplicationController
if !Discourse.avatar_sizes.include?(size) && Discourse.store.external? if !Discourse.avatar_sizes.include?(size) && Discourse.store.external?
closest = Discourse.avatar_sizes.to_a.min { |a, b| (size - a).abs <=> (size - b).abs } closest = Discourse.avatar_sizes.to_a.min { |a, b| (size - a).abs <=> (size - b).abs }
avatar_url = UserAvatar.local_avatar_url(hostname, user.encoded_username(lower: true), upload_id, closest) avatar_url =
UserAvatar.local_avatar_url(
hostname,
user.encoded_username(lower: true),
upload_id,
closest,
)
return redirect_to cdn_path(avatar_url), allow_other_host: true return redirect_to cdn_path(avatar_url), allow_other_host: true
end end
@ -119,7 +125,13 @@ class UserAvatarsController < ApplicationController
upload ||= user.uploaded_avatar if user.uploaded_avatar_id == upload_id upload ||= user.uploaded_avatar if user.uploaded_avatar_id == upload_id
if user.uploaded_avatar && !upload if user.uploaded_avatar && !upload
avatar_url = UserAvatar.local_avatar_url(hostname, user.encoded_username(lower: true), user.uploaded_avatar_id, size) avatar_url =
UserAvatar.local_avatar_url(
hostname,
user.encoded_username(lower: true),
user.uploaded_avatar_id,
size,
)
return redirect_to cdn_path(avatar_url), allow_other_host: true return redirect_to cdn_path(avatar_url), allow_other_host: true
elsif upload && optimized = get_optimized_image(upload, size) elsif upload && optimized = get_optimized_image(upload, size)
if optimized.local? if optimized.local?
@ -151,10 +163,7 @@ class UserAvatarsController < ApplicationController
PROXY_PATH = Rails.root + "tmp/avatar_proxy" PROXY_PATH = Rails.root + "tmp/avatar_proxy"
def proxy_avatar(url, last_modified) def proxy_avatar(url, last_modified)
url = (SiteSetting.force_https ? "https:" : "http:") + url if url[0..1] == "//"
if url[0..1] == "//"
url = (SiteSetting.force_https ? "https:" : "http:") + url
end
sha = Digest::SHA1.hexdigest(url) sha = Digest::SHA1.hexdigest(url)
filename = "#{sha}#{File.extname(url)}" filename = "#{sha}#{File.extname(url)}"
@ -162,13 +171,14 @@ class UserAvatarsController < ApplicationController
unless File.exist? path unless File.exist? path
FileUtils.mkdir_p PROXY_PATH FileUtils.mkdir_p PROXY_PATH
tmp = FileHelper.download( tmp =
url, FileHelper.download(
max_file_size: max_file_size, url,
tmp_file_name: filename, max_file_size: max_file_size,
follow_redirect: true, tmp_file_name: filename,
read_timeout: 10 follow_redirect: true,
) read_timeout: 10,
)
return render_blank if tmp.nil? return render_blank if tmp.nil?
@ -206,5 +216,4 @@ class UserAvatarsController < ApplicationController
upload.get_optimized_image(size, size) upload.get_optimized_image(size, size)
# TODO decide if we want to detach here # TODO decide if we want to detach here
end end
end end

View File

@ -6,11 +6,18 @@ class UserBadgesController < ApplicationController
before_action :ensure_badges_enabled before_action :ensure_badges_enabled
def index def index
params.permit [:granted_before, :offset, :username] params.permit %i[granted_before offset username]
badge = fetch_badge_from_params badge = fetch_badge_from_params
user_badges = badge.user_badges.order('granted_at DESC, id DESC').limit(MAX_BADGES) user_badges = badge.user_badges.order("granted_at DESC, id DESC").limit(MAX_BADGES)
user_badges = user_badges.includes(:user, :granted_by, badge: :badge_type, post: :topic, user: [:primary_group, :flair_group]) user_badges =
user_badges.includes(
:user,
:granted_by,
badge: :badge_type,
post: :topic,
user: %i[primary_group flair_group],
)
grant_count = nil grant_count = nil
@ -26,33 +33,40 @@ class UserBadgesController < ApplicationController
user_badges_topic_ids = user_badges.map { |user_badge| user_badge.post&.topic_id }.compact user_badges_topic_ids = user_badges.map { |user_badge| user_badge.post&.topic_id }.compact
user_badges = UserBadges.new(user_badges: user_badges, user_badges =
username: params[:username], UserBadges.new(
grant_count: grant_count) user_badges: user_badges,
username: params[:username],
grant_count: grant_count,
)
render_serialized( render_serialized(
user_badges, user_badges,
UserBadgesSerializer, UserBadgesSerializer,
root: :user_badge_info, root: :user_badge_info,
include_long_description: true, include_long_description: true,
allowed_user_badge_topic_ids: guardian.can_see_topic_ids(topic_ids: user_badges_topic_ids) allowed_user_badge_topic_ids: guardian.can_see_topic_ids(topic_ids: user_badges_topic_ids),
) )
end end
def username def username
params.permit [:grouped] params.permit [:grouped]
user = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts)) user =
fetch_user_from_params(
include_inactive:
current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts),
)
raise Discourse::NotFound unless guardian.can_see_profile?(user) raise Discourse::NotFound unless guardian.can_see_profile?(user)
user_badges = user.user_badges user_badges = user.user_badges
if params[:grouped] user_badges = user_badges.group(:badge_id).select_for_grouping if params[:grouped]
user_badges = user_badges.group(:badge_id).select_for_grouping
end
user_badges = user_badges.includes(badge: [:badge_grouping, :badge_type, :image_upload]) user_badges =
.includes(post: :topic) user_badges
.includes(:granted_by) .includes(badge: %i[badge_grouping badge_type image_upload])
.includes(post: :topic)
.includes(:granted_by)
user_badges_topic_ids = user_badges.map { |user_badge| user_badge.post&.topic_id }.compact user_badges_topic_ids = user_badges.map { |user_badge| user_badge.post&.topic_id }.compact
@ -68,16 +82,17 @@ class UserBadgesController < ApplicationController
params.require(:username) params.require(:username)
user = fetch_user_from_params user = fetch_user_from_params
unless can_assign_badge_to_user?(user) return render json: failed_json, status: 403 unless can_assign_badge_to_user?(user)
return render json: failed_json, status: 403
end
badge = fetch_badge_from_params badge = fetch_badge_from_params
post_id = nil post_id = nil
if params[:reason].present? if params[:reason].present?
unless is_badge_reason_valid? params[:reason] unless is_badge_reason_valid? params[:reason]
return render json: failed_json.merge(message: I18n.t('invalid_grant_badge_reason_link')), status: 400 return(
render json: failed_json.merge(message: I18n.t("invalid_grant_badge_reason_link")),
status: 400
)
end end
if route = Discourse.route_for(params[:reason]) if route = Discourse.route_for(params[:reason])
@ -112,17 +127,17 @@ class UserBadgesController < ApplicationController
user_badge = UserBadge.find(params[:user_badge_id]) user_badge = UserBadge.find(params[:user_badge_id])
user_badges = user_badge.user.user_badges user_badges = user_badge.user.user_badges
unless can_favorite_badge?(user_badge) return render json: failed_json, status: 403 unless can_favorite_badge?(user_badge)
return render json: failed_json, status: 403
end
if !user_badge.is_favorite && user_badges.select(:badge_id).distinct.where(is_favorite: true).count >= SiteSetting.max_favorite_badges if !user_badge.is_favorite &&
user_badges.select(:badge_id).distinct.where(is_favorite: true).count >=
SiteSetting.max_favorite_badges
return render json: failed_json, status: 400 return render json: failed_json, status: 400
end end
UserBadge UserBadge.where(user_id: user_badge.user_id, badge_id: user_badge.badge_id).update_all(
.where(user_id: user_badge.user_id, badge_id: user_badge.badge_id) is_favorite: !user_badge.is_favorite,
.update_all(is_favorite: !user_badge.is_favorite) )
UserBadge.update_featured_ranks!(user_badge.user_id) UserBadge.update_featured_ranks!(user_badge.user_id)
end end
@ -159,6 +174,6 @@ class UserBadgesController < ApplicationController
def is_badge_reason_valid?(reason) def is_badge_reason_valid?(reason)
route = Discourse.route_for(reason) route = Discourse.route_for(reason)
route && (route[:controller] == 'posts' || route[:controller] == 'topics') route && (route[:controller] == "posts" || route[:controller] == "topics")
end end
end end

View File

@ -9,11 +9,11 @@ class Users::AssociateAccountsController < ApplicationController
account_description = authenticator.description_for_auth_hash(auth_hash) account_description = authenticator.description_for_auth_hash(auth_hash)
existing_account_description = authenticator.description_for_user(current_user).presence existing_account_description = authenticator.description_for_user(current_user).presence
render json: { render json: {
token: params[:token], token: params[:token],
provider_name: auth_hash.provider, provider_name: auth_hash.provider,
account_description: account_description, account_description: account_description,
existing_account_description: existing_account_description existing_account_description: existing_account_description,
} }
end end
def connect def connect
@ -33,20 +33,23 @@ class Users::AssociateAccountsController < ApplicationController
private private
def auth_hash def auth_hash
@auth_hash ||= begin @auth_hash ||=
token = params[:token] begin
json = secure_session[self.class.key(token)] token = params[:token]
raise Discourse::NotFound if json.nil? json = secure_session[self.class.key(token)]
raise Discourse::NotFound if json.nil?
OmniAuth::AuthHash.new(JSON.parse(json)) OmniAuth::AuthHash.new(JSON.parse(json))
end end
end end
def authenticator def authenticator
provider_name = auth_hash.provider provider_name = auth_hash.provider
authenticator = Discourse.enabled_authenticators.find { |a| a.name == provider_name } authenticator = Discourse.enabled_authenticators.find { |a| a.name == provider_name }
raise Discourse::InvalidAccess.new(I18n.t('authenticator_not_found')) if authenticator.nil? raise Discourse::InvalidAccess.new(I18n.t("authenticator_not_found")) if authenticator.nil?
raise Discourse::InvalidAccess.new(I18n.t('authenticator_no_connect')) if !authenticator.can_connect_existing_user? if !authenticator.can_connect_existing_user?
raise Discourse::InvalidAccess.new(I18n.t("authenticator_no_connect"))
end
authenticator authenticator
end end

View File

@ -2,10 +2,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Users::OmniauthCallbacksController < ApplicationController class Users::OmniauthCallbacksController < ApplicationController
skip_before_action :redirect_to_login_if_required skip_before_action :redirect_to_login_if_required
layout 'no_ember' layout "no_ember"
# need to be able to call this # need to be able to call this
skip_before_action :check_xhr skip_before_action :check_xhr
@ -40,7 +39,7 @@ class Users::OmniauthCallbacksController < ApplicationController
DiscourseEvent.trigger(:after_auth, authenticator, @auth_result, session, cookies, request) DiscourseEvent.trigger(:after_auth, authenticator, @auth_result, session, cookies, request)
end end
preferred_origin = request.env['omniauth.origin'] preferred_origin = request.env["omniauth.origin"]
if session[:destination_url].present? if session[:destination_url].present?
preferred_origin = session[:destination_url] preferred_origin = session[:destination_url]
@ -53,10 +52,11 @@ class Users::OmniauthCallbacksController < ApplicationController
end end
if preferred_origin.present? if preferred_origin.present?
parsed = begin parsed =
URI.parse(preferred_origin) begin
rescue URI::Error URI.parse(preferred_origin)
end rescue URI::Error
end
if valid_origin?(parsed) if valid_origin?(parsed)
@origin = +"#{parsed.path}" @origin = +"#{parsed.path}"
@ -64,9 +64,7 @@ class Users::OmniauthCallbacksController < ApplicationController
end end
end end
if @origin.blank? @origin = Discourse.base_path("/") if @origin.blank?
@origin = Discourse.base_path("/")
end
@auth_result.destination_url = @origin @auth_result.destination_url = @origin
@auth_result.authenticator_name = authenticator.name @auth_result.authenticator_name = authenticator.name
@ -81,16 +79,13 @@ class Users::OmniauthCallbacksController < ApplicationController
client_hash = @auth_result.to_client_hash client_hash = @auth_result.to_client_hash
if authenticator.can_connect_existing_user? && if authenticator.can_connect_existing_user? &&
(SiteSetting.enable_local_logins || Discourse.enabled_authenticators.count > 1) (SiteSetting.enable_local_logins || Discourse.enabled_authenticators.count > 1)
# There is more than one login method, and users are allowed to manage associations themselves # There is more than one login method, and users are allowed to manage associations themselves
client_hash[:associate_url] = persist_auth_token(auth) client_hash[:associate_url] = persist_auth_token(auth)
end end
cookies['_bypass_cache'] = true cookies["_bypass_cache"] = true
cookies[:authentication_data] = { cookies[:authentication_data] = { value: client_hash.to_json, path: Discourse.base_path("/") }
value: client_hash.to_json,
path: Discourse.base_path("/")
}
redirect_to @origin redirect_to @origin
end end
@ -108,24 +103,24 @@ class Users::OmniauthCallbacksController < ApplicationController
flash[:error] = I18n.t( flash[:error] = I18n.t(
"login.omniauth_error.#{error_key}", "login.omniauth_error.#{error_key}",
default: I18n.t("login.omniauth_error.generic") default: I18n.t("login.omniauth_error.generic"),
).html_safe ).html_safe
render 'failure' render "failure"
end end
def self.find_authenticator(name) def self.find_authenticator(name)
Discourse.enabled_authenticators.each do |authenticator| Discourse.enabled_authenticators.each do |authenticator|
return authenticator if authenticator.name == name return authenticator if authenticator.name == name
end end
raise Discourse::InvalidAccess.new(I18n.t('authenticator_not_found')) raise Discourse::InvalidAccess.new(I18n.t("authenticator_not_found"))
end end
protected protected
def render_auth_result_failure def render_auth_result_failure
flash[:error] = @auth_result.failed_reason.html_safe flash[:error] = @auth_result.failed_reason.html_safe
render 'failure' render "failure"
end end
def complete_response_data def complete_response_data
@ -160,13 +155,16 @@ class Users::OmniauthCallbacksController < ApplicationController
user.update!(password: SecureRandom.hex) user.update!(password: SecureRandom.hex)
# Ensure there is an active email token # Ensure there is an active email token
if !EmailToken.where(email: user.email, confirmed: true).exists? && !user.email_tokens.active.where(email: user.email).exists? if !EmailToken.where(email: user.email, confirmed: true).exists? &&
!user.email_tokens.active.where(email: user.email).exists?
user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:signup]) user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:signup])
end end
user.activate user.activate
end end
user.update!(registration_ip_address: request.remote_ip) if user.registration_ip_address.blank? if user.registration_ip_address.blank?
user.update!(registration_ip_address: request.remote_ip)
end
end end
if ScreenedIpAddress.should_block?(request.remote_ip) if ScreenedIpAddress.should_block?(request.remote_ip)
@ -198,7 +196,9 @@ class Users::OmniauthCallbacksController < ApplicationController
def persist_auth_token(auth) def persist_auth_token(auth)
secret = SecureRandom.hex secret = SecureRandom.hex
secure_session.set "#{Users::AssociateAccountsController.key(secret)}", auth.to_json, expires: 10.minutes secure_session.set "#{Users::AssociateAccountsController.key(secret)}",
auth.to_json,
expires: 10.minutes
"#{Discourse.base_path}/associate/#{secret}" "#{Discourse.base_path}/associate/#{secret}"
end end
end end

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +1,31 @@
# frozen_string_literal: true # frozen_string_literal: true
class UsersEmailController < ApplicationController class UsersEmailController < ApplicationController
requires_login only: %i[index update]
requires_login only: [:index, :update] skip_before_action :check_xhr,
only: %i[
confirm_old_email
show_confirm_old_email
confirm_new_email
show_confirm_new_email
]
skip_before_action :check_xhr, only: [ skip_before_action :redirect_to_login_if_required,
:confirm_old_email, only: %i[
:show_confirm_old_email, confirm_old_email
:confirm_new_email, show_confirm_old_email
:show_confirm_new_email confirm_new_email
] show_confirm_new_email
]
skip_before_action :redirect_to_login_if_required, only: [ before_action :require_login, only: %i[confirm_old_email show_confirm_old_email]
:confirm_old_email,
:show_confirm_old_email,
:confirm_new_email,
:show_confirm_new_email
]
before_action :require_login, only: [
:confirm_old_email,
:show_confirm_old_email
]
def index def index
end end
def create def create
if !SiteSetting.enable_secondary_emails return render json: failed_json, status: 410 if !SiteSetting.enable_secondary_emails
return render json: failed_json, status: 410
end
params.require(:email) params.require(:email)
user = fetch_user_from_params user = fetch_user_from_params
@ -40,9 +36,7 @@ class UsersEmailController < ApplicationController
updater = EmailUpdater.new(guardian: guardian, user: user) updater = EmailUpdater.new(guardian: guardian, user: user)
updater.change_to(params[:email], add: true) updater.change_to(params[:email], add: true)
if updater.errors.present? return render_json_error(updater.errors.full_messages) if updater.errors.present?
return render_json_error(updater.errors.full_messages)
end
render body: nil render body: nil
rescue RateLimiter::LimitExceeded rescue RateLimiter::LimitExceeded
@ -59,9 +53,7 @@ class UsersEmailController < ApplicationController
updater = EmailUpdater.new(guardian: guardian, user: user) updater = EmailUpdater.new(guardian: guardian, user: user)
updater.change_to(params[:email]) updater.change_to(params[:email])
if updater.errors.present? return render_json_error(updater.errors.full_messages) if updater.errors.present?
return render_json_error(updater.errors.full_messages)
end
render body: nil render body: nil
rescue RateLimiter::LimitExceeded rescue RateLimiter::LimitExceeded
@ -119,9 +111,7 @@ class UsersEmailController < ApplicationController
def show_confirm_new_email def show_confirm_new_email
load_change_request(:new) load_change_request(:new)
if params[:done].to_s == "true" @done = true if params[:done].to_s == "true"
@done = true
end
if @change_request&.change_state != EmailChangeRequest.states[:authorizing_new] if @change_request&.change_state != EmailChangeRequest.states[:authorizing_new]
@error = I18n.t("change_email.already_done") @error = I18n.t("change_email.already_done")
@ -135,21 +125,20 @@ class UsersEmailController < ApplicationController
if params[:show_backup].to_s == "true" && @backup_codes_enabled if params[:show_backup].to_s == "true" && @backup_codes_enabled
@show_backup_codes = true @show_backup_codes = true
else else
if @user.totp_enabled? @show_second_factor = true if @user.totp_enabled?
@show_second_factor = true
end
if @user.security_keys_enabled? if @user.security_keys_enabled?
Webauthn.stage_challenge(@user, secure_session) Webauthn.stage_challenge(@user, secure_session)
@show_security_key = params[:show_totp].to_s == "true" ? false : true @show_security_key = params[:show_totp].to_s == "true" ? false : true
@security_key_challenge = Webauthn.challenge(@user, secure_session) @security_key_challenge = Webauthn.challenge(@user, secure_session)
@security_key_allowed_credential_ids = Webauthn.allowed_credentials(@user, secure_session)[:allowed_credential_ids] @security_key_allowed_credential_ids =
Webauthn.allowed_credentials(@user, secure_session)[:allowed_credential_ids]
end end
end end
@to_email = @change_request.new_email @to_email = @change_request.new_email
end end
render layout: 'no_ember' render layout: "no_ember"
end end
def confirm_old_email def confirm_old_email
@ -183,16 +172,14 @@ class UsersEmailController < ApplicationController
@error = I18n.t("change_email.already_done") @error = I18n.t("change_email.already_done")
end end
if params[:done].to_s == "true" @almost_done = true if params[:done].to_s == "true"
@almost_done = true
end
if !@error if !@error
@from_email = @user.email @from_email = @user.email
@to_email = @change_request.new_email @to_email = @change_request.new_email
end end
render layout: 'no_ember' render layout: "no_ember"
end end
private private
@ -204,27 +191,24 @@ class UsersEmailController < ApplicationController
if token if token
if type == :old if type == :old
@change_request = token.user&.email_change_requests.where(old_email_token_id: token.id).first @change_request =
token.user&.email_change_requests.where(old_email_token_id: token.id).first
elsif type == :new elsif type == :new
@change_request = token.user&.email_change_requests.where(new_email_token_id: token.id).first @change_request =
token.user&.email_change_requests.where(new_email_token_id: token.id).first
end end
end end
@user = token&.user @user = token&.user
if (!@user || !@change_request) @error = I18n.t("change_email.already_done") if (!@user || !@change_request)
@error = I18n.t("change_email.already_done")
end
if current_user && current_user.id != @user&.id if current_user && current_user.id != @user&.id
@error = I18n.t 'change_email.wrong_account_error' @error = I18n.t "change_email.wrong_account_error"
end end
end end
def require_login def require_login
if !current_user redirect_to_login if !current_user
redirect_to_login
end
end end
end end

View File

@ -95,8 +95,8 @@ class WebhooksController < ActionController::Base
message_event = event.dig("msys", "message_event") message_event = event.dig("msys", "message_event")
next unless message_event next unless message_event
message_id = message_event.dig("rcpt_meta", "message_id") message_id = message_event.dig("rcpt_meta", "message_id")
to_address = message_event["rcpt_to"] to_address = message_event["rcpt_to"]
bounce_class = message_event["bounce_class"] bounce_class = message_event["bounce_class"]
next unless bounce_class next unless bounce_class
@ -116,7 +116,7 @@ class WebhooksController < ActionController::Base
end end
def aws def aws
raw = request.raw_post raw = request.raw_post
json = JSON.parse(raw) json = JSON.parse(raw)
case json["Type"] case json["Type"]
@ -152,11 +152,14 @@ class WebhooksController < ActionController::Base
return false if (Time.at(timestamp.to_i) - Time.now).abs > 12.hours.to_i return false if (Time.at(timestamp.to_i) - Time.now).abs > 12.hours.to_i
# check the signature # check the signature
signature == OpenSSL::HMAC.hexdigest("SHA256", SiteSetting.mailgun_api_key, "#{timestamp}#{token}") signature ==
OpenSSL::HMAC.hexdigest("SHA256", SiteSetting.mailgun_api_key, "#{timestamp}#{token}")
end end
def handle_mailgun_legacy(params) def handle_mailgun_legacy(params)
return mailgun_failure unless valid_mailgun_signature?(params["token"], params["timestamp"], params["signature"]) unless valid_mailgun_signature?(params["token"], params["timestamp"], params["signature"])
return mailgun_failure
end
event = params["event"] event = params["event"]
message_id = Email::MessageIdService.message_id_clean(params["Message-Id"]) message_id = Email::MessageIdService.message_id_clean(params["Message-Id"])
@ -177,7 +180,13 @@ class WebhooksController < ActionController::Base
def handle_mailgun_new(params) def handle_mailgun_new(params)
signature = params["signature"] signature = params["signature"]
return mailgun_failure unless valid_mailgun_signature?(signature["token"], signature["timestamp"], signature["signature"]) unless valid_mailgun_signature?(
signature["token"],
signature["timestamp"],
signature["signature"],
)
return mailgun_failure
end
data = params["event-data"] data = params["event-data"]
error_code = params.dig("delivery-status", "code") error_code = params.dig("delivery-status", "code")
@ -207,5 +216,4 @@ class WebhooksController < ActionController::Base
Email::Receiver.update_bounce_score(email_log.user.email, bounce_score) Email::Receiver.update_bounce_score(email_log.user.email, bounce_score)
end end
end end

View File

@ -13,9 +13,7 @@ class WizardController < ApplicationController
render_serialized(wizard, WizardSerializer) render_serialized(wizard, WizardSerializer)
end end
format.html do format.html { render body: nil }
render body: nil
end
end end
end end
end end

Some files were not shown because too many files have changed in this diff Show More