Make rubocop happy again.
This commit is contained in:
parent
c6c1ef71c1
commit
ad5082d969
|
@ -88,44 +88,44 @@ class Admin::BadgesController < Admin::AdminController
|
|||
end
|
||||
|
||||
private
|
||||
def find_badge
|
||||
params.require(:id)
|
||||
Badge.find(params[:id])
|
||||
end
|
||||
def find_badge
|
||||
params.require(:id)
|
||||
Badge.find(params[:id])
|
||||
end
|
||||
|
||||
# Options:
|
||||
# :new - reset the badge id to nil before saving
|
||||
def update_badge_from_params(badge, opts = {})
|
||||
errors = []
|
||||
Badge.transaction do
|
||||
allowed = Badge.column_names.map(&:to_sym)
|
||||
allowed -= [:id, :created_at, :updated_at, :grant_count]
|
||||
allowed -= Badge.protected_system_fields if badge.system?
|
||||
allowed -= [:query] unless SiteSetting.enable_badge_sql
|
||||
# Options:
|
||||
# :new - reset the badge id to nil before saving
|
||||
def update_badge_from_params(badge, opts = {})
|
||||
errors = []
|
||||
Badge.transaction do
|
||||
allowed = Badge.column_names.map(&:to_sym)
|
||||
allowed -= [:id, :created_at, :updated_at, :grant_count]
|
||||
allowed -= Badge.protected_system_fields if badge.system?
|
||||
allowed -= [:query] unless SiteSetting.enable_badge_sql
|
||||
|
||||
params.permit(*allowed)
|
||||
params.permit(*allowed)
|
||||
|
||||
allowed.each do |key|
|
||||
badge.send("#{key}=" , params[key]) if params[key]
|
||||
end
|
||||
|
||||
# Badge query contract checks
|
||||
begin
|
||||
if SiteSetting.enable_badge_sql
|
||||
BadgeGranter.contract_checks!(badge.query, target_posts: badge.target_posts, trigger: badge.trigger)
|
||||
end
|
||||
rescue => e
|
||||
errors << e.message
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
badge.id = nil if opts[:new]
|
||||
badge.save!
|
||||
allowed.each do |key|
|
||||
badge.send("#{key}=" , params[key]) if params[key]
|
||||
end
|
||||
|
||||
errors
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
errors.push(*badge.errors.full_messages)
|
||||
errors
|
||||
# Badge query contract checks
|
||||
begin
|
||||
if SiteSetting.enable_badge_sql
|
||||
BadgeGranter.contract_checks!(badge.query, target_posts: badge.target_posts, trigger: badge.trigger)
|
||||
end
|
||||
rescue => e
|
||||
errors << e.message
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
badge.id = nil if opts[:new]
|
||||
badge.save!
|
||||
end
|
||||
|
||||
errors
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
errors.push(*badge.errors.full_messages)
|
||||
errors
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,18 +17,18 @@ class Admin::EmbeddableHostsController < Admin::AdminController
|
|||
|
||||
protected
|
||||
|
||||
def save_host(host)
|
||||
host.host = params[:embeddable_host][:host]
|
||||
host.path_whitelist = params[:embeddable_host][:path_whitelist]
|
||||
host.class_name = params[:embeddable_host][:class_name]
|
||||
host.category_id = params[:embeddable_host][:category_id]
|
||||
host.category_id = SiteSetting.uncategorized_category_id if host.category_id.blank?
|
||||
def save_host(host)
|
||||
host.host = params[:embeddable_host][:host]
|
||||
host.path_whitelist = params[:embeddable_host][:path_whitelist]
|
||||
host.class_name = params[:embeddable_host][:class_name]
|
||||
host.category_id = params[:embeddable_host][:category_id]
|
||||
host.category_id = SiteSetting.uncategorized_category_id if host.category_id.blank?
|
||||
|
||||
if host.save
|
||||
render_serialized(host, EmbeddableHostSerializer, root: 'embeddable_host', rest_serializer: true)
|
||||
else
|
||||
render_json_error(host)
|
||||
end
|
||||
if host.save
|
||||
render_serialized(host, EmbeddableHostSerializer, root: 'embeddable_host', rest_serializer: true)
|
||||
else
|
||||
render_json_error(host)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -27,7 +27,7 @@ class Admin::EmbeddingController < Admin::AdminController
|
|||
|
||||
protected
|
||||
|
||||
def fetch_embedding
|
||||
@embedding = Embedding.find
|
||||
end
|
||||
def fetch_embedding
|
||||
@embedding = Embedding.find
|
||||
end
|
||||
end
|
||||
|
|
|
@ -51,13 +51,13 @@ class Admin::ScreenedIpAddressesController < Admin::AdminController
|
|||
|
||||
private
|
||||
|
||||
def allowed_params
|
||||
params.require(:ip_address)
|
||||
params.permit(:ip_address, :action_name)
|
||||
end
|
||||
def allowed_params
|
||||
params.require(:ip_address)
|
||||
params.permit(:ip_address, :action_name)
|
||||
end
|
||||
|
||||
def fetch_screened_ip_address
|
||||
@screened_ip_address = ScreenedIpAddress.find(params[:id])
|
||||
end
|
||||
def fetch_screened_ip_address
|
||||
@screened_ip_address = ScreenedIpAddress.find(params[:id])
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -75,19 +75,19 @@ class Admin::SiteTextsController < Admin::AdminController
|
|||
|
||||
protected
|
||||
|
||||
def record_for(k, value = nil)
|
||||
if k.ends_with?("_MF")
|
||||
ovr = TranslationOverride.where(translation_key: k, locale: I18n.locale).pluck(:value)
|
||||
value = ovr[0] if ovr.present?
|
||||
end
|
||||
|
||||
value ||= I18n.t(k)
|
||||
{ id: k, value: value }
|
||||
def record_for(k, value = nil)
|
||||
if k.ends_with?("_MF")
|
||||
ovr = TranslationOverride.where(translation_key: k, locale: I18n.locale).pluck(:value)
|
||||
value = ovr[0] if ovr.present?
|
||||
end
|
||||
|
||||
def find_site_text
|
||||
raise Discourse::NotFound unless I18n.exists?(params[:id]) && !self.class.restricted_keys.include?(params[:id])
|
||||
record_for(params[:id])
|
||||
end
|
||||
value ||= I18n.t(k)
|
||||
{ id: k, value: value }
|
||||
end
|
||||
|
||||
def find_site_text
|
||||
raise Discourse::NotFound unless I18n.exists?(params[:id]) && !self.class.restricted_keys.include?(params[:id])
|
||||
record_for(params[:id])
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -223,59 +223,59 @@ class Admin::ThemesController < Admin::AdminController
|
|||
|
||||
private
|
||||
|
||||
def update_default_theme
|
||||
if theme_params.key?(:default)
|
||||
is_default = theme_params[:default].to_s == "true"
|
||||
if @theme.key == SiteSetting.default_theme_key && !is_default
|
||||
Theme.clear_default!
|
||||
elsif is_default
|
||||
@theme.set_default!
|
||||
end
|
||||
def update_default_theme
|
||||
if theme_params.key?(:default)
|
||||
is_default = theme_params[:default].to_s == "true"
|
||||
if @theme.key == SiteSetting.default_theme_key && !is_default
|
||||
Theme.clear_default!
|
||||
elsif is_default
|
||||
@theme.set_default!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def theme_params
|
||||
@theme_params ||=
|
||||
begin
|
||||
# deep munge is a train wreck, work around it for now
|
||||
params[:theme][:child_theme_ids] ||= [] if params[:theme].key?(:child_theme_ids)
|
||||
def theme_params
|
||||
@theme_params ||=
|
||||
begin
|
||||
# deep munge is a train wreck, work around it for now
|
||||
params[:theme][:child_theme_ids] ||= [] if params[:theme].key?(:child_theme_ids)
|
||||
|
||||
params.require(:theme).permit(
|
||||
:name,
|
||||
:color_scheme_id,
|
||||
:default,
|
||||
:user_selectable,
|
||||
settings: {},
|
||||
theme_fields: [:name, :target, :value, :upload_id, :type_id],
|
||||
child_theme_ids: []
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def set_fields
|
||||
return unless fields = theme_params[:theme_fields]
|
||||
|
||||
fields.each do |field|
|
||||
@theme.set_field(
|
||||
target: field[:target],
|
||||
name: field[:name],
|
||||
value: field[:value],
|
||||
type_id: field[:type_id],
|
||||
upload_id: field[:upload_id]
|
||||
params.require(:theme).permit(
|
||||
:name,
|
||||
:color_scheme_id,
|
||||
:default,
|
||||
:user_selectable,
|
||||
settings: {},
|
||||
theme_fields: [:name, :target, :value, :upload_id, :type_id],
|
||||
child_theme_ids: []
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_settings
|
||||
return unless target_settings = theme_params[:settings]
|
||||
def set_fields
|
||||
return unless fields = theme_params[:theme_fields]
|
||||
|
||||
target_settings.each_pair do |setting_name, new_value|
|
||||
@theme.update_setting(setting_name.to_sym, new_value)
|
||||
end
|
||||
fields.each do |field|
|
||||
@theme.set_field(
|
||||
target: field[:target],
|
||||
name: field[:name],
|
||||
value: field[:value],
|
||||
type_id: field[:type_id],
|
||||
upload_id: field[:upload_id]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def log_theme_change(old_record, new_record)
|
||||
StaffActionLogger.new(current_user).log_theme_change(old_record, new_record)
|
||||
def update_settings
|
||||
return unless target_settings = theme_params[:settings]
|
||||
|
||||
target_settings.each_pair do |setting_name, new_value|
|
||||
@theme.update_setting(setting_name.to_sym, new_value)
|
||||
end
|
||||
end
|
||||
|
||||
def log_theme_change(old_record, new_record)
|
||||
StaffActionLogger.new(current_user).log_theme_change(old_record, new_record)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -47,11 +47,11 @@ class Admin::UserFieldsController < Admin::AdminController
|
|||
|
||||
protected
|
||||
|
||||
def update_options(field)
|
||||
options = params[:user_field][:options]
|
||||
if options.present?
|
||||
UserFieldOption.where(user_field_id: field.id).delete_all
|
||||
field.user_field_options_attributes = options.map { |o| { value: o } }.uniq
|
||||
end
|
||||
def update_options(field)
|
||||
options = params[:user_field][:options]
|
||||
if options.present?
|
||||
UserFieldOption.where(user_field_id: field.id).delete_all
|
||||
field.user_field_options_attributes = options.map { |o| { value: o } }.uniq
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -544,34 +544,34 @@ class Admin::UsersController < Admin::AdminController
|
|||
|
||||
private
|
||||
|
||||
def perform_post_action
|
||||
return unless params[:post_id].present? &&
|
||||
params[:post_action].present?
|
||||
def perform_post_action
|
||||
return unless params[:post_id].present? &&
|
||||
params[:post_action].present?
|
||||
|
||||
if post = Post.where(id: params[:post_id]).first
|
||||
case params[:post_action]
|
||||
when 'delete'
|
||||
PostDestroyer.new(current_user, post).destroy
|
||||
when 'edit'
|
||||
revisor = PostRevisor.new(post)
|
||||
if post = Post.where(id: params[:post_id]).first
|
||||
case params[:post_action]
|
||||
when 'delete'
|
||||
PostDestroyer.new(current_user, post).destroy
|
||||
when 'edit'
|
||||
revisor = PostRevisor.new(post)
|
||||
|
||||
# Take what the moderator edited in as gospel
|
||||
revisor.revise!(
|
||||
current_user,
|
||||
{ raw: params[:post_edit] },
|
||||
skip_validations: true,
|
||||
skip_revision: true
|
||||
)
|
||||
end
|
||||
# Take what the moderator edited in as gospel
|
||||
revisor.revise!(
|
||||
current_user,
|
||||
{ raw: params[:post_edit] },
|
||||
skip_validations: true,
|
||||
skip_revision: true
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_user
|
||||
@user = User.find_by(id: params[:user_id])
|
||||
end
|
||||
def fetch_user
|
||||
@user = User.find_by(id: params[:user_id])
|
||||
end
|
||||
|
||||
def refresh_browser(user)
|
||||
MessageBus.publish "/file-change", ["refresh"], user_ids: [user.id]
|
||||
end
|
||||
def refresh_browser(user)
|
||||
MessageBus.publish "/file-change", ["refresh"], user_ids: [user.id]
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -462,255 +462,255 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
private
|
||||
|
||||
def check_readonly_mode
|
||||
@readonly_mode = Discourse.readonly_mode?
|
||||
def check_readonly_mode
|
||||
@readonly_mode = Discourse.readonly_mode?
|
||||
end
|
||||
|
||||
def locale_from_header
|
||||
begin
|
||||
# Rails I18n uses underscores between the locale and the region; the request
|
||||
# headers use hyphens.
|
||||
require 'http_accept_language' unless defined? HttpAcceptLanguage
|
||||
available_locales = I18n.available_locales.map { |locale| locale.to_s.tr('_', '-') }
|
||||
parser = HttpAcceptLanguage::Parser.new(request.env["HTTP_ACCEPT_LANGUAGE"])
|
||||
parser.language_region_compatible_from(available_locales).tr('-', '_')
|
||||
rescue
|
||||
# If Accept-Language headers are not set.
|
||||
I18n.default_locale
|
||||
end
|
||||
end
|
||||
|
||||
def locale_from_header
|
||||
begin
|
||||
# Rails I18n uses underscores between the locale and the region; the request
|
||||
# headers use hyphens.
|
||||
require 'http_accept_language' unless defined? HttpAcceptLanguage
|
||||
available_locales = I18n.available_locales.map { |locale| locale.to_s.tr('_', '-') }
|
||||
parser = HttpAcceptLanguage::Parser.new(request.env["HTTP_ACCEPT_LANGUAGE"])
|
||||
parser.language_region_compatible_from(available_locales).tr('-', '_')
|
||||
rescue
|
||||
# If Accept-Language headers are not set.
|
||||
I18n.default_locale
|
||||
end
|
||||
end
|
||||
def preload_anonymous_data
|
||||
store_preloaded("site", Site.json_for(guardian))
|
||||
store_preloaded("siteSettings", SiteSetting.client_settings_json)
|
||||
store_preloaded("themeSettings", Theme.settings_for_client(@theme_key))
|
||||
store_preloaded("customHTML", custom_html_json)
|
||||
store_preloaded("banner", banner_json)
|
||||
store_preloaded("customEmoji", custom_emoji)
|
||||
store_preloaded("translationOverrides", I18n.client_overrides_json(I18n.locale))
|
||||
end
|
||||
|
||||
def preload_anonymous_data
|
||||
store_preloaded("site", Site.json_for(guardian))
|
||||
store_preloaded("siteSettings", SiteSetting.client_settings_json)
|
||||
store_preloaded("themeSettings", Theme.settings_for_client(@theme_key))
|
||||
store_preloaded("customHTML", custom_html_json)
|
||||
store_preloaded("banner", banner_json)
|
||||
store_preloaded("customEmoji", custom_emoji)
|
||||
store_preloaded("translationOverrides", I18n.client_overrides_json(I18n.locale))
|
||||
end
|
||||
def preload_current_user_data
|
||||
store_preloaded("currentUser", MultiJson.dump(CurrentUserSerializer.new(current_user, scope: guardian, root: false)))
|
||||
report = TopicTrackingState.report(current_user)
|
||||
serializer = ActiveModel::ArraySerializer.new(report, each_serializer: TopicTrackingStateSerializer)
|
||||
store_preloaded("topicTrackingStates", MultiJson.dump(serializer))
|
||||
end
|
||||
|
||||
def preload_current_user_data
|
||||
store_preloaded("currentUser", MultiJson.dump(CurrentUserSerializer.new(current_user, scope: guardian, root: false)))
|
||||
report = TopicTrackingState.report(current_user)
|
||||
serializer = ActiveModel::ArraySerializer.new(report, each_serializer: TopicTrackingStateSerializer)
|
||||
store_preloaded("topicTrackingStates", MultiJson.dump(serializer))
|
||||
end
|
||||
def custom_html_json
|
||||
target = view_context.mobile_view? ? :mobile : :desktop
|
||||
|
||||
def custom_html_json
|
||||
target = view_context.mobile_view? ? :mobile : :desktop
|
||||
|
||||
data =
|
||||
if @theme_key
|
||||
{
|
||||
top: Theme.lookup_field(@theme_key, target, "after_header"),
|
||||
footer: Theme.lookup_field(@theme_key, target, "footer")
|
||||
}
|
||||
else
|
||||
{}
|
||||
end
|
||||
|
||||
if DiscoursePluginRegistry.custom_html
|
||||
data.merge! DiscoursePluginRegistry.custom_html
|
||||
end
|
||||
|
||||
DiscoursePluginRegistry.html_builders.each do |name, _|
|
||||
if name.start_with?("client:")
|
||||
data[name.sub(/^client:/, '')] = DiscoursePluginRegistry.build_html(name, self)
|
||||
end
|
||||
end
|
||||
|
||||
MultiJson.dump(data)
|
||||
end
|
||||
|
||||
def self.banner_json_cache
|
||||
@banner_json_cache ||= DistributedCache.new("banner_json")
|
||||
end
|
||||
|
||||
def banner_json
|
||||
json = ApplicationController.banner_json_cache["json"]
|
||||
|
||||
unless json
|
||||
topic = Topic.where(archetype: Archetype.banner).first
|
||||
banner = topic.present? ? topic.banner : {}
|
||||
ApplicationController.banner_json_cache["json"] = json = MultiJson.dump(banner)
|
||||
end
|
||||
|
||||
json
|
||||
end
|
||||
|
||||
def custom_emoji
|
||||
serializer = ActiveModel::ArraySerializer.new(Emoji.custom, each_serializer: EmojiSerializer)
|
||||
MultiJson.dump(serializer)
|
||||
end
|
||||
|
||||
# Render action for a JSON error.
|
||||
#
|
||||
# obj - a translated string, an ActiveRecord model, or an array of translated strings
|
||||
# opts:
|
||||
# type - a machine-readable description of the error
|
||||
# status - HTTP status code to return
|
||||
# headers - extra headers for the response
|
||||
def render_json_error(obj, opts = {})
|
||||
opts = { status: opts } if opts.is_a?(Integer)
|
||||
opts.fetch(:headers, {}).each { |name, value| headers[name.to_s] = value }
|
||||
|
||||
render json: MultiJson.dump(create_errors_json(obj, opts)), status: opts[:status] || 422
|
||||
end
|
||||
|
||||
def success_json
|
||||
{ success: 'OK' }
|
||||
end
|
||||
|
||||
def failed_json
|
||||
{ failed: 'FAILED' }
|
||||
end
|
||||
|
||||
def json_result(obj, opts = {})
|
||||
if yield(obj)
|
||||
json = success_json
|
||||
|
||||
# If we were given a serializer, add the class to the json that comes back
|
||||
if opts[:serializer].present?
|
||||
json[obj.class.name.underscore] = opts[:serializer].new(obj, scope: guardian).serializable_hash
|
||||
end
|
||||
|
||||
render json: MultiJson.dump(json)
|
||||
data =
|
||||
if @theme_key
|
||||
{
|
||||
top: Theme.lookup_field(@theme_key, target, "after_header"),
|
||||
footer: Theme.lookup_field(@theme_key, target, "footer")
|
||||
}
|
||||
else
|
||||
error_obj = nil
|
||||
if opts[:additional_errors]
|
||||
error_target = opts[:additional_errors].find do |o|
|
||||
target = obj.send(o)
|
||||
target && target.errors.present?
|
||||
end
|
||||
error_obj = obj.send(error_target) if error_target
|
||||
{}
|
||||
end
|
||||
|
||||
if DiscoursePluginRegistry.custom_html
|
||||
data.merge! DiscoursePluginRegistry.custom_html
|
||||
end
|
||||
|
||||
DiscoursePluginRegistry.html_builders.each do |name, _|
|
||||
if name.start_with?("client:")
|
||||
data[name.sub(/^client:/, '')] = DiscoursePluginRegistry.build_html(name, self)
|
||||
end
|
||||
end
|
||||
|
||||
MultiJson.dump(data)
|
||||
end
|
||||
|
||||
def self.banner_json_cache
|
||||
@banner_json_cache ||= DistributedCache.new("banner_json")
|
||||
end
|
||||
|
||||
def banner_json
|
||||
json = ApplicationController.banner_json_cache["json"]
|
||||
|
||||
unless json
|
||||
topic = Topic.where(archetype: Archetype.banner).first
|
||||
banner = topic.present? ? topic.banner : {}
|
||||
ApplicationController.banner_json_cache["json"] = json = MultiJson.dump(banner)
|
||||
end
|
||||
|
||||
json
|
||||
end
|
||||
|
||||
def custom_emoji
|
||||
serializer = ActiveModel::ArraySerializer.new(Emoji.custom, each_serializer: EmojiSerializer)
|
||||
MultiJson.dump(serializer)
|
||||
end
|
||||
|
||||
# Render action for a JSON error.
|
||||
#
|
||||
# obj - a translated string, an ActiveRecord model, or an array of translated strings
|
||||
# opts:
|
||||
# type - a machine-readable description of the error
|
||||
# status - HTTP status code to return
|
||||
# headers - extra headers for the response
|
||||
def render_json_error(obj, opts = {})
|
||||
opts = { status: opts } if opts.is_a?(Integer)
|
||||
opts.fetch(:headers, {}).each { |name, value| headers[name.to_s] = value }
|
||||
|
||||
render json: MultiJson.dump(create_errors_json(obj, opts)), status: opts[:status] || 422
|
||||
end
|
||||
|
||||
def success_json
|
||||
{ success: 'OK' }
|
||||
end
|
||||
|
||||
def failed_json
|
||||
{ failed: 'FAILED' }
|
||||
end
|
||||
|
||||
def json_result(obj, opts = {})
|
||||
if yield(obj)
|
||||
json = success_json
|
||||
|
||||
# If we were given a serializer, add the class to the json that comes back
|
||||
if opts[:serializer].present?
|
||||
json[obj.class.name.underscore] = opts[:serializer].new(obj, scope: guardian).serializable_hash
|
||||
end
|
||||
|
||||
render json: MultiJson.dump(json)
|
||||
else
|
||||
error_obj = nil
|
||||
if opts[:additional_errors]
|
||||
error_target = opts[:additional_errors].find do |o|
|
||||
target = obj.send(o)
|
||||
target && target.errors.present?
|
||||
end
|
||||
render_json_error(error_obj || obj)
|
||||
error_obj = obj.send(error_target) if error_target
|
||||
end
|
||||
render_json_error(error_obj || obj)
|
||||
end
|
||||
end
|
||||
|
||||
def mini_profiler_enabled?
|
||||
defined?(Rack::MiniProfiler) && (guardian.is_developer? || Rails.env.development?)
|
||||
end
|
||||
def mini_profiler_enabled?
|
||||
defined?(Rack::MiniProfiler) && (guardian.is_developer? || Rails.env.development?)
|
||||
end
|
||||
|
||||
def authorize_mini_profiler
|
||||
return unless mini_profiler_enabled?
|
||||
Rack::MiniProfiler.authorize_request
|
||||
end
|
||||
def authorize_mini_profiler
|
||||
return unless mini_profiler_enabled?
|
||||
Rack::MiniProfiler.authorize_request
|
||||
end
|
||||
|
||||
def check_xhr
|
||||
# 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?)
|
||||
raise RenderEmpty.new unless ((request.format && request.format.json?) || request.xhr?)
|
||||
end
|
||||
def check_xhr
|
||||
# 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?)
|
||||
raise RenderEmpty.new unless ((request.format && request.format.json?) || request.xhr?)
|
||||
end
|
||||
|
||||
def self.requires_login(arg = {})
|
||||
@requires_login_arg = arg
|
||||
end
|
||||
def self.requires_login(arg = {})
|
||||
@requires_login_arg = arg
|
||||
end
|
||||
|
||||
def self.requires_login_arg
|
||||
@requires_login_arg
|
||||
end
|
||||
def self.requires_login_arg
|
||||
@requires_login_arg
|
||||
end
|
||||
|
||||
def block_if_requires_login
|
||||
if arg = self.class.requires_login_arg
|
||||
check =
|
||||
if except = arg[:except]
|
||||
!except.include?(action_name.to_sym)
|
||||
elsif only = arg[:only]
|
||||
only.include?(action_name.to_sym)
|
||||
else
|
||||
true
|
||||
end
|
||||
ensure_logged_in if check
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_logged_in
|
||||
raise Discourse::NotLoggedIn.new unless current_user.present?
|
||||
end
|
||||
|
||||
def ensure_staff
|
||||
raise Discourse::InvalidAccess.new unless current_user && current_user.staff?
|
||||
end
|
||||
|
||||
def ensure_admin
|
||||
raise Discourse::InvalidAccess.new unless current_user && current_user.admin?
|
||||
end
|
||||
|
||||
def ensure_wizard_enabled
|
||||
raise Discourse::InvalidAccess.new unless SiteSetting.wizard_enabled?
|
||||
end
|
||||
|
||||
def destination_url
|
||||
request.original_url unless request.original_url =~ /uploads/
|
||||
end
|
||||
|
||||
def redirect_to_login_if_required
|
||||
return if current_user || (request.format.json? && is_api?)
|
||||
|
||||
if SiteSetting.login_required?
|
||||
flash.keep
|
||||
|
||||
if SiteSetting.enable_sso?
|
||||
# save original URL in a session so we can redirect after login
|
||||
session[:destination_url] = destination_url
|
||||
redirect_to path('/session/sso')
|
||||
elsif params[:authComplete].present?
|
||||
redirect_to path("/login?authComplete=true")
|
||||
def block_if_requires_login
|
||||
if arg = self.class.requires_login_arg
|
||||
check =
|
||||
if except = arg[:except]
|
||||
!except.include?(action_name.to_sym)
|
||||
elsif only = arg[:only]
|
||||
only.include?(action_name.to_sym)
|
||||
else
|
||||
# save original URL in a cookie (javascript redirects after login in this case)
|
||||
cookies[:destination_url] = destination_url
|
||||
redirect_to path("/login")
|
||||
true
|
||||
end
|
||||
ensure_logged_in if check
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_logged_in
|
||||
raise Discourse::NotLoggedIn.new unless current_user.present?
|
||||
end
|
||||
|
||||
def ensure_staff
|
||||
raise Discourse::InvalidAccess.new unless current_user && current_user.staff?
|
||||
end
|
||||
|
||||
def ensure_admin
|
||||
raise Discourse::InvalidAccess.new unless current_user && current_user.admin?
|
||||
end
|
||||
|
||||
def ensure_wizard_enabled
|
||||
raise Discourse::InvalidAccess.new unless SiteSetting.wizard_enabled?
|
||||
end
|
||||
|
||||
def destination_url
|
||||
request.original_url unless request.original_url =~ /uploads/
|
||||
end
|
||||
|
||||
def redirect_to_login_if_required
|
||||
return if current_user || (request.format.json? && is_api?)
|
||||
|
||||
if SiteSetting.login_required?
|
||||
flash.keep
|
||||
|
||||
if SiteSetting.enable_sso?
|
||||
# save original URL in a session so we can redirect after login
|
||||
session[:destination_url] = destination_url
|
||||
redirect_to path('/session/sso')
|
||||
elsif params[:authComplete].present?
|
||||
redirect_to path("/login?authComplete=true")
|
||||
else
|
||||
# save original URL in a cookie (javascript redirects after login in this case)
|
||||
cookies[:destination_url] = destination_url
|
||||
redirect_to path("/login")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def block_if_readonly_mode
|
||||
return if request.fullpath.start_with?(path "/admin/backups")
|
||||
raise Discourse::ReadOnly.new if !(request.get? || request.head?) && @readonly_mode
|
||||
def block_if_readonly_mode
|
||||
return if request.fullpath.start_with?(path "/admin/backups")
|
||||
raise Discourse::ReadOnly.new if !(request.get? || request.head?) && @readonly_mode
|
||||
end
|
||||
|
||||
def build_not_found_page(status = 404, layout = false)
|
||||
if SiteSetting.bootstrap_error_pages?
|
||||
preload_json
|
||||
layout = 'application' if layout == 'no_ember'
|
||||
end
|
||||
|
||||
def build_not_found_page(status = 404, layout = false)
|
||||
if SiteSetting.bootstrap_error_pages?
|
||||
preload_json
|
||||
layout = 'application' if layout == 'no_ember'
|
||||
end
|
||||
category_topic_ids = Category.pluck(:topic_id).compact
|
||||
@container_class = "wrap not-found-container"
|
||||
@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)
|
||||
@slug = params[:slug].class == String ? params[:slug] : ''
|
||||
@slug = (params[:id].class == String ? params[:id] : '') if @slug.blank?
|
||||
@slug.tr!('-', ' ')
|
||||
@hide_google = true if SiteSetting.login_required
|
||||
render_to_string status: status, layout: layout, formats: [:html], template: '/exceptions/not_found'
|
||||
end
|
||||
|
||||
category_topic_ids = Category.pluck(:topic_id).compact
|
||||
@container_class = "wrap not-found-container"
|
||||
@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)
|
||||
@slug = params[:slug].class == String ? params[:slug] : ''
|
||||
@slug = (params[:id].class == String ? params[:id] : '') if @slug.blank?
|
||||
@slug.tr!('-', ' ')
|
||||
@hide_google = true if SiteSetting.login_required
|
||||
render_to_string status: status, layout: layout, formats: [:html], template: '/exceptions/not_found'
|
||||
end
|
||||
|
||||
def is_asset_path
|
||||
request.env['DISCOURSE_IS_ASSET_PATH'] = 1
|
||||
end
|
||||
def is_asset_path
|
||||
request.env['DISCOURSE_IS_ASSET_PATH'] = 1
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def render_post_json(post, add_raw = true)
|
||||
post_serializer = PostSerializer.new(post, scope: guardian, root: false)
|
||||
post_serializer.add_raw = add_raw
|
||||
def render_post_json(post, add_raw = true)
|
||||
post_serializer = PostSerializer.new(post, scope: guardian, root: false)
|
||||
post_serializer.add_raw = add_raw
|
||||
|
||||
counts = PostAction.counts_for([post], current_user)
|
||||
if counts && counts = counts[post.id]
|
||||
post_serializer.post_actions = counts
|
||||
end
|
||||
render_json_dump(post_serializer)
|
||||
counts = PostAction.counts_for([post], current_user)
|
||||
if counts && counts = counts[post.id]
|
||||
post_serializer.post_actions = counts
|
||||
end
|
||||
render_json_dump(post_serializer)
|
||||
end
|
||||
|
||||
# returns an array of integers given a param key
|
||||
# returns nil if key is not found
|
||||
def param_to_integer_list(key, delimiter = ',')
|
||||
if params[key]
|
||||
params[key].split(delimiter).map(&:to_i)
|
||||
end
|
||||
# returns an array of integers given a param key
|
||||
# returns nil if key is not found
|
||||
def param_to_integer_list(key, delimiter = ',')
|
||||
if params[key]
|
||||
params[key].split(delimiter).map(&:to_i)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -201,109 +201,109 @@ class CategoriesController < ApplicationController
|
|||
end
|
||||
|
||||
private
|
||||
def categories_and_topics(topics_filter)
|
||||
discourse_expires_in 1.minute
|
||||
def categories_and_topics(topics_filter)
|
||||
discourse_expires_in 1.minute
|
||||
|
||||
category_options = {
|
||||
is_homepage: current_homepage == "categories".freeze,
|
||||
parent_category_id: params[:parent_category_id],
|
||||
include_topics: false
|
||||
}
|
||||
category_options = {
|
||||
is_homepage: current_homepage == "categories".freeze,
|
||||
parent_category_id: params[:parent_category_id],
|
||||
include_topics: false
|
||||
}
|
||||
|
||||
topic_options = {
|
||||
per_page: SiteSetting.categories_topics,
|
||||
no_definitions: true,
|
||||
exclude_category_ids: Category.where(suppress_from_latest: true).pluck(:id)
|
||||
}
|
||||
topic_options = {
|
||||
per_page: SiteSetting.categories_topics,
|
||||
no_definitions: true,
|
||||
exclude_category_ids: Category.where(suppress_from_latest: true).pluck(:id)
|
||||
}
|
||||
|
||||
result = CategoryAndTopicLists.new
|
||||
result.category_list = CategoryList.new(guardian, category_options)
|
||||
result = CategoryAndTopicLists.new
|
||||
result.category_list = CategoryList.new(guardian, category_options)
|
||||
|
||||
if topics_filter == :latest
|
||||
result.topic_list = TopicQuery.new(current_user, topic_options).list_latest
|
||||
elsif topics_filter == :top
|
||||
result.topic_list = TopicQuery.new(nil, topic_options).list_top_for(SiteSetting.top_page_default_timeframe.to_sym)
|
||||
if topics_filter == :latest
|
||||
result.topic_list = TopicQuery.new(current_user, topic_options).list_latest
|
||||
elsif topics_filter == :top
|
||||
result.topic_list = TopicQuery.new(nil, topic_options).list_top_for(SiteSetting.top_page_default_timeframe.to_sym)
|
||||
end
|
||||
|
||||
draft_key = Draft::NEW_TOPIC
|
||||
draft_sequence = DraftSequence.current(current_user, draft_key)
|
||||
draft = Draft.get(current_user, draft_key, draft_sequence) if current_user
|
||||
|
||||
%w{category topic}.each do |type|
|
||||
result.send(:"#{type}_list").draft = draft
|
||||
result.send(:"#{type}_list").draft_key = draft_key
|
||||
result.send(:"#{type}_list").draft_sequence = draft_sequence
|
||||
end
|
||||
|
||||
render_serialized(result, CategoryAndTopicListsSerializer, root: false)
|
||||
end
|
||||
|
||||
def required_param_keys
|
||||
[:name, :color, :text_color]
|
||||
end
|
||||
|
||||
def category_params
|
||||
@category_params ||= begin
|
||||
required_param_keys.each do |key|
|
||||
params.require(key)
|
||||
end
|
||||
|
||||
draft_key = Draft::NEW_TOPIC
|
||||
draft_sequence = DraftSequence.current(current_user, draft_key)
|
||||
draft = Draft.get(current_user, draft_key, draft_sequence) if current_user
|
||||
|
||||
%w{category topic}.each do |type|
|
||||
result.send(:"#{type}_list").draft = draft
|
||||
result.send(:"#{type}_list").draft_key = draft_key
|
||||
result.send(:"#{type}_list").draft_sequence = draft_sequence
|
||||
if p = params[:permissions]
|
||||
p.each do |k, v|
|
||||
p[k] = v.to_i
|
||||
end
|
||||
end
|
||||
|
||||
render_serialized(result, CategoryAndTopicListsSerializer, root: false)
|
||||
end
|
||||
|
||||
def required_param_keys
|
||||
[:name, :color, :text_color]
|
||||
end
|
||||
|
||||
def category_params
|
||||
@category_params ||= begin
|
||||
required_param_keys.each do |key|
|
||||
params.require(key)
|
||||
end
|
||||
|
||||
if p = params[:permissions]
|
||||
p.each do |k, v|
|
||||
p[k] = v.to_i
|
||||
end
|
||||
end
|
||||
|
||||
if SiteSetting.tagging_enabled
|
||||
params[:allowed_tags] ||= []
|
||||
params[:allowed_tag_groups] ||= []
|
||||
end
|
||||
|
||||
params.permit(*required_param_keys,
|
||||
:position,
|
||||
:email_in,
|
||||
:email_in_allow_strangers,
|
||||
:mailinglist_mirror,
|
||||
:suppress_from_latest,
|
||||
:all_topics_wiki,
|
||||
:parent_category_id,
|
||||
:auto_close_hours,
|
||||
:auto_close_based_on_last_post,
|
||||
:uploaded_logo_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,
|
||||
custom_fields: [params[:custom_fields].try(:keys)],
|
||||
permissions: [*p.try(:keys)],
|
||||
allowed_tags: [],
|
||||
allowed_tag_groups: [])
|
||||
if SiteSetting.tagging_enabled
|
||||
params[:allowed_tags] ||= []
|
||||
params[:allowed_tag_groups] ||= []
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_category
|
||||
@category = Category.find_by(slug: params[:id]) || Category.find_by(id: params[:id].to_i)
|
||||
params.permit(*required_param_keys,
|
||||
:position,
|
||||
:email_in,
|
||||
:email_in_allow_strangers,
|
||||
:mailinglist_mirror,
|
||||
:suppress_from_latest,
|
||||
:all_topics_wiki,
|
||||
:parent_category_id,
|
||||
:auto_close_hours,
|
||||
:auto_close_based_on_last_post,
|
||||
:uploaded_logo_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,
|
||||
custom_fields: [params[:custom_fields].try(:keys)],
|
||||
permissions: [*p.try(:keys)],
|
||||
allowed_tags: [],
|
||||
allowed_tag_groups: [])
|
||||
end
|
||||
end
|
||||
|
||||
def initialize_staff_action_logger
|
||||
@staff_action_logger = StaffActionLogger.new(current_user)
|
||||
end
|
||||
def fetch_category
|
||||
@category = Category.find_by(slug: params[:id]) || Category.find_by(id: params[:id].to_i)
|
||||
end
|
||||
|
||||
def include_topics(parent_category = nil)
|
||||
style = SiteSetting.desktop_category_page_style
|
||||
view_context.mobile_view? ||
|
||||
params[:include_topics] ||
|
||||
(parent_category && parent_category.subcategory_list_includes_topics?) ||
|
||||
style == "categories_with_featured_topics".freeze ||
|
||||
style == "categories_with_top_topics".freeze
|
||||
end
|
||||
def initialize_staff_action_logger
|
||||
@staff_action_logger = StaffActionLogger.new(current_user)
|
||||
end
|
||||
|
||||
def include_topics(parent_category = nil)
|
||||
style = SiteSetting.desktop_category_page_style
|
||||
view_context.mobile_view? ||
|
||||
params[:include_topics] ||
|
||||
(parent_category && parent_category.subcategory_list_includes_topics?) ||
|
||||
style == "categories_with_featured_topics".freeze ||
|
||||
style == "categories_with_top_topics".freeze
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,9 +31,9 @@ class ClicksController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def track_params
|
||||
params.require(:url)
|
||||
params.permit(:url, :post_id, :topic_id, :redirect)
|
||||
end
|
||||
def track_params
|
||||
params.require(:url)
|
||||
params.permit(:url, :post_id, :topic_id, :redirect)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -92,28 +92,28 @@ class EmbedController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def get_embeddable_css_class
|
||||
@embeddable_css_class = ""
|
||||
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?
|
||||
end
|
||||
def get_embeddable_css_class
|
||||
@embeddable_css_class = ""
|
||||
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?
|
||||
end
|
||||
|
||||
def ensure_api_request
|
||||
raise Discourse::InvalidAccess.new('api key not set') if !is_api?
|
||||
end
|
||||
def ensure_api_request
|
||||
raise Discourse::InvalidAccess.new('api key not set') if !is_api?
|
||||
end
|
||||
|
||||
def ensure_embeddable
|
||||
if !(Rails.env.development? && current_user&.admin?)
|
||||
referer = request.referer
|
||||
def ensure_embeddable
|
||||
if !(Rails.env.development? && current_user&.admin?)
|
||||
referer = request.referer
|
||||
|
||||
unless referer && EmbeddableHost.url_allowed?(referer)
|
||||
raise Discourse::InvalidAccess.new('invalid referer host')
|
||||
end
|
||||
unless referer && EmbeddableHost.url_allowed?(referer)
|
||||
raise Discourse::InvalidAccess.new('invalid referer host')
|
||||
end
|
||||
|
||||
response.headers['X-Frame-Options'] = "ALLOWALL"
|
||||
rescue URI::InvalidURIError
|
||||
raise Discourse::InvalidAccess.new('invalid referer host')
|
||||
end
|
||||
|
||||
response.headers['X-Frame-Options'] = "ALLOWALL"
|
||||
rescue URI::InvalidURIError
|
||||
raise Discourse::InvalidAccess.new('invalid referer host')
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -14,8 +14,8 @@ class ExceptionsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def hide_google
|
||||
@hide_google = true if SiteSetting.login_required
|
||||
end
|
||||
def hide_google
|
||||
@hide_google = true if SiteSetting.login_required
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -9,10 +9,10 @@ class ExportCsvController < ApplicationController
|
|||
end
|
||||
|
||||
private
|
||||
def export_params
|
||||
@_export_params ||= begin
|
||||
params.require(:entity)
|
||||
params.permit(:entity, args: [:name, :start_date, :end_date, :category_id, :group_id, :trust_level]).to_h
|
||||
end
|
||||
def export_params
|
||||
@_export_params ||= begin
|
||||
params.require(:entity)
|
||||
params.permit(:entity, args: [:name, :start_date, :end_date, :category_id, :group_id, :trust_level]).to_h
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -48,18 +48,18 @@ class FinishInstallationController < ApplicationController
|
|||
|
||||
protected
|
||||
|
||||
def redirect_confirm(email)
|
||||
session[:registered_email] = email
|
||||
redirect_to(finish_installation_confirm_email_path)
|
||||
end
|
||||
def redirect_confirm(email)
|
||||
session[:registered_email] = email
|
||||
redirect_to(finish_installation_confirm_email_path)
|
||||
end
|
||||
|
||||
def find_allowed_emails
|
||||
return [] unless GlobalSetting.respond_to?(:developer_emails) && GlobalSetting.developer_emails.present?
|
||||
GlobalSetting.developer_emails.split(",").map(&:strip)
|
||||
end
|
||||
def find_allowed_emails
|
||||
return [] unless GlobalSetting.respond_to?(:developer_emails) && GlobalSetting.developer_emails.present?
|
||||
GlobalSetting.developer_emails.split(",").map(&:strip)
|
||||
end
|
||||
|
||||
def ensure_no_admins
|
||||
preload_anonymous_data
|
||||
raise Discourse::InvalidAccess.new unless SiteSetting.has_login_hint?
|
||||
end
|
||||
def ensure_no_admins
|
||||
preload_anonymous_data
|
||||
raise Discourse::InvalidAccess.new unless SiteSetting.has_login_hint?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -211,14 +211,14 @@ class InvitesController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def post_process_invite(user)
|
||||
user.enqueue_welcome_message('welcome_invite') if user.send_welcome_message
|
||||
if user.has_password?
|
||||
email_token = user.email_tokens.create(email: user.email)
|
||||
Jobs.enqueue(:critical_user_email, type: :signup, user_id: user.id, email_token: email_token.token)
|
||||
elsif !SiteSetting.enable_sso && SiteSetting.enable_local_logins
|
||||
Jobs.enqueue(:invite_password_instructions_email, username: user.username)
|
||||
end
|
||||
def post_process_invite(user)
|
||||
user.enqueue_welcome_message('welcome_invite') if user.send_welcome_message
|
||||
if user.has_password?
|
||||
email_token = user.email_tokens.create(email: user.email)
|
||||
Jobs.enqueue(:critical_user_email, type: :signup, user_id: user.id, email_token: email_token.token)
|
||||
elsif !SiteSetting.enable_sso && SiteSetting.enable_local_logins
|
||||
Jobs.enqueue(:invite_password_instructions_email, username: user.username)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -84,16 +84,16 @@ class NotificationsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def set_notification
|
||||
@notification = Notification.find(params[:id])
|
||||
end
|
||||
def set_notification
|
||||
@notification = Notification.find(params[:id])
|
||||
end
|
||||
|
||||
def notification_params
|
||||
params.permit(:notification_type, :user_id, :data, :read, :topic_id, :post_number, :post_action_id)
|
||||
end
|
||||
def notification_params
|
||||
params.permit(:notification_type, :user_id, :data, :read, :topic_id, :post_number, :post_action_id)
|
||||
end
|
||||
|
||||
def render_notification
|
||||
render_json_dump(NotificationSerializer.new(@notification, scope: guardian, root: false))
|
||||
end
|
||||
def render_notification
|
||||
render_json_dump(NotificationSerializer.new(@notification, scope: guardian, root: false))
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -65,32 +65,32 @@ class PostActionsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def fetch_post_from_params
|
||||
params.require(:id)
|
||||
def fetch_post_from_params
|
||||
params.require(:id)
|
||||
|
||||
flag_topic = params[:flag_topic]
|
||||
flag_topic = flag_topic && (flag_topic == true || flag_topic == "true")
|
||||
flag_topic = params[:flag_topic]
|
||||
flag_topic = flag_topic && (flag_topic == true || flag_topic == "true")
|
||||
|
||||
post_id = if flag_topic
|
||||
begin
|
||||
Topic.find(params[:id]).posts.first.id
|
||||
rescue
|
||||
raise Discourse::NotFound
|
||||
end
|
||||
else
|
||||
params[:id]
|
||||
post_id = if flag_topic
|
||||
begin
|
||||
Topic.find(params[:id]).posts.first.id
|
||||
rescue
|
||||
raise Discourse::NotFound
|
||||
end
|
||||
|
||||
finder = Post.where(id: post_id)
|
||||
|
||||
# Include deleted posts if the user is a staff
|
||||
finder = finder.with_deleted if guardian.is_staff?
|
||||
|
||||
@post = finder.first
|
||||
else
|
||||
params[:id]
|
||||
end
|
||||
|
||||
def fetch_post_action_type_id_from_params
|
||||
params.require(:post_action_type_id)
|
||||
@post_action_type_id = params[:post_action_type_id].to_i
|
||||
end
|
||||
finder = Post.where(id: post_id)
|
||||
|
||||
# Include deleted posts if the user is a staff
|
||||
finder = finder.with_deleted if guardian.is_staff?
|
||||
|
||||
@post = finder.first
|
||||
end
|
||||
|
||||
def fetch_post_action_type_id_from_params
|
||||
params.require(:post_action_type_id)
|
||||
@post_action_type_id = params[:post_action_type_id].to_i
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,18 +52,18 @@ class QueuedPostsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def user_deletion_opts
|
||||
base = {
|
||||
context: I18n.t('queue.delete_reason', performed_by: current_user.username),
|
||||
delete_posts: true,
|
||||
delete_as_spammer: true
|
||||
}
|
||||
def user_deletion_opts
|
||||
base = {
|
||||
context: I18n.t('queue.delete_reason', performed_by: current_user.username),
|
||||
delete_posts: true,
|
||||
delete_as_spammer: true
|
||||
}
|
||||
|
||||
if Rails.env.production? && ENV["Staging"].nil?
|
||||
base.merge!(block_email: true, block_ip: true)
|
||||
end
|
||||
|
||||
base
|
||||
if Rails.env.production? && ENV["Staging"].nil?
|
||||
base.merge!(block_email: true, block_ip: true)
|
||||
end
|
||||
|
||||
base
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -98,11 +98,11 @@ class StylesheetsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def read_file(location)
|
||||
begin
|
||||
File.read(location)
|
||||
rescue Errno::ENOENT
|
||||
end
|
||||
def read_file(location)
|
||||
begin
|
||||
File.read(location)
|
||||
rescue Errno::ENOENT
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -64,28 +64,28 @@ class TagGroupsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def fetch_tag_group
|
||||
@tag_group = TagGroup.find(params[:id])
|
||||
end
|
||||
def fetch_tag_group
|
||||
@tag_group = TagGroup.find(params[:id])
|
||||
end
|
||||
|
||||
def tag_groups_params
|
||||
if permissions = params[:permissions]
|
||||
permissions.each do |k, v|
|
||||
permissions[k] = v.to_i
|
||||
end
|
||||
def tag_groups_params
|
||||
if permissions = params[:permissions]
|
||||
permissions.each do |k, v|
|
||||
permissions[k] = v.to_i
|
||||
end
|
||||
|
||||
result = params.permit(
|
||||
:id,
|
||||
:name,
|
||||
:one_per_topic,
|
||||
tag_names: [],
|
||||
parent_tag_name: [],
|
||||
permissions: permissions&.keys,
|
||||
)
|
||||
result[:tag_names] ||= []
|
||||
result[:parent_tag_name] ||= []
|
||||
result[:one_per_topic] = (params[:one_per_topic] == "true")
|
||||
result
|
||||
end
|
||||
|
||||
result = params.permit(
|
||||
:id,
|
||||
:name,
|
||||
:one_per_topic,
|
||||
tag_names: [],
|
||||
parent_tag_name: [],
|
||||
permissions: permissions&.keys,
|
||||
)
|
||||
result[:tag_names] ||= []
|
||||
result[:parent_tag_name] ||= []
|
||||
result[:one_per_topic] = (params[:one_per_topic] == "true")
|
||||
result
|
||||
end
|
||||
end
|
||||
|
|
|
@ -207,130 +207,130 @@ class TagsController < ::ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def ensure_tags_enabled
|
||||
raise Discourse::NotFound unless SiteSetting.tagging_enabled?
|
||||
end
|
||||
def ensure_tags_enabled
|
||||
raise Discourse::NotFound unless SiteSetting.tagging_enabled?
|
||||
end
|
||||
|
||||
def self.tag_counts_json(tags)
|
||||
tags.map { |t| { id: t.name, text: t.name, count: t.topic_count, pm_count: t.pm_topic_count } }
|
||||
end
|
||||
def self.tag_counts_json(tags)
|
||||
tags.map { |t| { id: t.name, text: t.name, count: t.topic_count, pm_count: t.pm_topic_count } }
|
||||
end
|
||||
|
||||
def set_category_from_params
|
||||
slug_or_id = params[:category]
|
||||
return true if slug_or_id.nil?
|
||||
def set_category_from_params
|
||||
slug_or_id = params[:category]
|
||||
return true if slug_or_id.nil?
|
||||
|
||||
if slug_or_id == 'none' && params[:parent_category]
|
||||
@filter_on_category = Category.query_category(params[:parent_category], nil)
|
||||
params[:no_subcategories] = 'true'
|
||||
else
|
||||
parent_slug_or_id = params[:parent_category]
|
||||
if slug_or_id == 'none' && params[:parent_category]
|
||||
@filter_on_category = Category.query_category(params[:parent_category], nil)
|
||||
params[:no_subcategories] = 'true'
|
||||
else
|
||||
parent_slug_or_id = params[:parent_category]
|
||||
|
||||
parent_category_id = nil
|
||||
if parent_slug_or_id.present?
|
||||
parent_category_id = Category.query_parent_category(parent_slug_or_id)
|
||||
category_redirect_or_not_found && (return) if parent_category_id.blank?
|
||||
end
|
||||
|
||||
@filter_on_category = Category.query_category(slug_or_id, parent_category_id)
|
||||
parent_category_id = nil
|
||||
if parent_slug_or_id.present?
|
||||
parent_category_id = Category.query_parent_category(parent_slug_or_id)
|
||||
category_redirect_or_not_found && (return) if parent_category_id.blank?
|
||||
end
|
||||
|
||||
category_redirect_or_not_found && (return) if !@filter_on_category
|
||||
|
||||
guardian.ensure_can_see!(@filter_on_category)
|
||||
@filter_on_category = Category.query_category(slug_or_id, parent_category_id)
|
||||
end
|
||||
|
||||
# TODO: this is duplication of ListController
|
||||
def page_params(opts = nil)
|
||||
opts ||= {}
|
||||
route_params = { format: 'json' }
|
||||
route_params[:category] = @filter_on_category.slug_for_url if @filter_on_category
|
||||
route_params[:parent_category] = @filter_on_category.parent_category.slug_for_url if @filter_on_category && @filter_on_category.parent_category
|
||||
route_params[:order] = opts[:order] if opts[:order].present?
|
||||
route_params[:ascending] = opts[:ascending] if opts[:ascending].present?
|
||||
route_params
|
||||
end
|
||||
category_redirect_or_not_found && (return) if !@filter_on_category
|
||||
|
||||
def next_page_params(opts = nil)
|
||||
page_params(opts).merge(page: params[:page].to_i + 1)
|
||||
end
|
||||
guardian.ensure_can_see!(@filter_on_category)
|
||||
end
|
||||
|
||||
def prev_page_params(opts = nil)
|
||||
pg = params[:page].to_i
|
||||
if pg > 1
|
||||
page_params(opts).merge(page: pg - 1)
|
||||
else
|
||||
page_params(opts).merge(page: nil)
|
||||
# TODO: this is duplication of ListController
|
||||
def page_params(opts = nil)
|
||||
opts ||= {}
|
||||
route_params = { format: 'json' }
|
||||
route_params[:category] = @filter_on_category.slug_for_url if @filter_on_category
|
||||
route_params[:parent_category] = @filter_on_category.parent_category.slug_for_url if @filter_on_category && @filter_on_category.parent_category
|
||||
route_params[:order] = opts[:order] if opts[:order].present?
|
||||
route_params[:ascending] = opts[:ascending] if opts[:ascending].present?
|
||||
route_params
|
||||
end
|
||||
|
||||
def next_page_params(opts = nil)
|
||||
page_params(opts).merge(page: params[:page].to_i + 1)
|
||||
end
|
||||
|
||||
def prev_page_params(opts = nil)
|
||||
pg = params[:page].to_i
|
||||
if pg > 1
|
||||
page_params(opts).merge(page: pg - 1)
|
||||
else
|
||||
page_params(opts).merge(page: nil)
|
||||
end
|
||||
end
|
||||
|
||||
def url_method(opts = {})
|
||||
if opts[:parent_category] && opts[:category]
|
||||
"tag_parent_category_category_#{action_name}_path"
|
||||
elsif opts[:category]
|
||||
"tag_category_#{action_name}_path"
|
||||
else
|
||||
"tag_#{action_name}_path"
|
||||
end
|
||||
end
|
||||
|
||||
def construct_url_with(action, opts)
|
||||
method = url_method(opts)
|
||||
|
||||
begin
|
||||
url = if action == :prev
|
||||
public_send(method, opts.merge(prev_page_params(opts)))
|
||||
else # :next
|
||||
public_send(method, opts.merge(next_page_params(opts)))
|
||||
end
|
||||
rescue ActionController::UrlGenerationError
|
||||
raise Discourse::NotFound
|
||||
end
|
||||
url.sub('.json?', '?')
|
||||
end
|
||||
|
||||
def build_topic_list_options
|
||||
options = {
|
||||
page: params[:page],
|
||||
topic_ids: param_to_integer_list(:topic_ids),
|
||||
exclude_category_ids: params[:exclude_category_ids],
|
||||
category: @filter_on_category ? @filter_on_category.id : params[:category],
|
||||
order: params[:order],
|
||||
ascending: params[:ascending],
|
||||
min_posts: params[:min_posts],
|
||||
max_posts: params[:max_posts],
|
||||
status: params[:status],
|
||||
filter: params[:filter],
|
||||
state: params[:state],
|
||||
search: params[:search],
|
||||
q: params[:q]
|
||||
}
|
||||
options[:no_subcategories] = true if params[:no_subcategories] == 'true'
|
||||
options[:slow_platform] = true if slow_platform?
|
||||
|
||||
if params[:tag_id] == 'none'
|
||||
options[:no_tags] = true
|
||||
else
|
||||
options[:tags] = tag_params
|
||||
options[:match_all_tags] = true
|
||||
end
|
||||
|
||||
def url_method(opts = {})
|
||||
if opts[:parent_category] && opts[:category]
|
||||
"tag_parent_category_category_#{action_name}_path"
|
||||
elsif opts[:category]
|
||||
"tag_category_#{action_name}_path"
|
||||
else
|
||||
"tag_#{action_name}_path"
|
||||
end
|
||||
options
|
||||
end
|
||||
|
||||
def category_redirect_or_not_found
|
||||
# automatic redirects for renamed categories
|
||||
url = params[:parent_category] ? "c/#{params[:parent_category]}/#{params[:category]}" : "c/#{params[:category]}"
|
||||
permalink = Permalink.find_by_url(url)
|
||||
|
||||
if permalink.present? && permalink.category_id
|
||||
redirect_to "#{Discourse::base_uri}/tags#{permalink.target_url}/#{params[:tag_id]}", status: :moved_permanently
|
||||
else
|
||||
# redirect to 404
|
||||
raise Discourse::NotFound
|
||||
end
|
||||
end
|
||||
|
||||
def construct_url_with(action, opts)
|
||||
method = url_method(opts)
|
||||
|
||||
begin
|
||||
url = if action == :prev
|
||||
public_send(method, opts.merge(prev_page_params(opts)))
|
||||
else # :next
|
||||
public_send(method, opts.merge(next_page_params(opts)))
|
||||
end
|
||||
rescue ActionController::UrlGenerationError
|
||||
raise Discourse::NotFound
|
||||
end
|
||||
url.sub('.json?', '?')
|
||||
end
|
||||
|
||||
def build_topic_list_options
|
||||
options = {
|
||||
page: params[:page],
|
||||
topic_ids: param_to_integer_list(:topic_ids),
|
||||
exclude_category_ids: params[:exclude_category_ids],
|
||||
category: @filter_on_category ? @filter_on_category.id : params[:category],
|
||||
order: params[:order],
|
||||
ascending: params[:ascending],
|
||||
min_posts: params[:min_posts],
|
||||
max_posts: params[:max_posts],
|
||||
status: params[:status],
|
||||
filter: params[:filter],
|
||||
state: params[:state],
|
||||
search: params[:search],
|
||||
q: params[:q]
|
||||
}
|
||||
options[:no_subcategories] = true if params[:no_subcategories] == 'true'
|
||||
options[:slow_platform] = true if slow_platform?
|
||||
|
||||
if params[:tag_id] == 'none'
|
||||
options[:no_tags] = true
|
||||
else
|
||||
options[:tags] = tag_params
|
||||
options[:match_all_tags] = true
|
||||
end
|
||||
|
||||
options
|
||||
end
|
||||
|
||||
def category_redirect_or_not_found
|
||||
# automatic redirects for renamed categories
|
||||
url = params[:parent_category] ? "c/#{params[:parent_category]}/#{params[:category]}" : "c/#{params[:category]}"
|
||||
permalink = Permalink.find_by_url(url)
|
||||
|
||||
if permalink.present? && permalink.category_id
|
||||
redirect_to "#{Discourse::base_uri}/tags#{permalink.target_url}/#{params[:tag_id]}", status: :moved_permanently
|
||||
else
|
||||
# redirect to 404
|
||||
raise Discourse::NotFound
|
||||
end
|
||||
end
|
||||
|
||||
def tag_params
|
||||
[@tag_id].concat(Array(@additional_tags))
|
||||
end
|
||||
def tag_params
|
||||
[@tag_id].concat(Array(@additional_tags))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -92,28 +92,28 @@ class UserBadgesController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
# Get the badge from either the badge name or id specified in the params.
|
||||
def fetch_badge_from_params
|
||||
badge = nil
|
||||
# Get the badge from either the badge name or id specified in the params.
|
||||
def fetch_badge_from_params
|
||||
badge = nil
|
||||
|
||||
params.permit(:badge_name)
|
||||
if params[:badge_name].nil?
|
||||
params.require(:badge_id)
|
||||
badge = Badge.find_by(id: params[:badge_id], enabled: true)
|
||||
else
|
||||
badge = Badge.find_by(name: params[:badge_name], enabled: true)
|
||||
end
|
||||
raise Discourse::NotFound if badge.blank?
|
||||
|
||||
badge
|
||||
params.permit(:badge_name)
|
||||
if params[:badge_name].nil?
|
||||
params.require(:badge_id)
|
||||
badge = Badge.find_by(id: params[:badge_id], enabled: true)
|
||||
else
|
||||
badge = Badge.find_by(name: params[:badge_name], enabled: true)
|
||||
end
|
||||
raise Discourse::NotFound if badge.blank?
|
||||
|
||||
def can_assign_badge_to_user?(user)
|
||||
master_api_call = current_user.nil? && is_api?
|
||||
master_api_call || guardian.can_grant_badges?(user)
|
||||
end
|
||||
badge
|
||||
end
|
||||
|
||||
def ensure_badges_enabled
|
||||
raise Discourse::NotFound unless SiteSetting.enable_badges?
|
||||
end
|
||||
def can_assign_badge_to_user?(user)
|
||||
master_api_call = current_user.nil? && is_api?
|
||||
master_api_call || guardian.can_grant_badges?(user)
|
||||
end
|
||||
|
||||
def ensure_badges_enabled
|
||||
raise Discourse::NotFound unless SiteSetting.enable_badges?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -994,104 +994,104 @@ class UsersController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def honeypot_value
|
||||
Digest::SHA1::hexdigest("#{Discourse.current_hostname}:#{GlobalSetting.safe_secret_key_base}")[0, 15]
|
||||
def honeypot_value
|
||||
Digest::SHA1::hexdigest("#{Discourse.current_hostname}:#{GlobalSetting.safe_secret_key_base}")[0, 15]
|
||||
end
|
||||
|
||||
def challenge_value
|
||||
challenge = $redis.get('SECRET_CHALLENGE')
|
||||
unless challenge && challenge.length == 16 * 2
|
||||
challenge = SecureRandom.hex(16)
|
||||
$redis.set('SECRET_CHALLENGE', challenge)
|
||||
end
|
||||
|
||||
def challenge_value
|
||||
challenge = $redis.get('SECRET_CHALLENGE')
|
||||
unless challenge && challenge.length == 16 * 2
|
||||
challenge = SecureRandom.hex(16)
|
||||
$redis.set('SECRET_CHALLENGE', challenge)
|
||||
end
|
||||
challenge
|
||||
end
|
||||
|
||||
challenge
|
||||
def respond_to_suspicious_request
|
||||
if suspicious?(params)
|
||||
render json: {
|
||||
success: true,
|
||||
active: false,
|
||||
message: I18n.t("login.activate_email", email: params[:email])
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def suspicious?(params)
|
||||
return false if current_user && is_api? && current_user.admin?
|
||||
honeypot_or_challenge_fails?(params) || SiteSetting.invite_only?
|
||||
end
|
||||
|
||||
def honeypot_or_challenge_fails?(params)
|
||||
return false if is_api?
|
||||
params[:password_confirmation] != honeypot_value ||
|
||||
params[:challenge] != challenge_value.try(:reverse)
|
||||
end
|
||||
|
||||
def user_params
|
||||
permitted = [
|
||||
:name,
|
||||
:email,
|
||||
:password,
|
||||
:username,
|
||||
:title,
|
||||
:date_of_birth,
|
||||
:muted_usernames,
|
||||
:theme_key,
|
||||
:locale,
|
||||
:bio_raw,
|
||||
:location,
|
||||
:website,
|
||||
:dismissed_banner_key,
|
||||
:profile_background,
|
||||
:card_background
|
||||
]
|
||||
|
||||
permitted.concat UserUpdater::OPTION_ATTR
|
||||
permitted.concat UserUpdater::CATEGORY_IDS.keys.map { |k| { k => [] } }
|
||||
permitted.concat UserUpdater::TAG_NAMES.keys
|
||||
|
||||
result = params
|
||||
.permit(permitted)
|
||||
.reverse_merge(
|
||||
ip_address: request.remote_ip,
|
||||
registration_ip_address: request.remote_ip,
|
||||
locale: user_locale
|
||||
)
|
||||
|
||||
if !UsernameCheckerService.is_developer?(result['email']) &&
|
||||
is_api? &&
|
||||
current_user.present? &&
|
||||
current_user.admin?
|
||||
|
||||
result.merge!(params.permit(:active, :staged, :approved))
|
||||
end
|
||||
|
||||
def respond_to_suspicious_request
|
||||
if suspicious?(params)
|
||||
render json: {
|
||||
success: true,
|
||||
active: false,
|
||||
message: I18n.t("login.activate_email", email: params[:email])
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def suspicious?(params)
|
||||
return false if current_user && is_api? && current_user.admin?
|
||||
honeypot_or_challenge_fails?(params) || SiteSetting.invite_only?
|
||||
end
|
||||
|
||||
def honeypot_or_challenge_fails?(params)
|
||||
return false if is_api?
|
||||
params[:password_confirmation] != honeypot_value ||
|
||||
params[:challenge] != challenge_value.try(:reverse)
|
||||
end
|
||||
|
||||
def user_params
|
||||
permitted = [
|
||||
:name,
|
||||
:email,
|
||||
:password,
|
||||
:username,
|
||||
:title,
|
||||
:date_of_birth,
|
||||
:muted_usernames,
|
||||
:theme_key,
|
||||
:locale,
|
||||
:bio_raw,
|
||||
:location,
|
||||
:website,
|
||||
:dismissed_banner_key,
|
||||
:profile_background,
|
||||
:card_background
|
||||
]
|
||||
|
||||
permitted.concat UserUpdater::OPTION_ATTR
|
||||
permitted.concat UserUpdater::CATEGORY_IDS.keys.map { |k| { k => [] } }
|
||||
permitted.concat UserUpdater::TAG_NAMES.keys
|
||||
|
||||
result = params
|
||||
.permit(permitted)
|
||||
.reverse_merge(
|
||||
ip_address: request.remote_ip,
|
||||
registration_ip_address: request.remote_ip,
|
||||
locale: user_locale
|
||||
)
|
||||
|
||||
if !UsernameCheckerService.is_developer?(result['email']) &&
|
||||
is_api? &&
|
||||
current_user.present? &&
|
||||
current_user.admin?
|
||||
|
||||
result.merge!(params.permit(:active, :staged, :approved))
|
||||
end
|
||||
|
||||
modify_user_params(result)
|
||||
end
|
||||
|
||||
# Plugins can use this to modify user parameters
|
||||
def modify_user_params(attrs)
|
||||
attrs
|
||||
end
|
||||
|
||||
def user_locale
|
||||
I18n.locale
|
||||
end
|
||||
|
||||
def fail_with(key)
|
||||
render json: { success: false, message: I18n.t(key) }
|
||||
end
|
||||
|
||||
def track_visit_to_user_profile
|
||||
user_profile_id = @user.user_profile.id
|
||||
ip = request.remote_ip
|
||||
user_id = (current_user.id if current_user)
|
||||
|
||||
Scheduler::Defer.later 'Track profile view visit' do
|
||||
UserProfileView.add(user_profile_id, ip, user_id)
|
||||
end
|
||||
modify_user_params(result)
|
||||
end
|
||||
|
||||
# Plugins can use this to modify user parameters
|
||||
def modify_user_params(attrs)
|
||||
attrs
|
||||
end
|
||||
|
||||
def user_locale
|
||||
I18n.locale
|
||||
end
|
||||
|
||||
def fail_with(key)
|
||||
render json: { success: false, message: I18n.t(key) }
|
||||
end
|
||||
|
||||
def track_visit_to_user_profile
|
||||
user_profile_id = @user.user_profile.id
|
||||
ip = request.remote_ip
|
||||
user_id = (current_user.id if current_user)
|
||||
|
||||
Scheduler::Defer.later 'Track profile view visit' do
|
||||
UserProfileView.add(user_profile_id, ip, user_id)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -119,30 +119,30 @@ class WebhooksController < ActionController::Base
|
|||
|
||||
private
|
||||
|
||||
def mailgun_failure
|
||||
render body: nil, status: 406
|
||||
end
|
||||
def mailgun_failure
|
||||
render body: nil, status: 406
|
||||
end
|
||||
|
||||
def mailgun_success
|
||||
render body: nil, status: 200
|
||||
end
|
||||
def mailgun_success
|
||||
render body: nil, status: 200
|
||||
end
|
||||
|
||||
def mailgun_verify(timestamp, token, signature)
|
||||
digest = OpenSSL::Digest::SHA256.new
|
||||
data = "#{timestamp}#{token}"
|
||||
signature == OpenSSL::HMAC.hexdigest(digest, SiteSetting.mailgun_api_key, data)
|
||||
end
|
||||
def mailgun_verify(timestamp, token, signature)
|
||||
digest = OpenSSL::Digest::SHA256.new
|
||||
data = "#{timestamp}#{token}"
|
||||
signature == OpenSSL::HMAC.hexdigest(digest, SiteSetting.mailgun_api_key, data)
|
||||
end
|
||||
|
||||
def process_bounce(message_id, to_address, bounce_score)
|
||||
return if message_id.blank? || to_address.blank?
|
||||
def process_bounce(message_id, to_address, bounce_score)
|
||||
return if message_id.blank? || to_address.blank?
|
||||
|
||||
email_log = EmailLog.find_by(message_id: message_id, to_address: to_address)
|
||||
return if email_log.nil?
|
||||
email_log = EmailLog.find_by(message_id: message_id, to_address: to_address)
|
||||
return if email_log.nil?
|
||||
|
||||
email_log.update_columns(bounced: true)
|
||||
return if email_log.user.nil? || email_log.user.email.blank?
|
||||
email_log.update_columns(bounced: true)
|
||||
return if email_log.user.nil? || email_log.user.email.blank?
|
||||
|
||||
Email::Receiver.update_bounce_score(email_log.user.email, bounce_score)
|
||||
end
|
||||
Email::Receiver.update_bounce_score(email_log.user.email, bounce_score)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -211,169 +211,169 @@ module Jobs
|
|||
|
||||
private
|
||||
|
||||
def escape_comma(string)
|
||||
if string && string =~ /,/
|
||||
return "#{string}"
|
||||
else
|
||||
return string
|
||||
def escape_comma(string)
|
||||
if string && string =~ /,/
|
||||
return "#{string}"
|
||||
else
|
||||
return string
|
||||
end
|
||||
end
|
||||
|
||||
def get_base_user_array(user)
|
||||
user_array = []
|
||||
user_array.push(user.id, escape_comma(user.name), user.username, user.email, escape_comma(user.title), user.created_at, user.last_seen_at, user.last_posted_at, user.last_emailed_at, user.trust_level, user.approved, user.suspended_at, user.suspended_till, user.silenced_till, user.active, user.admin, user.moderator, user.ip_address, user.staged, user.user_stat.topics_entered, user.user_stat.posts_read_count, user.user_stat.time_read, user.user_stat.topic_count, user.user_stat.post_count, user.user_stat.likes_given, user.user_stat.likes_received, escape_comma(user.user_profile.location), user.user_profile.website, user.user_profile.views)
|
||||
end
|
||||
|
||||
def add_single_sign_on(user, user_info_array)
|
||||
if user.single_sign_on_record
|
||||
user_info_array.push(user.single_sign_on_record.external_id, user.single_sign_on_record.external_email, user.single_sign_on_record.external_username, escape_comma(user.single_sign_on_record.external_name), user.single_sign_on_record.external_avatar_url)
|
||||
else
|
||||
user_info_array.push(nil, nil, nil, nil, nil)
|
||||
end
|
||||
user_info_array
|
||||
end
|
||||
|
||||
def add_custom_fields(user, user_info_array, user_field_ids)
|
||||
if user_field_ids.present?
|
||||
user.user_fields.each do |custom_field|
|
||||
user_info_array << escape_comma(custom_field[1])
|
||||
end
|
||||
end
|
||||
user_info_array
|
||||
end
|
||||
|
||||
def get_base_user_array(user)
|
||||
user_array = []
|
||||
user_array.push(user.id, escape_comma(user.name), user.username, user.email, escape_comma(user.title), user.created_at, user.last_seen_at, user.last_posted_at, user.last_emailed_at, user.trust_level, user.approved, user.suspended_at, user.suspended_till, user.silenced_till, user.active, user.admin, user.moderator, user.ip_address, user.staged, user.user_stat.topics_entered, user.user_stat.posts_read_count, user.user_stat.time_read, user.user_stat.topic_count, user.user_stat.post_count, user.user_stat.likes_given, user.user_stat.likes_received, escape_comma(user.user_profile.location), user.user_profile.website, user.user_profile.views)
|
||||
def add_group_names(user, user_info_array)
|
||||
group_names = user.groups.each_with_object("") do |group, names|
|
||||
names << "#{group.name};"
|
||||
end
|
||||
user_info_array << group_names[0..-2] unless group_names.blank?
|
||||
group_names = nil
|
||||
user_info_array
|
||||
end
|
||||
|
||||
def add_single_sign_on(user, user_info_array)
|
||||
if user.single_sign_on_record
|
||||
user_info_array.push(user.single_sign_on_record.external_id, user.single_sign_on_record.external_email, user.single_sign_on_record.external_username, escape_comma(user.single_sign_on_record.external_name), user.single_sign_on_record.external_avatar_url)
|
||||
else
|
||||
user_info_array.push(nil, nil, nil, nil, nil)
|
||||
end
|
||||
user_info_array
|
||||
end
|
||||
|
||||
def add_custom_fields(user, user_info_array, user_field_ids)
|
||||
if user_field_ids.present?
|
||||
user.user_fields.each do |custom_field|
|
||||
user_info_array << escape_comma(custom_field[1])
|
||||
def get_user_archive_fields(user_archive)
|
||||
user_archive_array = []
|
||||
topic_data = user_archive.topic
|
||||
user_archive = user_archive.as_json
|
||||
topic_data = Topic.with_deleted.find_by(id: user_archive['topic_id']) if topic_data.nil?
|
||||
return user_archive_array if topic_data.nil?
|
||||
category = topic_data.category
|
||||
sub_category_name = "-"
|
||||
if category
|
||||
category_name = category.name
|
||||
if category.parent_category_id.present?
|
||||
# sub category
|
||||
if parent_category = Category.find_by(id: category.parent_category_id)
|
||||
category_name = parent_category.name
|
||||
sub_category_name = category.name
|
||||
end
|
||||
end
|
||||
user_info_array
|
||||
else
|
||||
# PM
|
||||
category_name = "-"
|
||||
end
|
||||
is_pm = topic_data.archetype == "private_message" ? I18n.t("csv_export.boolean_yes") : I18n.t("csv_export.boolean_no")
|
||||
url = "#{Discourse.base_url}/t/#{topic_data.slug}/#{topic_data.id}/#{user_archive['post_number']}"
|
||||
|
||||
topic_hash = { "post" => user_archive['raw'], "topic_title" => topic_data.title, "category" => category_name, "sub_category" => sub_category_name, "is_pm" => is_pm, "url" => url }
|
||||
user_archive.merge!(topic_hash)
|
||||
|
||||
HEADER_ATTRS_FOR['user_archive'].each do |attr|
|
||||
user_archive_array.push(user_archive[attr])
|
||||
end
|
||||
|
||||
def add_group_names(user, user_info_array)
|
||||
group_names = user.groups.each_with_object("") do |group, names|
|
||||
names << "#{group.name};"
|
||||
end
|
||||
user_info_array << group_names[0..-2] unless group_names.blank?
|
||||
group_names = nil
|
||||
user_info_array
|
||||
end
|
||||
user_archive_array
|
||||
end
|
||||
|
||||
def get_user_archive_fields(user_archive)
|
||||
user_archive_array = []
|
||||
topic_data = user_archive.topic
|
||||
user_archive = user_archive.as_json
|
||||
topic_data = Topic.with_deleted.find_by(id: user_archive['topic_id']) if topic_data.nil?
|
||||
return user_archive_array if topic_data.nil?
|
||||
category = topic_data.category
|
||||
sub_category_name = "-"
|
||||
if category
|
||||
category_name = category.name
|
||||
if category.parent_category_id.present?
|
||||
# sub category
|
||||
if parent_category = Category.find_by(id: category.parent_category_id)
|
||||
category_name = parent_category.name
|
||||
sub_category_name = category.name
|
||||
end
|
||||
end
|
||||
else
|
||||
# PM
|
||||
category_name = "-"
|
||||
end
|
||||
is_pm = topic_data.archetype == "private_message" ? I18n.t("csv_export.boolean_yes") : I18n.t("csv_export.boolean_no")
|
||||
url = "#{Discourse.base_url}/t/#{topic_data.slug}/#{topic_data.id}/#{user_archive['post_number']}"
|
||||
def get_staff_action_fields(staff_action)
|
||||
staff_action_array = []
|
||||
|
||||
topic_hash = { "post" => user_archive['raw'], "topic_title" => topic_data.title, "category" => category_name, "sub_category" => sub_category_name, "is_pm" => is_pm, "url" => url }
|
||||
user_archive.merge!(topic_hash)
|
||||
|
||||
HEADER_ATTRS_FOR['user_archive'].each do |attr|
|
||||
user_archive_array.push(user_archive[attr])
|
||||
end
|
||||
|
||||
user_archive_array
|
||||
end
|
||||
|
||||
def get_staff_action_fields(staff_action)
|
||||
staff_action_array = []
|
||||
|
||||
HEADER_ATTRS_FOR['staff_action'].each do |attr|
|
||||
data =
|
||||
if attr == 'action'
|
||||
UserHistory.actions.key(staff_action.attributes[attr]).to_s
|
||||
elsif attr == 'staff_user'
|
||||
user = User.find_by(id: staff_action.attributes['acting_user_id'])
|
||||
user.username if !user.nil?
|
||||
elsif attr == 'subject'
|
||||
user = User.find_by(id: staff_action.attributes['target_user_id'])
|
||||
user.nil? ? staff_action.attributes[attr] : "#{user.username} #{staff_action.attributes[attr]}"
|
||||
else
|
||||
staff_action.attributes[attr]
|
||||
end
|
||||
|
||||
staff_action_array.push(data)
|
||||
end
|
||||
staff_action_array
|
||||
end
|
||||
|
||||
def get_screened_email_fields(screened_email)
|
||||
screened_email_array = []
|
||||
|
||||
HEADER_ATTRS_FOR['screened_email'].each do |attr|
|
||||
data =
|
||||
if attr == 'action'
|
||||
ScreenedEmail.actions.key(screened_email.attributes['action_type']).to_s
|
||||
else
|
||||
screened_email.attributes[attr]
|
||||
end
|
||||
|
||||
screened_email_array.push(data)
|
||||
end
|
||||
|
||||
screened_email_array
|
||||
end
|
||||
|
||||
def get_screened_ip_fields(screened_ip)
|
||||
screened_ip_array = []
|
||||
|
||||
HEADER_ATTRS_FOR['screened_ip'].each do |attr|
|
||||
data =
|
||||
if attr == 'action'
|
||||
ScreenedIpAddress.actions.key(screened_ip.attributes['action_type']).to_s
|
||||
else
|
||||
screened_ip.attributes[attr]
|
||||
end
|
||||
|
||||
screened_ip_array.push(data)
|
||||
end
|
||||
|
||||
screened_ip_array
|
||||
end
|
||||
|
||||
def get_screened_url_fields(screened_url)
|
||||
screened_url_array = []
|
||||
|
||||
HEADER_ATTRS_FOR['screened_url'].each do |attr|
|
||||
data =
|
||||
if attr == 'action'
|
||||
action = ScreenedUrl.actions.key(screened_url.attributes['action_type']).to_s
|
||||
action = "do nothing" if action.blank?
|
||||
else
|
||||
screened_url.attributes[attr]
|
||||
end
|
||||
|
||||
screened_url_array.push(data)
|
||||
end
|
||||
|
||||
screened_url_array
|
||||
end
|
||||
|
||||
def notify_user(download_link, file_name, file_size, export_title)
|
||||
if @current_user
|
||||
if download_link.present?
|
||||
SystemMessage.create_from_system_user(
|
||||
@current_user,
|
||||
:csv_export_succeeded,
|
||||
download_link: download_link,
|
||||
file_name: "#{file_name}.gz",
|
||||
file_size: file_size,
|
||||
export_title: export_title
|
||||
)
|
||||
HEADER_ATTRS_FOR['staff_action'].each do |attr|
|
||||
data =
|
||||
if attr == 'action'
|
||||
UserHistory.actions.key(staff_action.attributes[attr]).to_s
|
||||
elsif attr == 'staff_user'
|
||||
user = User.find_by(id: staff_action.attributes['acting_user_id'])
|
||||
user.username if !user.nil?
|
||||
elsif attr == 'subject'
|
||||
user = User.find_by(id: staff_action.attributes['target_user_id'])
|
||||
user.nil? ? staff_action.attributes[attr] : "#{user.username} #{staff_action.attributes[attr]}"
|
||||
else
|
||||
SystemMessage.create_from_system_user(@current_user, :csv_export_failed)
|
||||
staff_action.attributes[attr]
|
||||
end
|
||||
|
||||
staff_action_array.push(data)
|
||||
end
|
||||
staff_action_array
|
||||
end
|
||||
|
||||
def get_screened_email_fields(screened_email)
|
||||
screened_email_array = []
|
||||
|
||||
HEADER_ATTRS_FOR['screened_email'].each do |attr|
|
||||
data =
|
||||
if attr == 'action'
|
||||
ScreenedEmail.actions.key(screened_email.attributes['action_type']).to_s
|
||||
else
|
||||
screened_email.attributes[attr]
|
||||
end
|
||||
|
||||
screened_email_array.push(data)
|
||||
end
|
||||
|
||||
screened_email_array
|
||||
end
|
||||
|
||||
def get_screened_ip_fields(screened_ip)
|
||||
screened_ip_array = []
|
||||
|
||||
HEADER_ATTRS_FOR['screened_ip'].each do |attr|
|
||||
data =
|
||||
if attr == 'action'
|
||||
ScreenedIpAddress.actions.key(screened_ip.attributes['action_type']).to_s
|
||||
else
|
||||
screened_ip.attributes[attr]
|
||||
end
|
||||
|
||||
screened_ip_array.push(data)
|
||||
end
|
||||
|
||||
screened_ip_array
|
||||
end
|
||||
|
||||
def get_screened_url_fields(screened_url)
|
||||
screened_url_array = []
|
||||
|
||||
HEADER_ATTRS_FOR['screened_url'].each do |attr|
|
||||
data =
|
||||
if attr == 'action'
|
||||
action = ScreenedUrl.actions.key(screened_url.attributes['action_type']).to_s
|
||||
action = "do nothing" if action.blank?
|
||||
else
|
||||
screened_url.attributes[attr]
|
||||
end
|
||||
|
||||
screened_url_array.push(data)
|
||||
end
|
||||
|
||||
screened_url_array
|
||||
end
|
||||
|
||||
def notify_user(download_link, file_name, file_size, export_title)
|
||||
if @current_user
|
||||
if download_link.present?
|
||||
SystemMessage.create_from_system_user(
|
||||
@current_user,
|
||||
:csv_export_succeeded,
|
||||
download_link: download_link,
|
||||
file_name: "#{file_name}.gz",
|
||||
file_size: file_size,
|
||||
export_title: export_title
|
||||
)
|
||||
else
|
||||
SystemMessage.create_from_system_user(@current_user, :csv_export_failed)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -174,9 +174,9 @@ module Jobs
|
|||
|
||||
private
|
||||
|
||||
def remove_scheme(src)
|
||||
src.sub(/^https?:/i, "")
|
||||
end
|
||||
def remove_scheme(src)
|
||||
src.sub(/^https?:/i, "")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -225,13 +225,13 @@ class Badge < ActiveRecord::Base
|
|||
|
||||
protected
|
||||
|
||||
def ensure_not_system
|
||||
self.id = [Badge.maximum(:id) + 1, 100].max unless id
|
||||
end
|
||||
def ensure_not_system
|
||||
self.id = [Badge.maximum(:id) + 1, 100].max unless id
|
||||
end
|
||||
|
||||
def i18n_name
|
||||
self.name.downcase.tr(' ', '_')
|
||||
end
|
||||
def i18n_name
|
||||
self.name.downcase.tr(' ', '_')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -44,127 +44,127 @@ class CategoryList
|
|||
|
||||
private
|
||||
|
||||
def find_relevant_topics
|
||||
@topics_by_id = {}
|
||||
@topics_by_category_id = {}
|
||||
def find_relevant_topics
|
||||
@topics_by_id = {}
|
||||
@topics_by_category_id = {}
|
||||
|
||||
category_featured_topics = CategoryFeaturedTopic.select([:category_id, :topic_id]).order(:rank)
|
||||
category_featured_topics = CategoryFeaturedTopic.select([:category_id, :topic_id]).order(:rank)
|
||||
|
||||
@all_topics = Topic.where(id: category_featured_topics.map(&:topic_id))
|
||||
@all_topics = @all_topics.includes(:last_poster) if @options[:include_topics]
|
||||
@all_topics.each do |t|
|
||||
# hint for the serializer
|
||||
t.include_last_poster = true if @options[:include_topics]
|
||||
@topics_by_id[t.id] = t
|
||||
end
|
||||
|
||||
category_featured_topics.each do |cft|
|
||||
@topics_by_category_id[cft.category_id] ||= []
|
||||
@topics_by_category_id[cft.category_id] << cft.topic_id
|
||||
end
|
||||
@all_topics = Topic.where(id: category_featured_topics.map(&:topic_id))
|
||||
@all_topics = @all_topics.includes(:last_poster) if @options[:include_topics]
|
||||
@all_topics.each do |t|
|
||||
# hint for the serializer
|
||||
t.include_last_poster = true if @options[:include_topics]
|
||||
@topics_by_id[t.id] = t
|
||||
end
|
||||
|
||||
def find_categories
|
||||
@categories = Category.includes(
|
||||
:uploaded_background,
|
||||
:uploaded_logo,
|
||||
:topic_only_relative_url,
|
||||
subcategories: [:topic_only_relative_url]
|
||||
).secured(@guardian)
|
||||
category_featured_topics.each do |cft|
|
||||
@topics_by_category_id[cft.category_id] ||= []
|
||||
@topics_by_category_id[cft.category_id] << cft.topic_id
|
||||
end
|
||||
end
|
||||
|
||||
@categories = @categories.where("categories.parent_category_id = ?", @options[:parent_category_id].to_i) if @options[:parent_category_id].present?
|
||||
def find_categories
|
||||
@categories = Category.includes(
|
||||
:uploaded_background,
|
||||
:uploaded_logo,
|
||||
:topic_only_relative_url,
|
||||
subcategories: [:topic_only_relative_url]
|
||||
).secured(@guardian)
|
||||
|
||||
if SiteSetting.fixed_category_positions
|
||||
@categories = @categories.order(:position, :id)
|
||||
else
|
||||
@categories = @categories.order('COALESCE(categories.posts_week, 0) DESC')
|
||||
.order('COALESCE(categories.posts_month, 0) DESC')
|
||||
.order('COALESCE(categories.posts_year, 0) DESC')
|
||||
.order('id ASC')
|
||||
end
|
||||
@categories = @categories.where("categories.parent_category_id = ?", @options[:parent_category_id].to_i) if @options[:parent_category_id].present?
|
||||
|
||||
@categories = @categories.to_a
|
||||
if SiteSetting.fixed_category_positions
|
||||
@categories = @categories.order(:position, :id)
|
||||
else
|
||||
@categories = @categories.order('COALESCE(categories.posts_week, 0) DESC')
|
||||
.order('COALESCE(categories.posts_month, 0) DESC')
|
||||
.order('COALESCE(categories.posts_year, 0) DESC')
|
||||
.order('id ASC')
|
||||
end
|
||||
|
||||
category_user = {}
|
||||
default_notification_level = nil
|
||||
unless @guardian.anonymous?
|
||||
category_user = Hash[*CategoryUser.where(user: @guardian.user).pluck(:category_id, :notification_level).flatten]
|
||||
default_notification_level = CategoryUser.notification_levels[:regular]
|
||||
end
|
||||
@categories = @categories.to_a
|
||||
|
||||
allowed_topic_create = Set.new(Category.topic_create_allowed(@guardian).pluck(:id))
|
||||
@categories.each do |category|
|
||||
category.notification_level = category_user[category.id] || default_notification_level
|
||||
category.permission = CategoryGroup.permission_types[:full] if allowed_topic_create.include?(category.id)
|
||||
category.has_children = category.subcategories.present?
|
||||
end
|
||||
category_user = {}
|
||||
default_notification_level = nil
|
||||
unless @guardian.anonymous?
|
||||
category_user = Hash[*CategoryUser.where(user: @guardian.user).pluck(:category_id, :notification_level).flatten]
|
||||
default_notification_level = CategoryUser.notification_levels[:regular]
|
||||
end
|
||||
|
||||
if @options[:parent_category_id].blank?
|
||||
subcategories = {}
|
||||
to_delete = Set.new
|
||||
@categories.each do |c|
|
||||
if c.parent_category_id.present?
|
||||
subcategories[c.parent_category_id] ||= []
|
||||
subcategories[c.parent_category_id] << c.id
|
||||
to_delete << c
|
||||
end
|
||||
allowed_topic_create = Set.new(Category.topic_create_allowed(@guardian).pluck(:id))
|
||||
@categories.each do |category|
|
||||
category.notification_level = category_user[category.id] || default_notification_level
|
||||
category.permission = CategoryGroup.permission_types[:full] if allowed_topic_create.include?(category.id)
|
||||
category.has_children = category.subcategories.present?
|
||||
end
|
||||
|
||||
if @options[:parent_category_id].blank?
|
||||
subcategories = {}
|
||||
to_delete = Set.new
|
||||
@categories.each do |c|
|
||||
if c.parent_category_id.present?
|
||||
subcategories[c.parent_category_id] ||= []
|
||||
subcategories[c.parent_category_id] << c.id
|
||||
to_delete << c
|
||||
end
|
||||
@categories.each { |c| c.subcategory_ids = subcategories[c.id] }
|
||||
@categories.delete_if { |c| to_delete.include?(c) }
|
||||
end
|
||||
@categories.each { |c| c.subcategory_ids = subcategories[c.id] }
|
||||
@categories.delete_if { |c| to_delete.include?(c) }
|
||||
end
|
||||
|
||||
if @topics_by_category_id
|
||||
@categories.each do |c|
|
||||
topics_in_cat = @topics_by_category_id[c.id]
|
||||
if topics_in_cat.present?
|
||||
c.displayable_topics = []
|
||||
topics_in_cat.each do |topic_id|
|
||||
topic = @topics_by_id[topic_id]
|
||||
if topic.present? && @guardian.can_see?(topic)
|
||||
# topic.category is very slow under rails 4.2
|
||||
topic.association(:category).target = c
|
||||
c.displayable_topics << topic
|
||||
end
|
||||
if @topics_by_category_id
|
||||
@categories.each do |c|
|
||||
topics_in_cat = @topics_by_category_id[c.id]
|
||||
if topics_in_cat.present?
|
||||
c.displayable_topics = []
|
||||
topics_in_cat.each do |topic_id|
|
||||
topic = @topics_by_id[topic_id]
|
||||
if topic.present? && @guardian.can_see?(topic)
|
||||
# topic.category is very slow under rails 4.2
|
||||
topic.association(:category).target = c
|
||||
c.displayable_topics << topic
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def prune_empty
|
||||
return if SiteSetting.allow_uncategorized_topics
|
||||
@categories.delete_if { |c| c.uncategorized? && c.displayable_topics.blank? }
|
||||
def prune_empty
|
||||
return if SiteSetting.allow_uncategorized_topics
|
||||
@categories.delete_if { |c| c.uncategorized? && c.displayable_topics.blank? }
|
||||
end
|
||||
|
||||
# Attach some data for serialization to each topic
|
||||
def find_user_data
|
||||
if @guardian.current_user && @all_topics.present?
|
||||
topic_lookup = TopicUser.lookup_for(@guardian.current_user, @all_topics)
|
||||
@all_topics.each { |ft| ft.user_data = topic_lookup[ft.id] }
|
||||
end
|
||||
end
|
||||
|
||||
# Attach some data for serialization to each topic
|
||||
def find_user_data
|
||||
if @guardian.current_user && @all_topics.present?
|
||||
topic_lookup = TopicUser.lookup_for(@guardian.current_user, @all_topics)
|
||||
@all_topics.each { |ft| ft.user_data = topic_lookup[ft.id] }
|
||||
end
|
||||
end
|
||||
|
||||
# Put unpinned topics at the end of the list
|
||||
def sort_unpinned
|
||||
if @guardian.current_user && @all_topics.present?
|
||||
@categories.each do |c|
|
||||
next if c.displayable_topics.blank? || c.displayable_topics.size <= c.num_featured_topics
|
||||
unpinned = []
|
||||
c.displayable_topics.each do |t|
|
||||
unpinned << t if t.pinned_at && PinnedCheck.unpinned?(t, t.user_data)
|
||||
end
|
||||
unless unpinned.empty?
|
||||
c.displayable_topics = (c.displayable_topics - unpinned) + unpinned
|
||||
end
|
||||
# Put unpinned topics at the end of the list
|
||||
def sort_unpinned
|
||||
if @guardian.current_user && @all_topics.present?
|
||||
@categories.each do |c|
|
||||
next if c.displayable_topics.blank? || c.displayable_topics.size <= c.num_featured_topics
|
||||
unpinned = []
|
||||
c.displayable_topics.each do |t|
|
||||
unpinned << t if t.pinned_at && PinnedCheck.unpinned?(t, t.user_data)
|
||||
end
|
||||
unless unpinned.empty?
|
||||
c.displayable_topics = (c.displayable_topics - unpinned) + unpinned
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def trim_results
|
||||
@categories.each do |c|
|
||||
next if c.displayable_topics.blank?
|
||||
c.displayable_topics = c.displayable_topics[0, c.num_featured_topics]
|
||||
end
|
||||
def trim_results
|
||||
@categories.each do |c|
|
||||
next if c.displayable_topics.blank?
|
||||
c.displayable_topics = c.displayable_topics[0, c.num_featured_topics]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -45,8 +45,8 @@ module Trashable
|
|||
|
||||
private
|
||||
|
||||
def trash_update(deleted_at, deleted_by_id)
|
||||
self.update_columns(deleted_at: deleted_at, deleted_by_id: deleted_by_id)
|
||||
end
|
||||
def trash_update(deleted_at, deleted_by_id)
|
||||
self.update_columns(deleted_at: deleted_at, deleted_by_id: deleted_by_id)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -53,13 +53,13 @@ class EmbeddableHost < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
def host_must_be_valid
|
||||
if host !~ /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,10}(:[0-9]{1,5})?(\/.*)?\Z/i &&
|
||||
host !~ /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(:[0-9]{1,5})?(\/.*)?\Z/ &&
|
||||
host !~ /\A([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.)?localhost(\:[0-9]{1,5})?(\/.*)?\Z/i
|
||||
errors.add(:host, I18n.t('errors.messages.invalid'))
|
||||
end
|
||||
def host_must_be_valid
|
||||
if host !~ /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,10}(:[0-9]{1,5})?(\/.*)?\Z/i &&
|
||||
host !~ /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(:[0-9]{1,5})?(\/.*)?\Z/ &&
|
||||
host !~ /\A([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.)?localhost(\:[0-9]{1,5})?(\/.*)?\Z/i
|
||||
errors.add(:host, I18n.t('errors.messages.invalid'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
|
|
@ -598,58 +598,58 @@ class Group < ActiveRecord::Base
|
|||
|
||||
protected
|
||||
|
||||
def name_format_validator
|
||||
self.name.strip!
|
||||
def name_format_validator
|
||||
self.name.strip!
|
||||
|
||||
UsernameValidator.perform_validation(self, 'name') || begin
|
||||
name_lower = self.name.downcase
|
||||
UsernameValidator.perform_validation(self, 'name') || begin
|
||||
name_lower = self.name.downcase
|
||||
|
||||
if self.will_save_change_to_name? && self.name_was&.downcase != name_lower
|
||||
existing = Group.exec_sql(
|
||||
User::USERNAME_EXISTS_SQL, username: name_lower
|
||||
).values.present?
|
||||
if self.will_save_change_to_name? && self.name_was&.downcase != name_lower
|
||||
existing = Group.exec_sql(
|
||||
User::USERNAME_EXISTS_SQL, username: name_lower
|
||||
).values.present?
|
||||
|
||||
if existing
|
||||
errors.add(:name, I18n.t("activerecord.errors.messages.taken"))
|
||||
end
|
||||
if existing
|
||||
errors.add(:name, I18n.t("activerecord.errors.messages.taken"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def automatic_membership_email_domains_format_validator
|
||||
return if self.automatic_membership_email_domains.blank?
|
||||
def automatic_membership_email_domains_format_validator
|
||||
return if self.automatic_membership_email_domains.blank?
|
||||
|
||||
domains = self.automatic_membership_email_domains.split("|")
|
||||
domains.each do |domain|
|
||||
domain.sub!(/^https?:\/\//, '')
|
||||
domain.sub!(/\/.*$/, '')
|
||||
self.errors.add :base, (I18n.t('groups.errors.invalid_domain', domain: domain)) unless domain =~ /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,24}(:[0-9]{1,5})?(\/.*)?\Z/i
|
||||
end
|
||||
self.automatic_membership_email_domains = domains.join("|")
|
||||
domains = self.automatic_membership_email_domains.split("|")
|
||||
domains.each do |domain|
|
||||
domain.sub!(/^https?:\/\//, '')
|
||||
domain.sub!(/\/.*$/, '')
|
||||
self.errors.add :base, (I18n.t('groups.errors.invalid_domain', domain: domain)) unless domain =~ /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,24}(:[0-9]{1,5})?(\/.*)?\Z/i
|
||||
end
|
||||
self.automatic_membership_email_domains = domains.join("|")
|
||||
end
|
||||
|
||||
# hack around AR
|
||||
def destroy_deletions
|
||||
if @deletions
|
||||
@deletions.each do |gu|
|
||||
gu.destroy
|
||||
User.where('id = ? AND primary_group_id = ?', gu.user_id, gu.group_id).update_all 'primary_group_id = NULL'
|
||||
end
|
||||
end
|
||||
@deletions = nil
|
||||
end
|
||||
|
||||
def automatic_group_membership
|
||||
if self.automatic_membership_retroactive
|
||||
Jobs.enqueue(:automatic_group_membership, group_id: self.id)
|
||||
# hack around AR
|
||||
def destroy_deletions
|
||||
if @deletions
|
||||
@deletions.each do |gu|
|
||||
gu.destroy
|
||||
User.where('id = ? AND primary_group_id = ?', gu.user_id, gu.group_id).update_all 'primary_group_id = NULL'
|
||||
end
|
||||
end
|
||||
@deletions = nil
|
||||
end
|
||||
|
||||
def update_title
|
||||
return if new_record? && !self.title.present?
|
||||
def automatic_group_membership
|
||||
if self.automatic_membership_retroactive
|
||||
Jobs.enqueue(:automatic_group_membership, group_id: self.id)
|
||||
end
|
||||
end
|
||||
|
||||
if self.saved_change_to_title?
|
||||
sql = <<-SQL.squish
|
||||
def update_title
|
||||
return if new_record? && !self.title.present?
|
||||
|
||||
if self.saved_change_to_title?
|
||||
sql = <<-SQL.squish
|
||||
UPDATE users
|
||||
SET title = :title
|
||||
WHERE (title = :title_was OR title = '' OR title IS NULL)
|
||||
|
@ -657,71 +657,71 @@ class Group < ActiveRecord::Base
|
|||
AND id IN (SELECT user_id FROM group_users WHERE group_id = :id)
|
||||
SQL
|
||||
|
||||
self.class.exec_sql(sql, title: title, title_was: title_before_last_save, id: id)
|
||||
end
|
||||
self.class.exec_sql(sql, title: title, title_was: title_before_last_save, id: id)
|
||||
end
|
||||
end
|
||||
|
||||
def update_primary_group
|
||||
return if new_record? && !self.primary_group?
|
||||
def update_primary_group
|
||||
return if new_record? && !self.primary_group?
|
||||
|
||||
if self.saved_change_to_primary_group?
|
||||
sql = <<~SQL
|
||||
if self.saved_change_to_primary_group?
|
||||
sql = <<~SQL
|
||||
UPDATE users
|
||||
/*set*/
|
||||
/*where*/
|
||||
SQL
|
||||
|
||||
builder = SqlBuilder.new(sql)
|
||||
builder.where("
|
||||
id IN (
|
||||
SELECT user_id
|
||||
FROM group_users
|
||||
WHERE group_id = :id
|
||||
)", id: id)
|
||||
builder = SqlBuilder.new(sql)
|
||||
builder.where("
|
||||
id IN (
|
||||
SELECT user_id
|
||||
FROM group_users
|
||||
WHERE group_id = :id
|
||||
)", id: id)
|
||||
|
||||
if primary_group
|
||||
builder.set("primary_group_id = :id")
|
||||
else
|
||||
builder.set("primary_group_id = NULL")
|
||||
builder.where("primary_group_id = :id")
|
||||
end
|
||||
|
||||
builder.exec
|
||||
if primary_group
|
||||
builder.set("primary_group_id = :id")
|
||||
else
|
||||
builder.set("primary_group_id = NULL")
|
||||
builder.where("primary_group_id = :id")
|
||||
end
|
||||
|
||||
builder.exec
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_grant_trust_level
|
||||
unless TrustLevel.valid?(self.grant_trust_level)
|
||||
self.errors.add(:base, I18n.t(
|
||||
'groups.errors.grant_trust_level_not_valid',
|
||||
trust_level: self.grant_trust_level
|
||||
))
|
||||
def validate_grant_trust_level
|
||||
unless TrustLevel.valid?(self.grant_trust_level)
|
||||
self.errors.add(:base, I18n.t(
|
||||
'groups.errors.grant_trust_level_not_valid',
|
||||
trust_level: self.grant_trust_level
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
def can_allow_membership_requests
|
||||
valid = true
|
||||
|
||||
valid =
|
||||
if self.persisted?
|
||||
self.group_users.where(owner: true).exists?
|
||||
else
|
||||
self.group_users.any?(&:owner)
|
||||
end
|
||||
|
||||
if !valid
|
||||
self.errors.add(:base, I18n.t('groups.errors.cant_allow_membership_requests'))
|
||||
end
|
||||
end
|
||||
|
||||
def can_allow_membership_requests
|
||||
valid = true
|
||||
|
||||
valid =
|
||||
if self.persisted?
|
||||
self.group_users.where(owner: true).exists?
|
||||
else
|
||||
self.group_users.any?(&:owner)
|
||||
end
|
||||
|
||||
if !valid
|
||||
self.errors.add(:base, I18n.t('groups.errors.cant_allow_membership_requests'))
|
||||
end
|
||||
end
|
||||
|
||||
def enqueue_update_mentions_job
|
||||
Jobs.enqueue(:update_group_mentions,
|
||||
previous_name: self.name_before_last_save,
|
||||
group_id: self.id
|
||||
)
|
||||
end
|
||||
def enqueue_update_mentions_job
|
||||
Jobs.enqueue(:update_group_mentions,
|
||||
previous_name: self.name_before_last_save,
|
||||
group_id: self.id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
|
|
@ -29,11 +29,11 @@ class GroupArchivedMessage < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
def self.publish_topic_tracking_state(topic)
|
||||
TopicTrackingState.publish_private_message(
|
||||
topic, group_archive: true
|
||||
)
|
||||
end
|
||||
def self.publish_topic_tracking_state(topic)
|
||||
TopicTrackingState.publish_private_message(
|
||||
topic, group_archive: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
|
|
@ -127,19 +127,19 @@ class PostAnalyzer
|
|||
|
||||
private
|
||||
|
||||
def cooked_stripped
|
||||
@cooked_stripped ||= begin
|
||||
doc = Nokogiri::HTML.fragment(cook(@raw, topic_id: @topic_id))
|
||||
doc.css("pre .mention, aside.quote > .title, aside.quote .mention, .onebox, .elided").remove
|
||||
doc
|
||||
end
|
||||
def cooked_stripped
|
||||
@cooked_stripped ||= begin
|
||||
doc = Nokogiri::HTML.fragment(cook(@raw, topic_id: @topic_id))
|
||||
doc.css("pre .mention, aside.quote > .title, aside.quote .mention, .onebox, .elided").remove
|
||||
doc
|
||||
end
|
||||
end
|
||||
|
||||
def link_is_a_mention?(l)
|
||||
html_class = l['class']
|
||||
return false if html_class.blank?
|
||||
href = l['href'].to_s
|
||||
html_class.to_s['mention'] && href[/^\/u\//] || href[/^\/users\//]
|
||||
end
|
||||
def link_is_a_mention?(l)
|
||||
html_class = l['class']
|
||||
return false if html_class.blank?
|
||||
href = l['href'].to_s
|
||||
html_class.to_s['mention'] && href[/^\/u\//] || href[/^\/users\//]
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -94,30 +94,30 @@ class QueuedPost < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
def change_to!(state, changed_by)
|
||||
state_val = QueuedPost.states[state]
|
||||
def change_to!(state, changed_by)
|
||||
state_val = QueuedPost.states[state]
|
||||
|
||||
updates = { state: state_val,
|
||||
"#{state}_by_id" => changed_by.id,
|
||||
"#{state}_at" => Time.now }
|
||||
updates = { state: state_val,
|
||||
"#{state}_by_id" => changed_by.id,
|
||||
"#{state}_at" => Time.now }
|
||||
|
||||
# We use an update with `row_count` trick here to avoid stampeding requests to
|
||||
# update the same row simultaneously. Only one state change should go through and
|
||||
# we can use the DB to enforce this
|
||||
row_count = QueuedPost.where('id = ? AND state <> ?', id, state_val).update_all(updates)
|
||||
raise InvalidStateTransition.new if row_count == 0
|
||||
# We use an update with `row_count` trick here to avoid stampeding requests to
|
||||
# update the same row simultaneously. Only one state change should go through and
|
||||
# we can use the DB to enforce this
|
||||
row_count = QueuedPost.where('id = ? AND state <> ?', id, state_val).update_all(updates)
|
||||
raise InvalidStateTransition.new if row_count == 0
|
||||
|
||||
if [:rejected, :approved].include?(state)
|
||||
UserAction.where(queued_post_id: id).destroy_all
|
||||
end
|
||||
|
||||
# Update the record in memory too, and clear the dirty flag
|
||||
updates.each { |k, v| send("#{k}=", v) }
|
||||
changes_applied
|
||||
|
||||
QueuedPost.broadcast_new! if visible?
|
||||
if [:rejected, :approved].include?(state)
|
||||
UserAction.where(queued_post_id: id).destroy_all
|
||||
end
|
||||
|
||||
# Update the record in memory too, and clear the dirty flag
|
||||
updates.each { |k, v| send("#{k}=", v) }
|
||||
changes_applied
|
||||
|
||||
QueuedPost.broadcast_new! if visible?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
|
|
@ -84,8 +84,8 @@ SQL
|
|||
|
||||
private
|
||||
|
||||
def update_participant_count
|
||||
count = topic.posts.where('NOT hidden AND post_type in (?)', Topic.visible_post_types).count('distinct user_id')
|
||||
topic.update_columns(participant_count: count)
|
||||
end
|
||||
def update_participant_count
|
||||
count = topic.posts.where('NOT hidden AND post_type in (?)', Topic.visible_post_types).count('distinct user_id')
|
||||
topic.update_columns(participant_count: count)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -83,66 +83,66 @@ class TopicTimer < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
def ensure_update_will_happen
|
||||
if created_at && (execute_at < created_at)
|
||||
errors.add(:execute_at, I18n.t(
|
||||
'activerecord.errors.models.topic_timer.attributes.execute_at.in_the_past'
|
||||
))
|
||||
end
|
||||
def ensure_update_will_happen
|
||||
if created_at && (execute_at < created_at)
|
||||
errors.add(:execute_at, I18n.t(
|
||||
'activerecord.errors.models.topic_timer.attributes.execute_at.in_the_past'
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
def cancel_auto_close_job
|
||||
Jobs.cancel_scheduled_job(:toggle_topic_closed, topic_timer_id: id)
|
||||
end
|
||||
alias_method :cancel_auto_open_job, :cancel_auto_close_job
|
||||
def cancel_auto_close_job
|
||||
Jobs.cancel_scheduled_job(:toggle_topic_closed, topic_timer_id: id)
|
||||
end
|
||||
alias_method :cancel_auto_open_job, :cancel_auto_close_job
|
||||
|
||||
def cancel_auto_publish_to_category_job
|
||||
Jobs.cancel_scheduled_job(:publish_topic_to_category, topic_timer_id: id)
|
||||
end
|
||||
def cancel_auto_publish_to_category_job
|
||||
Jobs.cancel_scheduled_job(:publish_topic_to_category, topic_timer_id: id)
|
||||
end
|
||||
|
||||
def cancel_auto_delete_job
|
||||
Jobs.cancel_scheduled_job(:delete_topic, topic_timer_id: id)
|
||||
end
|
||||
def cancel_auto_delete_job
|
||||
Jobs.cancel_scheduled_job(:delete_topic, topic_timer_id: id)
|
||||
end
|
||||
|
||||
def cancel_auto_reminder_job
|
||||
Jobs.cancel_scheduled_job(:topic_reminder, topic_timer_id: id)
|
||||
end
|
||||
def cancel_auto_reminder_job
|
||||
Jobs.cancel_scheduled_job(:topic_reminder, topic_timer_id: id)
|
||||
end
|
||||
|
||||
def schedule_auto_open_job(time)
|
||||
return unless topic
|
||||
topic.update_status('closed', true, user) if !topic.closed
|
||||
def schedule_auto_open_job(time)
|
||||
return unless topic
|
||||
topic.update_status('closed', true, user) if !topic.closed
|
||||
|
||||
Jobs.enqueue_at(time, :toggle_topic_closed,
|
||||
topic_timer_id: id,
|
||||
state: false
|
||||
)
|
||||
end
|
||||
Jobs.enqueue_at(time, :toggle_topic_closed,
|
||||
topic_timer_id: id,
|
||||
state: false
|
||||
)
|
||||
end
|
||||
|
||||
def schedule_auto_close_job(time)
|
||||
return unless topic
|
||||
topic.update_status('closed', false, user) if topic.closed
|
||||
def schedule_auto_close_job(time)
|
||||
return unless topic
|
||||
topic.update_status('closed', false, user) if topic.closed
|
||||
|
||||
Jobs.enqueue_at(time, :toggle_topic_closed,
|
||||
topic_timer_id: id,
|
||||
state: true
|
||||
)
|
||||
end
|
||||
Jobs.enqueue_at(time, :toggle_topic_closed,
|
||||
topic_timer_id: id,
|
||||
state: true
|
||||
)
|
||||
end
|
||||
|
||||
def schedule_auto_publish_to_category_job(time)
|
||||
Jobs.enqueue_at(time, :publish_topic_to_category, topic_timer_id: id)
|
||||
end
|
||||
def schedule_auto_publish_to_category_job(time)
|
||||
Jobs.enqueue_at(time, :publish_topic_to_category, topic_timer_id: id)
|
||||
end
|
||||
|
||||
def publishing_to_category?
|
||||
self.status_type.to_i == TopicTimer.types[:publish_to_category]
|
||||
end
|
||||
def publishing_to_category?
|
||||
self.status_type.to_i == TopicTimer.types[:publish_to_category]
|
||||
end
|
||||
|
||||
def schedule_auto_delete_job(time)
|
||||
Jobs.enqueue_at(time, :delete_topic, topic_timer_id: id)
|
||||
end
|
||||
def schedule_auto_delete_job(time)
|
||||
Jobs.enqueue_at(time, :delete_topic, topic_timer_id: id)
|
||||
end
|
||||
|
||||
def schedule_auto_reminder_job(time)
|
||||
Jobs.enqueue_at(time, :topic_reminder, topic_timer_id: id)
|
||||
end
|
||||
def schedule_auto_reminder_job(time)
|
||||
Jobs.enqueue_at(time, :topic_reminder, topic_timer_id: id)
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
|
|
@ -19,9 +19,9 @@ class UnsubscribeKey < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
def generate_random_key
|
||||
self.key = SecureRandom.hex(32)
|
||||
end
|
||||
def generate_random_key
|
||||
self.key = SecureRandom.hex(32)
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
|
|
@ -36,11 +36,11 @@ class UserArchivedMessage < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
def self.publish_topic_tracking_state(topic, user_id)
|
||||
TopicTrackingState.publish_private_message(
|
||||
topic, archive_user_id: user_id
|
||||
)
|
||||
end
|
||||
def self.publish_topic_tracking_state(topic, user_id)
|
||||
TopicTrackingState.publish_private_message(
|
||||
topic, archive_user_id: user_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
|
|
@ -26,9 +26,9 @@ class UserBadge < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
def single_grant_badge?
|
||||
self.badge.single_grant?
|
||||
end
|
||||
def single_grant_badge?
|
||||
self.badge.single_grant?
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
|
|
@ -141,10 +141,10 @@ class UserOption < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
def update_tracked_topics
|
||||
return unless saved_change_to_auto_track_topics_after_msecs?
|
||||
TrackedTopicsUpdater.new(id, auto_track_topics_after_msecs).call
|
||||
end
|
||||
def update_tracked_topics
|
||||
return unless saved_change_to_auto_track_topics_after_msecs?
|
||||
TrackedTopicsUpdater.new(id, auto_track_topics_after_msecs).call
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -93,9 +93,9 @@ class WebHook < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
def self.guardian
|
||||
@guardian ||= Guardian.new(Discourse.system_user)
|
||||
end
|
||||
def self.guardian
|
||||
@guardian ||= Guardian.new(Discourse.system_user)
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
|
|
@ -81,12 +81,12 @@ class AdminUserActionSerializer < ApplicationSerializer
|
|||
|
||||
private
|
||||
|
||||
# we need this to handle deleted topics which aren't loaded via the .includes(:topic)
|
||||
# because Rails 4 "unscoped" support is bugged (cf. https://github.com/rails/rails/issues/13775)
|
||||
def topic
|
||||
return @topic if @topic
|
||||
@topic = object.topic || Topic.with_deleted.find(object.topic_id)
|
||||
@topic
|
||||
end
|
||||
# we need this to handle deleted topics which aren't loaded via the .includes(:topic)
|
||||
# because Rails 4 "unscoped" support is bugged (cf. https://github.com/rails/rails/issues/13775)
|
||||
def topic
|
||||
return @topic if @topic
|
||||
@topic = object.topic || Topic.with_deleted.find(object.topic_id)
|
||||
@topic
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -68,15 +68,15 @@ class BasicGroupSerializer < ApplicationSerializer
|
|||
|
||||
private
|
||||
|
||||
def staff?
|
||||
@staff ||= scope.is_staff?
|
||||
end
|
||||
def staff?
|
||||
@staff ||= scope.is_staff?
|
||||
end
|
||||
|
||||
def user_group_ids
|
||||
@options[:user_group_ids]
|
||||
end
|
||||
def user_group_ids
|
||||
@options[:user_group_ids]
|
||||
end
|
||||
|
||||
def owner_group_ids
|
||||
@options[:owner_group_ids]
|
||||
end
|
||||
def owner_group_ids
|
||||
@options[:owner_group_ids]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -121,8 +121,8 @@ class ListableTopicSerializer < BasicTopicSerializer
|
|||
|
||||
protected
|
||||
|
||||
def unread_helper
|
||||
@unread_helper ||= Unread.new(object, object.user_data, scope)
|
||||
end
|
||||
def unread_helper
|
||||
@unread_helper ||= Unread.new(object, object.user_data, scope)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -45,9 +45,9 @@ class PostActionTypeSerializer < ApplicationSerializer
|
|||
|
||||
protected
|
||||
|
||||
def i18n(field, vars = nil)
|
||||
key = "post_action_types.#{name_key}.#{field}"
|
||||
vars ? I18n.t(key, vars) : I18n.t(key)
|
||||
end
|
||||
def i18n(field, vars = nil)
|
||||
key = "post_action_types.#{name_key}.#{field}"
|
||||
vars ? I18n.t(key, vars) : I18n.t(key)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -173,94 +173,94 @@ class PostRevisionSerializer < ApplicationSerializer
|
|||
|
||||
protected
|
||||
|
||||
def post
|
||||
@post ||= object.post
|
||||
def post
|
||||
@post ||= object.post
|
||||
end
|
||||
|
||||
def topic
|
||||
@topic ||= object.post.topic
|
||||
end
|
||||
|
||||
def revisions
|
||||
@revisions ||= all_revisions.select { |r| scope.can_view_hidden_post_revisions? || !r["hidden"] }
|
||||
end
|
||||
|
||||
def all_revisions
|
||||
return @all_revisions if @all_revisions
|
||||
|
||||
post_revisions = PostRevision.where(post_id: object.post_id).order(:number).to_a
|
||||
|
||||
latest_modifications = {
|
||||
"raw" => [post.raw],
|
||||
"cooked" => [post.cooked],
|
||||
"edit_reason" => [post.edit_reason],
|
||||
"wiki" => [post.wiki],
|
||||
"post_type" => [post.post_type],
|
||||
"user_id" => [post.user_id]
|
||||
}
|
||||
|
||||
# Retrieve any `tracked_topic_fields`
|
||||
PostRevisor.tracked_topic_fields.each_key do |field|
|
||||
latest_modifications[field.to_s] = [topic.send(field)] if topic.respond_to?(field)
|
||||
end
|
||||
|
||||
def topic
|
||||
@topic ||= object.post.topic
|
||||
end
|
||||
latest_modifications["featured_link"] = [post.topic.featured_link] if SiteSetting.topic_featured_link_enabled
|
||||
latest_modifications["tags"] = [topic.tags.pluck(:name)] if scope.can_see_tags?(topic)
|
||||
|
||||
def revisions
|
||||
@revisions ||= all_revisions.select { |r| scope.can_view_hidden_post_revisions? || !r["hidden"] }
|
||||
end
|
||||
post_revisions << PostRevision.new(
|
||||
number: post_revisions.last.number + 1,
|
||||
hidden: post.hidden,
|
||||
modifications: latest_modifications
|
||||
)
|
||||
|
||||
def all_revisions
|
||||
return @all_revisions if @all_revisions
|
||||
@all_revisions = []
|
||||
|
||||
post_revisions = PostRevision.where(post_id: object.post_id).order(:number).to_a
|
||||
# backtrack
|
||||
post_revisions.each do |pr|
|
||||
revision = HashWithIndifferentAccess.new
|
||||
revision[:revision] = pr.number
|
||||
revision[:hidden] = pr.hidden
|
||||
|
||||
latest_modifications = {
|
||||
"raw" => [post.raw],
|
||||
"cooked" => [post.cooked],
|
||||
"edit_reason" => [post.edit_reason],
|
||||
"wiki" => [post.wiki],
|
||||
"post_type" => [post.post_type],
|
||||
"user_id" => [post.user_id]
|
||||
}
|
||||
|
||||
# Retrieve any `tracked_topic_fields`
|
||||
PostRevisor.tracked_topic_fields.each_key do |field|
|
||||
latest_modifications[field.to_s] = [topic.send(field)] if topic.respond_to?(field)
|
||||
pr.modifications.each_key do |field|
|
||||
revision[field] = pr.modifications[field][0]
|
||||
end
|
||||
|
||||
latest_modifications["featured_link"] = [post.topic.featured_link] if SiteSetting.topic_featured_link_enabled
|
||||
latest_modifications["tags"] = [topic.tags.pluck(:name)] if scope.can_see_tags?(topic)
|
||||
|
||||
post_revisions << PostRevision.new(
|
||||
number: post_revisions.last.number + 1,
|
||||
hidden: post.hidden,
|
||||
modifications: latest_modifications
|
||||
)
|
||||
|
||||
@all_revisions = []
|
||||
|
||||
# backtrack
|
||||
post_revisions.each do |pr|
|
||||
revision = HashWithIndifferentAccess.new
|
||||
revision[:revision] = pr.number
|
||||
revision[:hidden] = pr.hidden
|
||||
|
||||
pr.modifications.each_key do |field|
|
||||
revision[field] = pr.modifications[field][0]
|
||||
end
|
||||
|
||||
@all_revisions << revision
|
||||
end
|
||||
|
||||
# waterfall
|
||||
(@all_revisions.count - 1).downto(1).each do |r|
|
||||
cur = @all_revisions[r]
|
||||
prev = @all_revisions[r - 1]
|
||||
|
||||
cur.each_key do |field|
|
||||
prev[field] = prev.has_key?(field) ? prev[field] : cur[field]
|
||||
end
|
||||
end
|
||||
|
||||
@all_revisions
|
||||
@all_revisions << revision
|
||||
end
|
||||
|
||||
def previous
|
||||
@previous ||= revisions.select { |r| r["revision"] <= current_revision }.last
|
||||
end
|
||||
# waterfall
|
||||
(@all_revisions.count - 1).downto(1).each do |r|
|
||||
cur = @all_revisions[r]
|
||||
prev = @all_revisions[r - 1]
|
||||
|
||||
def current
|
||||
@current ||= revisions.select { |r| r["revision"] > current_revision }.first
|
||||
end
|
||||
|
||||
def user
|
||||
# if stuff goes pear shape attribute to system
|
||||
object.user || Discourse.system_user
|
||||
end
|
||||
|
||||
def filter_visible_tags(tags)
|
||||
if tags.is_a?(Array) && tags.size > 0
|
||||
@hidden_tag_names ||= DiscourseTagging.hidden_tag_names(scope)
|
||||
tags - @hidden_tag_names
|
||||
else
|
||||
tags
|
||||
cur.each_key do |field|
|
||||
prev[field] = prev.has_key?(field) ? prev[field] : cur[field]
|
||||
end
|
||||
end
|
||||
|
||||
@all_revisions
|
||||
end
|
||||
|
||||
def previous
|
||||
@previous ||= revisions.select { |r| r["revision"] <= current_revision }.last
|
||||
end
|
||||
|
||||
def current
|
||||
@current ||= revisions.select { |r| r["revision"] > current_revision }.first
|
||||
end
|
||||
|
||||
def user
|
||||
# if stuff goes pear shape attribute to system
|
||||
object.user || Discourse.system_user
|
||||
end
|
||||
|
||||
def filter_visible_tags(tags)
|
||||
if tags.is_a?(Array) && tags.size > 0
|
||||
@hidden_tag_names ||= DiscourseTagging.hidden_tag_names(scope)
|
||||
tags - @hidden_tag_names
|
||||
else
|
||||
tags
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -380,26 +380,26 @@ class PostSerializer < BasicPostSerializer
|
|||
|
||||
private
|
||||
|
||||
def topic
|
||||
@topic = object.topic
|
||||
@topic ||= Topic.with_deleted.find(object.topic_id) if scope.is_staff?
|
||||
@topic
|
||||
end
|
||||
def topic
|
||||
@topic = object.topic
|
||||
@topic ||= Topic.with_deleted.find(object.topic_id) if scope.is_staff?
|
||||
@topic
|
||||
end
|
||||
|
||||
def post_actions
|
||||
@post_actions ||= (@topic_view&.all_post_actions || {})[object.id]
|
||||
end
|
||||
def post_actions
|
||||
@post_actions ||= (@topic_view&.all_post_actions || {})[object.id]
|
||||
end
|
||||
|
||||
def active_flags
|
||||
@active_flags ||= (@topic_view&.all_active_flags || {})[object.id]
|
||||
end
|
||||
def active_flags
|
||||
@active_flags ||= (@topic_view&.all_active_flags || {})[object.id]
|
||||
end
|
||||
|
||||
def post_custom_fields
|
||||
@post_custom_fields ||= if @topic_view
|
||||
(@topic_view.post_custom_fields || {})[object.id] || {}
|
||||
else
|
||||
object.custom_fields
|
||||
end
|
||||
def post_custom_fields
|
||||
@post_custom_fields ||= if @topic_view
|
||||
(@topic_view.post_custom_fields || {})[object.id] || {}
|
||||
else
|
||||
object.custom_fields
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -2,9 +2,9 @@ class TopicFlagTypeSerializer < PostActionTypeSerializer
|
|||
|
||||
protected
|
||||
|
||||
def i18n(field, vars = nil)
|
||||
key = "topic_flag_types.#{name_key}.#{field}"
|
||||
vars ? I18n.t(key, vars) : I18n.t(key)
|
||||
end
|
||||
def i18n(field, vars = nil)
|
||||
key = "topic_flag_types.#{name_key}.#{field}"
|
||||
vars ? I18n.t(key, vars) : I18n.t(key)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -301,8 +301,8 @@ class TopicViewSerializer < ApplicationSerializer
|
|||
|
||||
private
|
||||
|
||||
def private_message?(topic)
|
||||
@private_message ||= topic.private_message?
|
||||
end
|
||||
def private_message?(topic)
|
||||
@private_message ||= topic.private_message?
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -58,21 +58,21 @@ class GroupActionLogger
|
|||
|
||||
private
|
||||
|
||||
def excluded_attributes
|
||||
[
|
||||
:bio_cooked,
|
||||
:updated_at,
|
||||
:created_at,
|
||||
:user_count
|
||||
]
|
||||
end
|
||||
def excluded_attributes
|
||||
[
|
||||
:bio_cooked,
|
||||
:updated_at,
|
||||
:created_at,
|
||||
:user_count
|
||||
]
|
||||
end
|
||||
|
||||
def default_params
|
||||
{ group: @group, acting_user: @acting_user }
|
||||
end
|
||||
def default_params
|
||||
{ group: @group, acting_user: @acting_user }
|
||||
end
|
||||
|
||||
def can_edit?
|
||||
raise Discourse::InvalidParameters.new unless Guardian.new(@acting_user).can_log_group_changes?(@group)
|
||||
end
|
||||
def can_edit?
|
||||
raise Discourse::InvalidParameters.new unless Guardian.new(@acting_user).can_log_group_changes?(@group)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -550,13 +550,13 @@ class StaffActionLogger
|
|||
|
||||
private
|
||||
|
||||
def params(opts = nil)
|
||||
opts ||= {}
|
||||
{ acting_user_id: @admin.id, context: opts[:context] }
|
||||
end
|
||||
def params(opts = nil)
|
||||
opts ||= {}
|
||||
{ acting_user_id: @admin.id, context: opts[:context] }
|
||||
end
|
||||
|
||||
def validate_category(category)
|
||||
raise Discourse::InvalidParameters.new(:category) unless category && category.is_a?(Category)
|
||||
end
|
||||
def validate_category(category)
|
||||
raise Discourse::InvalidParameters.new(:category) unless category && category.is_a?(Category)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -77,13 +77,13 @@ class UserAnonymizer
|
|||
|
||||
private
|
||||
|
||||
def make_anon_username
|
||||
100.times do
|
||||
new_username = "anon#{(SecureRandom.random_number * 100000000).to_i}"
|
||||
return new_username unless User.where(username_lower: new_username).exists?
|
||||
end
|
||||
raise "Failed to generate an anon username"
|
||||
def make_anon_username
|
||||
100.times do
|
||||
new_username = "anon#{(SecureRandom.random_number * 100000000).to_i}"
|
||||
return new_username unless User.where(username_lower: new_username).exists?
|
||||
end
|
||||
raise "Failed to generate an anon username"
|
||||
end
|
||||
|
||||
def ip_where(column = 'user_id')
|
||||
["#{column} = :user_id AND ip_address IS NOT NULL", user_id: @user.id]
|
||||
|
|
|
@ -63,58 +63,58 @@ class Auth::FacebookAuthenticator < Auth::Authenticator
|
|||
|
||||
protected
|
||||
|
||||
def parse_auth_token(auth_token)
|
||||
raw_info = auth_token["extra"]["raw_info"]
|
||||
info = auth_token["info"]
|
||||
def parse_auth_token(auth_token)
|
||||
raw_info = auth_token["extra"]["raw_info"]
|
||||
info = auth_token["info"]
|
||||
|
||||
email = auth_token["info"][:email]
|
||||
email = auth_token["info"][:email]
|
||||
|
||||
website = (info["urls"] && info["urls"]["Website"]) || nil
|
||||
website = (info["urls"] && info["urls"]["Website"]) || nil
|
||||
|
||||
{
|
||||
facebook: {
|
||||
facebook_user_id: auth_token["uid"],
|
||||
link: raw_info["link"],
|
||||
username: raw_info["username"],
|
||||
first_name: raw_info["first_name"],
|
||||
last_name: raw_info["last_name"],
|
||||
email: email,
|
||||
gender: raw_info["gender"],
|
||||
name: raw_info["name"],
|
||||
avatar_url: info["image"],
|
||||
location: info["location"],
|
||||
website: website,
|
||||
about_me: info["description"]
|
||||
},
|
||||
{
|
||||
facebook: {
|
||||
facebook_user_id: auth_token["uid"],
|
||||
link: raw_info["link"],
|
||||
username: raw_info["username"],
|
||||
first_name: raw_info["first_name"],
|
||||
last_name: raw_info["last_name"],
|
||||
email: email,
|
||||
email_valid: true
|
||||
}
|
||||
gender: raw_info["gender"],
|
||||
name: raw_info["name"],
|
||||
avatar_url: info["image"],
|
||||
location: info["location"],
|
||||
website: website,
|
||||
about_me: info["description"]
|
||||
},
|
||||
email: email,
|
||||
email_valid: true
|
||||
}
|
||||
end
|
||||
|
||||
def retrieve_avatar(user, data)
|
||||
return unless user
|
||||
return if user.user_avatar.try(:custom_upload_id).present?
|
||||
|
||||
if (avatar_url = data[:avatar_url]).present?
|
||||
url = "#{avatar_url}?height=#{AVATAR_SIZE}&width=#{AVATAR_SIZE}"
|
||||
Jobs.enqueue(:download_avatar_from_url, url: url, user_id: user.id, override_gravatar: false)
|
||||
end
|
||||
end
|
||||
|
||||
def retrieve_avatar(user, data)
|
||||
return unless user
|
||||
return if user.user_avatar.try(:custom_upload_id).present?
|
||||
def retrieve_profile(user, data)
|
||||
return unless user
|
||||
|
||||
if (avatar_url = data[:avatar_url]).present?
|
||||
url = "#{avatar_url}?height=#{AVATAR_SIZE}&width=#{AVATAR_SIZE}"
|
||||
Jobs.enqueue(:download_avatar_from_url, url: url, user_id: user.id, override_gravatar: false)
|
||||
end
|
||||
end
|
||||
|
||||
def retrieve_profile(user, data)
|
||||
return unless user
|
||||
|
||||
bio = data[:about_me] || data[:about]
|
||||
location = data[:location]
|
||||
website = data[:website]
|
||||
|
||||
if bio || location || website
|
||||
profile = user.user_profile
|
||||
profile.bio_raw = bio unless profile.bio_raw.present?
|
||||
profile.location = location unless profile.location.present?
|
||||
profile.website = website unless profile.website.present?
|
||||
profile.save
|
||||
end
|
||||
bio = data[:about_me] || data[:about]
|
||||
location = data[:location]
|
||||
website = data[:website]
|
||||
|
||||
if bio || location || website
|
||||
profile = user.user_profile
|
||||
profile.bio_raw = bio unless profile.bio_raw.present?
|
||||
profile.location = location unless profile.location.present?
|
||||
profile.website = website unless profile.website.present?
|
||||
profile.save
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -69,28 +69,28 @@ class Auth::TwitterAuthenticator < Auth::Authenticator
|
|||
|
||||
protected
|
||||
|
||||
def retrieve_avatar(user, data)
|
||||
return unless user
|
||||
return if user.user_avatar.try(:custom_upload_id).present?
|
||||
def retrieve_avatar(user, data)
|
||||
return unless user
|
||||
return if user.user_avatar.try(:custom_upload_id).present?
|
||||
|
||||
if (avatar_url = data[:twitter_image]).present?
|
||||
url = avatar_url.sub("_normal", "")
|
||||
Jobs.enqueue(:download_avatar_from_url, url: url, user_id: user.id, override_gravatar: false)
|
||||
end
|
||||
if (avatar_url = data[:twitter_image]).present?
|
||||
url = avatar_url.sub("_normal", "")
|
||||
Jobs.enqueue(:download_avatar_from_url, url: url, user_id: user.id, override_gravatar: false)
|
||||
end
|
||||
end
|
||||
|
||||
def retrieve_profile(user, data)
|
||||
return unless user
|
||||
def retrieve_profile(user, data)
|
||||
return unless user
|
||||
|
||||
bio = data[:twitter_description]
|
||||
location = data[:twitter_location]
|
||||
bio = data[:twitter_description]
|
||||
location = data[:twitter_location]
|
||||
|
||||
if bio || location
|
||||
profile = user.user_profile
|
||||
profile.bio_raw = bio unless profile.bio_raw.present?
|
||||
profile.location = location unless profile.location.present?
|
||||
profile.save
|
||||
end
|
||||
if bio || location
|
||||
profile = user.user_profile
|
||||
profile.bio_raw = bio unless profile.bio_raw.present?
|
||||
profile.location = location unless profile.location.present?
|
||||
profile.save
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -23,32 +23,32 @@ class CommonPasswords
|
|||
|
||||
private
|
||||
|
||||
class RedisPasswordList
|
||||
def include?(password)
|
||||
CommonPasswords.redis.sismember CommonPasswords::LIST_KEY, password
|
||||
end
|
||||
class RedisPasswordList
|
||||
def include?(password)
|
||||
CommonPasswords.redis.sismember CommonPasswords::LIST_KEY, password
|
||||
end
|
||||
end
|
||||
|
||||
def self.password_list
|
||||
@mutex.synchronize do
|
||||
load_passwords unless redis.scard(LIST_KEY) > 0
|
||||
end
|
||||
RedisPasswordList.new
|
||||
def self.password_list
|
||||
@mutex.synchronize do
|
||||
load_passwords unless redis.scard(LIST_KEY) > 0
|
||||
end
|
||||
RedisPasswordList.new
|
||||
end
|
||||
|
||||
def self.redis
|
||||
$redis.without_namespace
|
||||
end
|
||||
def self.redis
|
||||
$redis.without_namespace
|
||||
end
|
||||
|
||||
def self.load_passwords
|
||||
passwords = File.readlines(PASSWORD_FILE)
|
||||
passwords.map!(&:chomp).each do |pwd|
|
||||
# slower, but a tad more compatible
|
||||
redis.sadd LIST_KEY, pwd
|
||||
end
|
||||
rescue Errno::ENOENT
|
||||
# tolerate this so we don't block signups
|
||||
Rails.logger.error "Common passwords file #{PASSWORD_FILE} is not found! Common password checking is skipped."
|
||||
def self.load_passwords
|
||||
passwords = File.readlines(PASSWORD_FILE)
|
||||
passwords.map!(&:chomp).each do |pwd|
|
||||
# slower, but a tad more compatible
|
||||
redis.sadd LIST_KEY, pwd
|
||||
end
|
||||
rescue Errno::ENOENT
|
||||
# tolerate this so we don't block signups
|
||||
Rails.logger.error "Common passwords file #{PASSWORD_FILE} is not found! Common password checking is skipped."
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -193,20 +193,20 @@ class ComposerMessagesFinder
|
|||
|
||||
private
|
||||
|
||||
def educate_reply?(type)
|
||||
replying? &&
|
||||
@details[:topic_id] &&
|
||||
(@topic.present? && !@topic.private_message?) &&
|
||||
(@user.post_count >= SiteSetting.educate_until_posts) &&
|
||||
!UserHistory.exists_for_user?(@user, type, topic_id: @details[:topic_id])
|
||||
end
|
||||
def educate_reply?(type)
|
||||
replying? &&
|
||||
@details[:topic_id] &&
|
||||
(@topic.present? && !@topic.private_message?) &&
|
||||
(@user.post_count >= SiteSetting.educate_until_posts) &&
|
||||
!UserHistory.exists_for_user?(@user, type, topic_id: @details[:topic_id])
|
||||
end
|
||||
|
||||
def creating_topic?
|
||||
@details[:composer_action] == "createTopic"
|
||||
end
|
||||
def creating_topic?
|
||||
@details[:composer_action] == "createTopic"
|
||||
end
|
||||
|
||||
def replying?
|
||||
@details[:composer_action] == "reply"
|
||||
end
|
||||
def replying?
|
||||
@details[:composer_action] == "reply"
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -104,19 +104,19 @@ class EmailUpdater
|
|||
|
||||
protected
|
||||
|
||||
def notify_old(old_email, new_email)
|
||||
Jobs.enqueue :critical_user_email,
|
||||
to_address: old_email,
|
||||
type: :notify_old_email,
|
||||
user_id: @user.id
|
||||
end
|
||||
def notify_old(old_email, new_email)
|
||||
Jobs.enqueue :critical_user_email,
|
||||
to_address: old_email,
|
||||
type: :notify_old_email,
|
||||
user_id: @user.id
|
||||
end
|
||||
|
||||
def send_email(type, email_token)
|
||||
Jobs.enqueue :critical_user_email,
|
||||
to_address: email_token.email,
|
||||
type: type,
|
||||
user_id: @user.id,
|
||||
email_token: email_token.token
|
||||
end
|
||||
def send_email(type, email_token)
|
||||
Jobs.enqueue :critical_user_email,
|
||||
to_address: email_token.email,
|
||||
type: type,
|
||||
user_id: @user.id,
|
||||
email_token: email_token.token
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -96,12 +96,12 @@ class FileHelper
|
|||
|
||||
private
|
||||
|
||||
def self.images
|
||||
@@images ||= Set.new %w{jpg jpeg png gif tif tiff bmp svg webp ico}
|
||||
end
|
||||
def self.images
|
||||
@@images ||= Set.new %w{jpg jpeg png gif tif tiff bmp svg webp ico}
|
||||
end
|
||||
|
||||
def self.images_regexp
|
||||
@@images_regexp ||= /\.(#{images.to_a.join("|")})$/i
|
||||
end
|
||||
def self.images_regexp
|
||||
@@images_regexp ||= /\.(#{images.to_a.join("|")})$/i
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -224,14 +224,14 @@ module FlagQuery
|
|||
|
||||
private
|
||||
|
||||
def self.excerpt(cooked)
|
||||
excerpt = Post.excerpt(cooked, 200, keep_emoji_images: true)
|
||||
# remove the first link if it's the first node
|
||||
fragment = Nokogiri::HTML.fragment(excerpt)
|
||||
if fragment.children.first == fragment.css("a:first").first && fragment.children.first
|
||||
fragment.children.first.remove
|
||||
end
|
||||
fragment.to_html.strip
|
||||
def self.excerpt(cooked)
|
||||
excerpt = Post.excerpt(cooked, 200, keep_emoji_images: true)
|
||||
# remove the first link if it's the first node
|
||||
fragment = Nokogiri::HTML.fragment(excerpt)
|
||||
if fragment.children.first == fragment.css("a:first").first && fragment.children.first
|
||||
fragment.children.first.remove
|
||||
end
|
||||
fragment.to_html.strip
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -3,40 +3,40 @@
|
|||
class Rack::ETag
|
||||
private
|
||||
|
||||
def digest_body(body)
|
||||
parts = []
|
||||
has_body = false
|
||||
def digest_body(body)
|
||||
parts = []
|
||||
has_body = false
|
||||
|
||||
body.each do |part|
|
||||
parts << part
|
||||
has_body ||= part.length > 0
|
||||
end
|
||||
body.each do |part|
|
||||
parts << part
|
||||
has_body ||= part.length > 0
|
||||
end
|
||||
|
||||
hexdigest =
|
||||
if has_body
|
||||
digest = Digest::MD5.new
|
||||
parts.each { |part| digest << part }
|
||||
digest.hexdigest
|
||||
end
|
||||
hexdigest =
|
||||
if has_body
|
||||
digest = Digest::MD5.new
|
||||
parts.each { |part| digest << part }
|
||||
digest.hexdigest
|
||||
end
|
||||
|
||||
[hexdigest, parts]
|
||||
end
|
||||
[hexdigest, parts]
|
||||
end
|
||||
end
|
||||
|
||||
# patch https://github.com/rack/rack/pull/596
|
||||
#
|
||||
class Rack::ConditionalGet
|
||||
private
|
||||
def to_rfc2822(since)
|
||||
# shortest possible valid date is the obsolete: 1 Nov 97 09:55 A
|
||||
# anything shorter is invalid, this avoids exceptions for common cases
|
||||
# most common being the empty string
|
||||
if since && since.length >= 16
|
||||
# NOTE: there is no trivial way to write this in a non execption way
|
||||
# _rfc2822 returns a hash but is not that usable
|
||||
Time.rfc2822(since) rescue nil
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
def to_rfc2822(since)
|
||||
# shortest possible valid date is the obsolete: 1 Nov 97 09:55 A
|
||||
# anything shorter is invalid, this avoids exceptions for common cases
|
||||
# most common being the empty string
|
||||
if since && since.length >= 16
|
||||
# NOTE: there is no trivial way to write this in a non execption way
|
||||
# _rfc2822 returns a hash but is not that usable
|
||||
Time.rfc2822(since) rescue nil
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,54 +52,54 @@ module I18n
|
|||
|
||||
protected
|
||||
|
||||
def find_results(regexp, results, translations, path = nil)
|
||||
return results if translations.blank?
|
||||
def find_results(regexp, results, translations, path = nil)
|
||||
return results if translations.blank?
|
||||
|
||||
translations.each do |k_sym, v|
|
||||
k = k_sym.to_s
|
||||
key_path = path ? "#{path}.#{k}" : k
|
||||
if v.is_a?(String)
|
||||
unless results.has_key?(key_path)
|
||||
results[key_path] = v if key_path =~ regexp || v =~ regexp
|
||||
end
|
||||
elsif v.is_a?(Hash)
|
||||
find_results(regexp, results, v, key_path)
|
||||
translations.each do |k_sym, v|
|
||||
k = k_sym.to_s
|
||||
key_path = path ? "#{path}.#{k}" : k
|
||||
if v.is_a?(String)
|
||||
unless results.has_key?(key_path)
|
||||
results[key_path] = v if key_path =~ regexp || v =~ regexp
|
||||
end
|
||||
elsif v.is_a?(Hash)
|
||||
find_results(regexp, results, v, key_path)
|
||||
end
|
||||
results
|
||||
end
|
||||
results
|
||||
end
|
||||
|
||||
# Support interpolation and pluralization of overrides by first looking up
|
||||
# the original translations before applying our overrides.
|
||||
def lookup(locale, key, scope = [], options = {})
|
||||
existing_translations = super(locale, key, scope, options)
|
||||
return existing_translations if scope.is_a?(Array) && scope.include?(:models)
|
||||
# Support interpolation and pluralization of overrides by first looking up
|
||||
# the original translations before applying our overrides.
|
||||
def lookup(locale, key, scope = [], options = {})
|
||||
existing_translations = super(locale, key, scope, options)
|
||||
return existing_translations if scope.is_a?(Array) && scope.include?(:models)
|
||||
|
||||
overrides = options.dig(:overrides, locale)
|
||||
overrides = options.dig(:overrides, locale)
|
||||
|
||||
if overrides
|
||||
if existing_translations && options[:count]
|
||||
remapped_translations =
|
||||
if existing_translations.is_a?(Hash)
|
||||
Hash[existing_translations.map { |k, v| ["#{key}.#{k}", v] }]
|
||||
elsif existing_translations.is_a?(String)
|
||||
Hash[[[key, existing_translations]]]
|
||||
end
|
||||
|
||||
result = {}
|
||||
|
||||
remapped_translations.merge(overrides).each do |k, v|
|
||||
result[k.split('.').last.to_sym] = v if k != key && k.start_with?(key.to_s)
|
||||
if overrides
|
||||
if existing_translations && options[:count]
|
||||
remapped_translations =
|
||||
if existing_translations.is_a?(Hash)
|
||||
Hash[existing_translations.map { |k, v| ["#{key}.#{k}", v] }]
|
||||
elsif existing_translations.is_a?(String)
|
||||
Hash[[[key, existing_translations]]]
|
||||
end
|
||||
return result if result.size > 0
|
||||
end
|
||||
|
||||
return overrides[key] if overrides[key]
|
||||
result = {}
|
||||
|
||||
remapped_translations.merge(overrides).each do |k, v|
|
||||
result[k.split('.').last.to_sym] = v if k != key && k.start_with?(key.to_s)
|
||||
end
|
||||
return result if result.size > 0
|
||||
end
|
||||
|
||||
existing_translations
|
||||
return overrides[key] if overrides[key]
|
||||
end
|
||||
|
||||
existing_translations
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,8 +10,8 @@ class DuplicateKeyFinder < LocaleFileWalker
|
|||
|
||||
protected
|
||||
|
||||
def handle_scalar(node, depth, parents)
|
||||
super
|
||||
@keys_with_count[parents.join('.')] += 1
|
||||
end
|
||||
def handle_scalar(node, depth, parents)
|
||||
super
|
||||
@keys_with_count[parents.join('.')] += 1
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,20 +63,20 @@ class InlineOneboxer
|
|||
|
||||
private
|
||||
|
||||
def self.onebox_for(url, title, opts)
|
||||
onebox = {
|
||||
url: url,
|
||||
title: title && Emoji.gsub_emoji_to_unicode(title)
|
||||
}
|
||||
unless opts[:skip_cache]
|
||||
Rails.cache.write(cache_key(url), onebox, expires_in: 1.day)
|
||||
end
|
||||
|
||||
onebox
|
||||
def self.onebox_for(url, title, opts)
|
||||
onebox = {
|
||||
url: url,
|
||||
title: title && Emoji.gsub_emoji_to_unicode(title)
|
||||
}
|
||||
unless opts[:skip_cache]
|
||||
Rails.cache.write(cache_key(url), onebox, expires_in: 1.day)
|
||||
end
|
||||
|
||||
def self.cache_key(url)
|
||||
"inline_onebox:#{url}"
|
||||
end
|
||||
onebox
|
||||
end
|
||||
|
||||
def self.cache_key(url)
|
||||
"inline_onebox:#{url}"
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -16,22 +16,22 @@ module Onebox
|
|||
|
||||
private
|
||||
|
||||
# overwrite to whitelist iframes
|
||||
def is_embedded?
|
||||
return false unless data[:html] && data[:height]
|
||||
return true if WhitelistedGenericOnebox.html_providers.include?(data[:provider_name])
|
||||
# overwrite to whitelist iframes
|
||||
def is_embedded?
|
||||
return false unless data[:html] && data[:height]
|
||||
return true if WhitelistedGenericOnebox.html_providers.include?(data[:provider_name])
|
||||
|
||||
if data[:html]["iframe"]
|
||||
fragment = Nokogiri::HTML::fragment(data[:html])
|
||||
if iframe = fragment.at_css("iframe")
|
||||
src = iframe["src"]
|
||||
return src.present? && SiteSetting.allowed_iframes.split("|").any? { |url| src.start_with?(url) }
|
||||
end
|
||||
if data[:html]["iframe"]
|
||||
fragment = Nokogiri::HTML::fragment(data[:html])
|
||||
if iframe = fragment.at_css("iframe")
|
||||
src = iframe["src"]
|
||||
return src.present? && SiteSetting.allowed_iframes.split("|").any? { |url| src.start_with?(url) }
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
258
lib/oneboxer.rb
258
lib/oneboxer.rb
|
@ -125,151 +125,151 @@ module Oneboxer
|
|||
|
||||
private
|
||||
|
||||
def self.preview_key(user_id)
|
||||
"onebox:preview:#{user_id}"
|
||||
end
|
||||
def self.preview_key(user_id)
|
||||
"onebox:preview:#{user_id}"
|
||||
end
|
||||
|
||||
def self.blank_onebox
|
||||
{ preview: "", onebox: "" }
|
||||
end
|
||||
def self.blank_onebox
|
||||
{ preview: "", onebox: "" }
|
||||
end
|
||||
|
||||
def self.onebox_cache_key(url)
|
||||
"onebox__#{url}"
|
||||
end
|
||||
def self.onebox_cache_key(url)
|
||||
"onebox__#{url}"
|
||||
end
|
||||
|
||||
def self.onebox_raw(url, opts = {})
|
||||
url = URI(url).to_s
|
||||
local_onebox(url, opts) || external_onebox(url)
|
||||
rescue => e
|
||||
# no point warning here, just cause we have an issue oneboxing a url
|
||||
# we can later hunt for failed oneboxes by searching logs if needed
|
||||
Rails.logger.info("Failed to onebox #{url} #{e} #{e.backtrace}")
|
||||
# return a blank hash, so rest of the code works
|
||||
blank_onebox
|
||||
end
|
||||
def self.onebox_raw(url, opts = {})
|
||||
url = URI(url).to_s
|
||||
local_onebox(url, opts) || external_onebox(url)
|
||||
rescue => e
|
||||
# no point warning here, just cause we have an issue oneboxing a url
|
||||
# we can later hunt for failed oneboxes by searching logs if needed
|
||||
Rails.logger.info("Failed to onebox #{url} #{e} #{e.backtrace}")
|
||||
# return a blank hash, so rest of the code works
|
||||
blank_onebox
|
||||
end
|
||||
|
||||
def self.local_onebox(url, opts = {})
|
||||
return unless route = Discourse.route_for(url)
|
||||
def self.local_onebox(url, opts = {})
|
||||
return unless route = Discourse.route_for(url)
|
||||
|
||||
html =
|
||||
case route[:controller]
|
||||
when "uploads" then local_upload_html(url)
|
||||
when "topics" then local_topic_html(url, route, opts)
|
||||
when "users" then local_user_html(url, route)
|
||||
end
|
||||
|
||||
html = html.presence || "<a href='#{url}'>#{url}</a>"
|
||||
{ onebox: html, preview: html }
|
||||
end
|
||||
|
||||
def self.local_upload_html(url)
|
||||
case File.extname(URI(url).path || "")
|
||||
when /^\.(mov|mp4|webm|ogv)$/i
|
||||
"<video width='100%' height='100%' controls><source src='#{url}'><a href='#{url}'>#{url}</a></video>"
|
||||
when /^\.(mp3|ogg|wav|m4a)$/i
|
||||
"<audio controls><source src='#{url}'><a href='#{url}'>#{url}</a></audio>"
|
||||
end
|
||||
end
|
||||
|
||||
def self.local_topic_html(url, route, opts)
|
||||
return unless current_user = User.find_by(id: opts[:user_id])
|
||||
|
||||
if current_category = Category.find_by(id: opts[:category_id])
|
||||
return unless Guardian.new(current_user).can_see_category?(current_category)
|
||||
html =
|
||||
case route[:controller]
|
||||
when "uploads" then local_upload_html(url)
|
||||
when "topics" then local_topic_html(url, route, opts)
|
||||
when "users" then local_user_html(url, route)
|
||||
end
|
||||
|
||||
if current_topic = Topic.find_by(id: opts[:topic_id])
|
||||
return unless Guardian.new(current_user).can_see_topic?(current_topic)
|
||||
end
|
||||
html = html.presence || "<a href='#{url}'>#{url}</a>"
|
||||
{ onebox: html, preview: html }
|
||||
end
|
||||
|
||||
topic = Topic.find_by(id: route[:topic_id])
|
||||
def self.local_upload_html(url)
|
||||
case File.extname(URI(url).path || "")
|
||||
when /^\.(mov|mp4|webm|ogv)$/i
|
||||
"<video width='100%' height='100%' controls><source src='#{url}'><a href='#{url}'>#{url}</a></video>"
|
||||
when /^\.(mp3|ogg|wav|m4a)$/i
|
||||
"<audio controls><source src='#{url}'><a href='#{url}'>#{url}</a></audio>"
|
||||
end
|
||||
end
|
||||
|
||||
return unless topic
|
||||
return if topic.private_message?
|
||||
def self.local_topic_html(url, route, opts)
|
||||
return unless current_user = User.find_by(id: opts[:user_id])
|
||||
|
||||
if current_category&.id != topic.category_id
|
||||
return unless Guardian.new.can_see_topic?(topic)
|
||||
end
|
||||
|
||||
post_number = route[:post_number].to_i
|
||||
|
||||
post = post_number > 1 ?
|
||||
topic.posts.where(post_number: post_number).first :
|
||||
topic.ordered_posts.first
|
||||
|
||||
return if !post || post.hidden || post.post_type != Post.types[:regular]
|
||||
|
||||
if post_number > 1 && current_topic&.id == topic.id
|
||||
excerpt = post.excerpt(SiteSetting.post_onebox_maxlength)
|
||||
excerpt.gsub!(/[\r\n]+/, " ")
|
||||
excerpt.gsub!("[/quote]", "[quote]") # don't break my quote
|
||||
|
||||
quote = "[quote=\"#{post.user.username}, topic:#{topic.id}, post:#{post.post_number}\"]\n#{excerpt}\n[/quote]"
|
||||
|
||||
PrettyText.cook(quote)
|
||||
else
|
||||
args = {
|
||||
topic_id: topic.id,
|
||||
post_number: post.post_number,
|
||||
avatar: PrettyText.avatar_img(post.user.avatar_template, "tiny"),
|
||||
original_url: url,
|
||||
title: PrettyText.unescape_emoji(CGI::escapeHTML(topic.title)),
|
||||
category_html: CategoryBadge.html_for(topic.category),
|
||||
quote: PrettyText.unescape_emoji(post.excerpt(SiteSetting.post_onebox_maxlength)),
|
||||
}
|
||||
|
||||
template = File.read("#{Rails.root}/lib/onebox/templates/discourse_topic_onebox.hbs")
|
||||
Mustache.render(template, args)
|
||||
end
|
||||
if current_category = Category.find_by(id: opts[:category_id])
|
||||
return unless Guardian.new(current_user).can_see_category?(current_category)
|
||||
end
|
||||
|
||||
def self.local_user_html(url, route)
|
||||
username = route[:username] || ""
|
||||
|
||||
if user = User.find_by(username_lower: username.downcase)
|
||||
args = {
|
||||
user_id: user.id,
|
||||
username: user.username,
|
||||
avatar: PrettyText.avatar_img(user.avatar_template, "extra_large"),
|
||||
name: user.name,
|
||||
bio: user.user_profile.bio_excerpt(230),
|
||||
location: user.user_profile.location,
|
||||
joined: I18n.t('joined'),
|
||||
created_at: user.created_at.strftime(I18n.t('datetime_formats.formats.date_only')),
|
||||
website: user.user_profile.website,
|
||||
website_name: UserSerializer.new(user).website_name,
|
||||
original_url: url
|
||||
}
|
||||
|
||||
template = File.read("#{Rails.root}/lib/onebox/templates/discourse_user_onebox.hbs")
|
||||
Mustache.render(template, args)
|
||||
else
|
||||
nil
|
||||
end
|
||||
if current_topic = Topic.find_by(id: opts[:topic_id])
|
||||
return unless Guardian.new(current_user).can_see_topic?(current_topic)
|
||||
end
|
||||
|
||||
def self.external_onebox(url)
|
||||
Rails.cache.fetch(onebox_cache_key(url), expires_in: 1.day) do
|
||||
fd = FinalDestination.new(url, ignore_redirects: ignore_redirects, force_get_hosts: force_get_hosts)
|
||||
uri = fd.resolve
|
||||
return blank_onebox if uri.blank? || SiteSetting.onebox_domains_blacklist.include?(uri.hostname)
|
||||
topic = Topic.find_by(id: route[:topic_id])
|
||||
|
||||
options = {
|
||||
cache: {},
|
||||
max_width: 695,
|
||||
sanitize_config: Sanitize::Config::DISCOURSE_ONEBOX
|
||||
}
|
||||
return unless topic
|
||||
return if topic.private_message?
|
||||
|
||||
options[:cookie] = fd.cookie if fd.cookie
|
||||
|
||||
if Rails.env.development? && SiteSetting.port.to_i > 0
|
||||
Onebox.options = { allowed_ports: [80, 443, SiteSetting.port.to_i] }
|
||||
end
|
||||
|
||||
r = Onebox.preview(uri.to_s, options)
|
||||
|
||||
{ onebox: r.to_s, preview: r&.placeholder_html.to_s }
|
||||
end
|
||||
if current_category&.id != topic.category_id
|
||||
return unless Guardian.new.can_see_topic?(topic)
|
||||
end
|
||||
|
||||
post_number = route[:post_number].to_i
|
||||
|
||||
post = post_number > 1 ?
|
||||
topic.posts.where(post_number: post_number).first :
|
||||
topic.ordered_posts.first
|
||||
|
||||
return if !post || post.hidden || post.post_type != Post.types[:regular]
|
||||
|
||||
if post_number > 1 && current_topic&.id == topic.id
|
||||
excerpt = post.excerpt(SiteSetting.post_onebox_maxlength)
|
||||
excerpt.gsub!(/[\r\n]+/, " ")
|
||||
excerpt.gsub!("[/quote]", "[quote]") # don't break my quote
|
||||
|
||||
quote = "[quote=\"#{post.user.username}, topic:#{topic.id}, post:#{post.post_number}\"]\n#{excerpt}\n[/quote]"
|
||||
|
||||
PrettyText.cook(quote)
|
||||
else
|
||||
args = {
|
||||
topic_id: topic.id,
|
||||
post_number: post.post_number,
|
||||
avatar: PrettyText.avatar_img(post.user.avatar_template, "tiny"),
|
||||
original_url: url,
|
||||
title: PrettyText.unescape_emoji(CGI::escapeHTML(topic.title)),
|
||||
category_html: CategoryBadge.html_for(topic.category),
|
||||
quote: PrettyText.unescape_emoji(post.excerpt(SiteSetting.post_onebox_maxlength)),
|
||||
}
|
||||
|
||||
template = File.read("#{Rails.root}/lib/onebox/templates/discourse_topic_onebox.hbs")
|
||||
Mustache.render(template, args)
|
||||
end
|
||||
end
|
||||
|
||||
def self.local_user_html(url, route)
|
||||
username = route[:username] || ""
|
||||
|
||||
if user = User.find_by(username_lower: username.downcase)
|
||||
args = {
|
||||
user_id: user.id,
|
||||
username: user.username,
|
||||
avatar: PrettyText.avatar_img(user.avatar_template, "extra_large"),
|
||||
name: user.name,
|
||||
bio: user.user_profile.bio_excerpt(230),
|
||||
location: user.user_profile.location,
|
||||
joined: I18n.t('joined'),
|
||||
created_at: user.created_at.strftime(I18n.t('datetime_formats.formats.date_only')),
|
||||
website: user.user_profile.website,
|
||||
website_name: UserSerializer.new(user).website_name,
|
||||
original_url: url
|
||||
}
|
||||
|
||||
template = File.read("#{Rails.root}/lib/onebox/templates/discourse_user_onebox.hbs")
|
||||
Mustache.render(template, args)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def self.external_onebox(url)
|
||||
Rails.cache.fetch(onebox_cache_key(url), expires_in: 1.day) do
|
||||
fd = FinalDestination.new(url, ignore_redirects: ignore_redirects, force_get_hosts: force_get_hosts)
|
||||
uri = fd.resolve
|
||||
return blank_onebox if uri.blank? || SiteSetting.onebox_domains_blacklist.include?(uri.hostname)
|
||||
|
||||
options = {
|
||||
cache: {},
|
||||
max_width: 695,
|
||||
sanitize_config: Sanitize::Config::DISCOURSE_ONEBOX
|
||||
}
|
||||
|
||||
options[:cookie] = fd.cookie if fd.cookie
|
||||
|
||||
if Rails.env.development? && SiteSetting.port.to_i > 0
|
||||
Onebox.options = { allowed_ports: [80, 443, SiteSetting.port.to_i] }
|
||||
end
|
||||
|
||||
r = Onebox.preview(uri.to_s, options)
|
||||
|
||||
{ onebox: r.to_s, preview: r&.placeholder_html.to_s }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -37,36 +37,36 @@ module RetrieveTitle
|
|||
|
||||
private
|
||||
|
||||
def self.max_chunk_size(uri)
|
||||
def self.max_chunk_size(uri)
|
||||
|
||||
# Amazon and YouTube leave the title until very late. Exceptions are bad
|
||||
# but these are large sites.
|
||||
return 500 if uri.host =~ /amazon\.(com|ca|co\.uk|es|fr|de|it|com\.au|com\.br|cn|in|co\.jp|com\.mx)$/
|
||||
return 300 if uri.host =~ /youtube\.com$/ || uri.host =~ /youtu.be/
|
||||
# Amazon and YouTube leave the title until very late. Exceptions are bad
|
||||
# but these are large sites.
|
||||
return 500 if uri.host =~ /amazon\.(com|ca|co\.uk|es|fr|de|it|com\.au|com\.br|cn|in|co\.jp|com\.mx)$/
|
||||
return 300 if uri.host =~ /youtube\.com$/ || uri.host =~ /youtu.be/
|
||||
|
||||
# default is 10k
|
||||
10
|
||||
end
|
||||
# default is 10k
|
||||
10
|
||||
end
|
||||
|
||||
# Fetch the beginning of a HTML document at a url
|
||||
def self.fetch_title(url)
|
||||
fd = FinalDestination.new(url, timeout: CRAWL_TIMEOUT)
|
||||
# Fetch the beginning of a HTML document at a url
|
||||
def self.fetch_title(url)
|
||||
fd = FinalDestination.new(url, timeout: CRAWL_TIMEOUT)
|
||||
|
||||
current = nil
|
||||
title = nil
|
||||
current = nil
|
||||
title = nil
|
||||
|
||||
fd.get do |_response, chunk, uri|
|
||||
fd.get do |_response, chunk, uri|
|
||||
|
||||
if current
|
||||
current << chunk
|
||||
else
|
||||
current = chunk
|
||||
end
|
||||
|
||||
max_size = max_chunk_size(uri) * 1024
|
||||
title = extract_title(current)
|
||||
throw :done if title || max_size < current.length
|
||||
if current
|
||||
current << chunk
|
||||
else
|
||||
current = chunk
|
||||
end
|
||||
return title
|
||||
|
||||
max_size = max_chunk_size(uri) * 1024
|
||||
title = extract_title(current)
|
||||
throw :done if title || max_size < current.length
|
||||
end
|
||||
return title
|
||||
end
|
||||
end
|
||||
|
|
730
lib/search.rb
730
lib/search.rb
|
@ -503,399 +503,399 @@ class Search
|
|||
|
||||
private
|
||||
|
||||
def process_advanced_search!(term)
|
||||
def process_advanced_search!(term)
|
||||
|
||||
term.to_s.scan(/(([^" \t\n\x0B\f\r]+)?(("[^"]+")?))/).to_a.map do |(word, _)|
|
||||
next if word.blank?
|
||||
term.to_s.scan(/(([^" \t\n\x0B\f\r]+)?(("[^"]+")?))/).to_a.map do |(word, _)|
|
||||
next if word.blank?
|
||||
|
||||
found = false
|
||||
found = false
|
||||
|
||||
Search.advanced_filters.each do |matcher, block|
|
||||
cleaned = word.gsub(/["']/, "")
|
||||
if cleaned =~ matcher
|
||||
(@filters ||= []) << [block, $1]
|
||||
found = true
|
||||
Search.advanced_filters.each do |matcher, block|
|
||||
cleaned = word.gsub(/["']/, "")
|
||||
if cleaned =~ matcher
|
||||
(@filters ||= []) << [block, $1]
|
||||
found = true
|
||||
end
|
||||
end
|
||||
|
||||
@in_title = false
|
||||
|
||||
if word == 'order:latest' || word == 'l'
|
||||
@order = :latest
|
||||
nil
|
||||
elsif word == 'order:latest_topic'
|
||||
@order = :latest_topic
|
||||
nil
|
||||
elsif word == 'in:title'
|
||||
@in_title = true
|
||||
nil
|
||||
elsif word =~ /topic:(\d+)/
|
||||
topic_id = $1.to_i
|
||||
if topic_id > 1
|
||||
topic = Topic.find_by(id: topic_id)
|
||||
if @guardian.can_see?(topic)
|
||||
@search_context = topic
|
||||
end
|
||||
end
|
||||
|
||||
@in_title = false
|
||||
|
||||
if word == 'order:latest' || word == 'l'
|
||||
@order = :latest
|
||||
nil
|
||||
elsif word == 'order:latest_topic'
|
||||
@order = :latest_topic
|
||||
nil
|
||||
elsif word == 'in:title'
|
||||
@in_title = true
|
||||
nil
|
||||
elsif word =~ /topic:(\d+)/
|
||||
topic_id = $1.to_i
|
||||
if topic_id > 1
|
||||
topic = Topic.find_by(id: topic_id)
|
||||
if @guardian.can_see?(topic)
|
||||
@search_context = topic
|
||||
end
|
||||
end
|
||||
nil
|
||||
elsif word == 'order:views'
|
||||
@order = :views
|
||||
nil
|
||||
elsif word == 'order:likes'
|
||||
@order = :likes
|
||||
nil
|
||||
elsif word == 'in:private'
|
||||
@search_pms = true
|
||||
nil
|
||||
elsif word =~ /^private_messages:(.+)$/
|
||||
@search_pms = true
|
||||
nil
|
||||
else
|
||||
found ? nil : word
|
||||
end
|
||||
end.compact.join(' ')
|
||||
end
|
||||
|
||||
def find_grouped_results
|
||||
|
||||
if @results.type_filter.present?
|
||||
raise Discourse::InvalidAccess.new("invalid type filter") unless Search.facets.include?(@results.type_filter)
|
||||
send("#{@results.type_filter}_search")
|
||||
nil
|
||||
elsif word == 'order:views'
|
||||
@order = :views
|
||||
nil
|
||||
elsif word == 'order:likes'
|
||||
@order = :likes
|
||||
nil
|
||||
elsif word == 'in:private'
|
||||
@search_pms = true
|
||||
nil
|
||||
elsif word =~ /^private_messages:(.+)$/
|
||||
@search_pms = true
|
||||
nil
|
||||
else
|
||||
unless @search_context
|
||||
user_search if @term.present?
|
||||
category_search if @term.present?
|
||||
tags_search if @term.present?
|
||||
found ? nil : word
|
||||
end
|
||||
end.compact.join(' ')
|
||||
end
|
||||
|
||||
def find_grouped_results
|
||||
|
||||
if @results.type_filter.present?
|
||||
raise Discourse::InvalidAccess.new("invalid type filter") unless Search.facets.include?(@results.type_filter)
|
||||
send("#{@results.type_filter}_search")
|
||||
else
|
||||
unless @search_context
|
||||
user_search if @term.present?
|
||||
category_search if @term.present?
|
||||
tags_search if @term.present?
|
||||
end
|
||||
topic_search
|
||||
end
|
||||
|
||||
add_more_topics_if_expected
|
||||
@results
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
# In the event of a PG:Error return nothing, it is likely they used a foreign language whose
|
||||
# locale is not supported by postgres
|
||||
end
|
||||
|
||||
# Add more topics if we expected them
|
||||
def add_more_topics_if_expected
|
||||
expected_topics = 0
|
||||
expected_topics = Search.facets.size unless @results.type_filter.present?
|
||||
expected_topics = Search.per_facet * Search.facets.size if @results.type_filter == 'topic'
|
||||
expected_topics -= @results.posts.length
|
||||
if expected_topics > 0
|
||||
extra_posts = posts_query(expected_topics * Search.burst_factor)
|
||||
extra_posts = extra_posts.where("posts.topic_id NOT in (?)", @results.posts.map(&:topic_id)) if @results.posts.present?
|
||||
extra_posts.each do |post|
|
||||
@results.add(post)
|
||||
expected_topics -= 1
|
||||
break if expected_topics == 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# If we're searching for a single topic
|
||||
def single_topic(id)
|
||||
post = Post.find_by(topic_id: id, post_number: 1)
|
||||
return nil unless @guardian.can_see?(post)
|
||||
|
||||
@results.add(post)
|
||||
@results
|
||||
end
|
||||
|
||||
def secure_category_ids
|
||||
return @secure_category_ids unless @secure_category_ids.nil?
|
||||
@secure_category_ids = @guardian.secure_category_ids
|
||||
end
|
||||
|
||||
def category_search
|
||||
# scope is leaking onto Category, this is not good and probably a bug in Rails
|
||||
# the secure_category_ids will invoke the same method on User, it calls Category.where
|
||||
# however the scope from the query below is leaking in to Category, this works around
|
||||
# the issue while we figure out what is up in Rails
|
||||
secure_category_ids
|
||||
|
||||
categories = Category.includes(:category_search_data)
|
||||
.where("category_search_data.search_data @@ #{ts_query}")
|
||||
.references(:category_search_data)
|
||||
.order("topics_month DESC")
|
||||
.secured(@guardian)
|
||||
.limit(limit)
|
||||
|
||||
categories.each do |category|
|
||||
@results.add(category)
|
||||
end
|
||||
end
|
||||
|
||||
def user_search
|
||||
return if SiteSetting.hide_user_profiles_from_public && !@guardian.user
|
||||
|
||||
users = User.includes(:user_search_data)
|
||||
.references(:user_search_data)
|
||||
.where(active: true)
|
||||
.where(staged: false)
|
||||
.where("user_search_data.search_data @@ #{ts_query("simple")}")
|
||||
.order("CASE WHEN username_lower = '#{@original_term.downcase}' THEN 0 ELSE 1 END")
|
||||
.order("last_posted_at DESC")
|
||||
.limit(limit)
|
||||
|
||||
users.each do |user|
|
||||
@results.add(user)
|
||||
end
|
||||
end
|
||||
|
||||
def tags_search
|
||||
return unless SiteSetting.tagging_enabled
|
||||
|
||||
tags = Tag.includes(:tag_search_data)
|
||||
.where("tag_search_data.search_data @@ #{ts_query}")
|
||||
.references(:tag_search_data)
|
||||
.order("name asc")
|
||||
.limit(limit)
|
||||
|
||||
tags.each do |tag|
|
||||
@results.add(tag)
|
||||
end
|
||||
end
|
||||
|
||||
def posts_query(limit, opts = nil)
|
||||
opts ||= {}
|
||||
posts = Post.where(post_type: Topic.visible_post_types(@guardian.user))
|
||||
.joins(:post_search_data, :topic)
|
||||
.joins("LEFT JOIN categories ON categories.id = topics.category_id")
|
||||
.where("topics.deleted_at" => nil)
|
||||
|
||||
is_topic_search = @search_context.present? && @search_context.is_a?(Topic)
|
||||
|
||||
posts = posts.where("topics.visible") unless is_topic_search
|
||||
|
||||
if opts[:private_messages] || (is_topic_search && @search_context.private_message?)
|
||||
posts = posts.where("topics.archetype = ?", Archetype.private_message)
|
||||
|
||||
unless @guardian.is_admin?
|
||||
posts = posts.private_posts_for_user(@guardian.user)
|
||||
end
|
||||
else
|
||||
posts = posts.where("topics.archetype <> ?", Archetype.private_message)
|
||||
end
|
||||
|
||||
if @term.present?
|
||||
if is_topic_search
|
||||
|
||||
term_without_quote = @term
|
||||
if @term =~ /"(.+)"/
|
||||
term_without_quote = $1
|
||||
end
|
||||
topic_search
|
||||
end
|
||||
|
||||
add_more_topics_if_expected
|
||||
@results
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
# In the event of a PG:Error return nothing, it is likely they used a foreign language whose
|
||||
# locale is not supported by postgres
|
||||
end
|
||||
|
||||
# Add more topics if we expected them
|
||||
def add_more_topics_if_expected
|
||||
expected_topics = 0
|
||||
expected_topics = Search.facets.size unless @results.type_filter.present?
|
||||
expected_topics = Search.per_facet * Search.facets.size if @results.type_filter == 'topic'
|
||||
expected_topics -= @results.posts.length
|
||||
if expected_topics > 0
|
||||
extra_posts = posts_query(expected_topics * Search.burst_factor)
|
||||
extra_posts = extra_posts.where("posts.topic_id NOT in (?)", @results.posts.map(&:topic_id)) if @results.posts.present?
|
||||
extra_posts.each do |post|
|
||||
@results.add(post)
|
||||
expected_topics -= 1
|
||||
break if expected_topics == 0
|
||||
if @term =~ /'(.+)'/
|
||||
term_without_quote = $1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# If we're searching for a single topic
|
||||
def single_topic(id)
|
||||
post = Post.find_by(topic_id: id, post_number: 1)
|
||||
return nil unless @guardian.can_see?(post)
|
||||
|
||||
@results.add(post)
|
||||
@results
|
||||
end
|
||||
|
||||
def secure_category_ids
|
||||
return @secure_category_ids unless @secure_category_ids.nil?
|
||||
@secure_category_ids = @guardian.secure_category_ids
|
||||
end
|
||||
|
||||
def category_search
|
||||
# scope is leaking onto Category, this is not good and probably a bug in Rails
|
||||
# the secure_category_ids will invoke the same method on User, it calls Category.where
|
||||
# however the scope from the query below is leaking in to Category, this works around
|
||||
# the issue while we figure out what is up in Rails
|
||||
secure_category_ids
|
||||
|
||||
categories = Category.includes(:category_search_data)
|
||||
.where("category_search_data.search_data @@ #{ts_query}")
|
||||
.references(:category_search_data)
|
||||
.order("topics_month DESC")
|
||||
.secured(@guardian)
|
||||
.limit(limit)
|
||||
|
||||
categories.each do |category|
|
||||
@results.add(category)
|
||||
end
|
||||
end
|
||||
|
||||
def user_search
|
||||
return if SiteSetting.hide_user_profiles_from_public && !@guardian.user
|
||||
|
||||
users = User.includes(:user_search_data)
|
||||
.references(:user_search_data)
|
||||
.where(active: true)
|
||||
.where(staged: false)
|
||||
.where("user_search_data.search_data @@ #{ts_query("simple")}")
|
||||
.order("CASE WHEN username_lower = '#{@original_term.downcase}' THEN 0 ELSE 1 END")
|
||||
.order("last_posted_at DESC")
|
||||
.limit(limit)
|
||||
|
||||
users.each do |user|
|
||||
@results.add(user)
|
||||
end
|
||||
end
|
||||
|
||||
def tags_search
|
||||
return unless SiteSetting.tagging_enabled
|
||||
|
||||
tags = Tag.includes(:tag_search_data)
|
||||
.where("tag_search_data.search_data @@ #{ts_query}")
|
||||
.references(:tag_search_data)
|
||||
.order("name asc")
|
||||
.limit(limit)
|
||||
|
||||
tags.each do |tag|
|
||||
@results.add(tag)
|
||||
end
|
||||
end
|
||||
|
||||
def posts_query(limit, opts = nil)
|
||||
opts ||= {}
|
||||
posts = Post.where(post_type: Topic.visible_post_types(@guardian.user))
|
||||
.joins(:post_search_data, :topic)
|
||||
.joins("LEFT JOIN categories ON categories.id = topics.category_id")
|
||||
.where("topics.deleted_at" => nil)
|
||||
|
||||
is_topic_search = @search_context.present? && @search_context.is_a?(Topic)
|
||||
|
||||
posts = posts.where("topics.visible") unless is_topic_search
|
||||
|
||||
if opts[:private_messages] || (is_topic_search && @search_context.private_message?)
|
||||
posts = posts.where("topics.archetype = ?", Archetype.private_message)
|
||||
|
||||
unless @guardian.is_admin?
|
||||
posts = posts.private_posts_for_user(@guardian.user)
|
||||
end
|
||||
posts = posts.joins('JOIN users u ON u.id = posts.user_id')
|
||||
posts = posts.where("posts.raw || ' ' || u.username || ' ' || COALESCE(u.name, '') ilike ?", "%#{term_without_quote}%")
|
||||
else
|
||||
posts = posts.where("topics.archetype <> ?", Archetype.private_message)
|
||||
end
|
||||
|
||||
if @term.present?
|
||||
if is_topic_search
|
||||
|
||||
term_without_quote = @term
|
||||
if @term =~ /"(.+)"/
|
||||
term_without_quote = $1
|
||||
end
|
||||
|
||||
if @term =~ /'(.+)'/
|
||||
term_without_quote = $1
|
||||
end
|
||||
|
||||
posts = posts.joins('JOIN users u ON u.id = posts.user_id')
|
||||
posts = posts.where("posts.raw || ' ' || u.username || ' ' || COALESCE(u.name, '') ilike ?", "%#{term_without_quote}%")
|
||||
else
|
||||
# A is for title
|
||||
# B is for category
|
||||
# C is for tags
|
||||
# D is for cooked
|
||||
weights = @in_title ? 'A' : (SiteSetting.tagging_enabled ? 'ABCD' : 'ABD')
|
||||
posts = posts.where("post_search_data.search_data @@ #{ts_query(weight_filter: weights)}")
|
||||
exact_terms = @term.scan(/"([^"]+)"/).flatten
|
||||
exact_terms.each do |exact|
|
||||
posts = posts.where("posts.raw ilike :exact OR topics.title ilike :exact", exact: "%#{exact}%")
|
||||
end
|
||||
# A is for title
|
||||
# B is for category
|
||||
# C is for tags
|
||||
# D is for cooked
|
||||
weights = @in_title ? 'A' : (SiteSetting.tagging_enabled ? 'ABCD' : 'ABD')
|
||||
posts = posts.where("post_search_data.search_data @@ #{ts_query(weight_filter: weights)}")
|
||||
exact_terms = @term.scan(/"([^"]+)"/).flatten
|
||||
exact_terms.each do |exact|
|
||||
posts = posts.where("posts.raw ilike :exact OR topics.title ilike :exact", exact: "%#{exact}%")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@filters.each do |block, match|
|
||||
if block.arity == 1
|
||||
posts = instance_exec(posts, &block) || posts
|
||||
else
|
||||
posts = instance_exec(posts, match, &block) || posts
|
||||
end
|
||||
end if @filters
|
||||
|
||||
# If we have a search context, prioritize those posts first
|
||||
if @search_context.present?
|
||||
|
||||
if @search_context.is_a?(User)
|
||||
|
||||
if opts[:private_messages]
|
||||
posts = posts.private_posts_for_user(@search_context)
|
||||
else
|
||||
posts = posts.where("posts.user_id = #{@search_context.id}")
|
||||
end
|
||||
|
||||
elsif @search_context.is_a?(Category)
|
||||
category_ids = [@search_context.id] + Category.where(parent_category_id: @search_context.id).pluck(:id)
|
||||
posts = posts.where("topics.category_id in (?)", category_ids)
|
||||
elsif @search_context.is_a?(Topic)
|
||||
posts = posts.where("topics.id = #{@search_context.id}")
|
||||
.order("posts.post_number #{@order == :latest ? "DESC" : ""}")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if @order == :latest || (@term.blank? && !@order)
|
||||
if opts[:aggregate_search]
|
||||
posts = posts.order("MAX(posts.created_at) DESC")
|
||||
else
|
||||
posts = posts.reorder("posts.created_at DESC")
|
||||
end
|
||||
elsif @order == :latest_topic
|
||||
if opts[:aggregate_search]
|
||||
posts = posts.order("MAX(topics.created_at) DESC")
|
||||
else
|
||||
posts = posts.order("topics.created_at DESC")
|
||||
end
|
||||
elsif @order == :views
|
||||
if opts[:aggregate_search]
|
||||
posts = posts.order("MAX(topics.views) DESC")
|
||||
else
|
||||
posts = posts.order("topics.views DESC")
|
||||
end
|
||||
elsif @order == :likes
|
||||
if opts[:aggregate_search]
|
||||
posts = posts.order("MAX(posts.like_count) DESC")
|
||||
else
|
||||
posts = posts.order("posts.like_count DESC")
|
||||
end
|
||||
@filters.each do |block, match|
|
||||
if block.arity == 1
|
||||
posts = instance_exec(posts, &block) || posts
|
||||
else
|
||||
data_ranking = "TS_RANK_CD(post_search_data.search_data, #{ts_query})"
|
||||
if opts[:aggregate_search]
|
||||
posts = posts.order("MAX(#{data_ranking}) DESC")
|
||||
posts = instance_exec(posts, match, &block) || posts
|
||||
end
|
||||
end if @filters
|
||||
|
||||
# If we have a search context, prioritize those posts first
|
||||
if @search_context.present?
|
||||
|
||||
if @search_context.is_a?(User)
|
||||
|
||||
if opts[:private_messages]
|
||||
posts = posts.private_posts_for_user(@search_context)
|
||||
else
|
||||
posts = posts.order("#{data_ranking} DESC")
|
||||
posts = posts.where("posts.user_id = #{@search_context.id}")
|
||||
end
|
||||
posts = posts.order("topics.bumped_at DESC")
|
||||
|
||||
elsif @search_context.is_a?(Category)
|
||||
category_ids = [@search_context.id] + Category.where(parent_category_id: @search_context.id).pluck(:id)
|
||||
posts = posts.where("topics.category_id in (?)", category_ids)
|
||||
elsif @search_context.is_a?(Topic)
|
||||
posts = posts.where("topics.id = #{@search_context.id}")
|
||||
.order("posts.post_number #{@order == :latest ? "DESC" : ""}")
|
||||
end
|
||||
|
||||
if secure_category_ids.present?
|
||||
posts = posts.where("(categories.id IS NULL) OR (NOT categories.read_restricted) OR (categories.id IN (?))", secure_category_ids).references(:categories)
|
||||
end
|
||||
|
||||
if @order == :latest || (@term.blank? && !@order)
|
||||
if opts[:aggregate_search]
|
||||
posts = posts.order("MAX(posts.created_at) DESC")
|
||||
else
|
||||
posts = posts.where("(categories.id IS NULL) OR (NOT categories.read_restricted)").references(:categories)
|
||||
posts = posts.reorder("posts.created_at DESC")
|
||||
end
|
||||
|
||||
posts = posts.offset(offset)
|
||||
posts.limit(limit)
|
||||
end
|
||||
|
||||
def self.default_ts_config
|
||||
"'#{Search.ts_config}'"
|
||||
end
|
||||
|
||||
def default_ts_config
|
||||
self.class.default_ts_config
|
||||
end
|
||||
|
||||
def self.ts_query(term: , ts_config: nil, joiner: "&", weight_filter: nil)
|
||||
|
||||
data = Post.exec_sql("SELECT TO_TSVECTOR(:config, :term)",
|
||||
config: 'simple',
|
||||
term: term).values[0][0]
|
||||
|
||||
ts_config = ActiveRecord::Base.connection.quote(ts_config) if ts_config
|
||||
all_terms = data.scan(/'([^']+)'\:\d+/).flatten
|
||||
all_terms.map! do |t|
|
||||
t.split(/[\)\(&']/)[0]
|
||||
end.compact!
|
||||
|
||||
query = ActiveRecord::Base.connection.quote(
|
||||
all_terms
|
||||
.map { |t| "'#{PG::Connection.escape_string(t)}':*#{weight_filter}" }
|
||||
.join(" #{joiner} ")
|
||||
)
|
||||
|
||||
"TO_TSQUERY(#{ts_config || default_ts_config}, #{query})"
|
||||
end
|
||||
|
||||
def ts_query(ts_config = nil, weight_filter: nil)
|
||||
@ts_query_cache ||= {}
|
||||
@ts_query_cache["#{ts_config || default_ts_config} #{@term} #{weight_filter}"] ||=
|
||||
Search.ts_query(term: @term, ts_config: ts_config, weight_filter: weight_filter)
|
||||
end
|
||||
|
||||
def wrap_rows(query)
|
||||
"SELECT *, row_number() over() row_number FROM (#{query.to_sql}) xxx"
|
||||
end
|
||||
|
||||
def aggregate_post_sql(opts)
|
||||
min_or_max = @order == :latest ? "max" : "min"
|
||||
|
||||
query =
|
||||
if @order == :likes
|
||||
# likes are a pain to aggregate so skip
|
||||
posts_query(limit, private_messages: opts[:private_messages])
|
||||
.select('topics.id', "posts.post_number")
|
||||
else
|
||||
posts_query(limit, aggregate_search: true, private_messages: opts[:private_messages])
|
||||
.select('topics.id', "#{min_or_max}(posts.post_number) post_number")
|
||||
.group('topics.id')
|
||||
end
|
||||
|
||||
min_id = Search.min_post_id
|
||||
if min_id > 0
|
||||
low_set = query.dup.where("post_search_data.post_id < #{min_id}")
|
||||
high_set = query.where("post_search_data.post_id >= #{min_id}")
|
||||
|
||||
return { default: wrap_rows(high_set), remaining: wrap_rows(low_set) }
|
||||
end
|
||||
|
||||
# double wrapping so we get correct row numbers
|
||||
{ default: wrap_rows(query) }
|
||||
end
|
||||
|
||||
def aggregate_posts(post_sql)
|
||||
return [] unless post_sql
|
||||
|
||||
posts_eager_loads(Post)
|
||||
.joins("JOIN (#{post_sql}) x ON x.id = posts.topic_id AND x.post_number = posts.post_number")
|
||||
.order('row_number')
|
||||
end
|
||||
|
||||
def aggregate_search(opts = {})
|
||||
post_sql = aggregate_post_sql(opts)
|
||||
|
||||
added = 0
|
||||
|
||||
aggregate_posts(post_sql[:default]).each do |p|
|
||||
@results.add(p)
|
||||
added += 1
|
||||
end
|
||||
|
||||
if added < limit
|
||||
aggregate_posts(post_sql[:remaining]).each { |p| @results.add(p) }
|
||||
end
|
||||
end
|
||||
|
||||
def private_messages_search
|
||||
raise Discourse::InvalidAccess.new("anonymous can not search PMs") unless @guardian.user
|
||||
|
||||
aggregate_search(private_messages: true)
|
||||
end
|
||||
|
||||
def topic_search
|
||||
if @search_context.is_a?(Topic)
|
||||
posts = posts_eager_loads(posts_query(limit))
|
||||
.where('posts.topic_id = ?', @search_context.id)
|
||||
|
||||
posts.each do |post|
|
||||
@results.add(post)
|
||||
end
|
||||
elsif @order == :latest_topic
|
||||
if opts[:aggregate_search]
|
||||
posts = posts.order("MAX(topics.created_at) DESC")
|
||||
else
|
||||
aggregate_search
|
||||
posts = posts.order("topics.created_at DESC")
|
||||
end
|
||||
elsif @order == :views
|
||||
if opts[:aggregate_search]
|
||||
posts = posts.order("MAX(topics.views) DESC")
|
||||
else
|
||||
posts = posts.order("topics.views DESC")
|
||||
end
|
||||
elsif @order == :likes
|
||||
if opts[:aggregate_search]
|
||||
posts = posts.order("MAX(posts.like_count) DESC")
|
||||
else
|
||||
posts = posts.order("posts.like_count DESC")
|
||||
end
|
||||
else
|
||||
data_ranking = "TS_RANK_CD(post_search_data.search_data, #{ts_query})"
|
||||
if opts[:aggregate_search]
|
||||
posts = posts.order("MAX(#{data_ranking}) DESC")
|
||||
else
|
||||
posts = posts.order("#{data_ranking} DESC")
|
||||
end
|
||||
posts = posts.order("topics.bumped_at DESC")
|
||||
end
|
||||
|
||||
def posts_eager_loads(query)
|
||||
query = query.includes(:user)
|
||||
topic_eager_loads = [:category]
|
||||
if secure_category_ids.present?
|
||||
posts = posts.where("(categories.id IS NULL) OR (NOT categories.read_restricted) OR (categories.id IN (?))", secure_category_ids).references(:categories)
|
||||
else
|
||||
posts = posts.where("(categories.id IS NULL) OR (NOT categories.read_restricted)").references(:categories)
|
||||
end
|
||||
|
||||
if SiteSetting.tagging_enabled
|
||||
topic_eager_loads << :tags
|
||||
posts = posts.offset(offset)
|
||||
posts.limit(limit)
|
||||
end
|
||||
|
||||
def self.default_ts_config
|
||||
"'#{Search.ts_config}'"
|
||||
end
|
||||
|
||||
def default_ts_config
|
||||
self.class.default_ts_config
|
||||
end
|
||||
|
||||
def self.ts_query(term: , ts_config: nil, joiner: "&", weight_filter: nil)
|
||||
|
||||
data = Post.exec_sql("SELECT TO_TSVECTOR(:config, :term)",
|
||||
config: 'simple',
|
||||
term: term).values[0][0]
|
||||
|
||||
ts_config = ActiveRecord::Base.connection.quote(ts_config) if ts_config
|
||||
all_terms = data.scan(/'([^']+)'\:\d+/).flatten
|
||||
all_terms.map! do |t|
|
||||
t.split(/[\)\(&']/)[0]
|
||||
end.compact!
|
||||
|
||||
query = ActiveRecord::Base.connection.quote(
|
||||
all_terms
|
||||
.map { |t| "'#{PG::Connection.escape_string(t)}':*#{weight_filter}" }
|
||||
.join(" #{joiner} ")
|
||||
)
|
||||
|
||||
"TO_TSQUERY(#{ts_config || default_ts_config}, #{query})"
|
||||
end
|
||||
|
||||
def ts_query(ts_config = nil, weight_filter: nil)
|
||||
@ts_query_cache ||= {}
|
||||
@ts_query_cache["#{ts_config || default_ts_config} #{@term} #{weight_filter}"] ||=
|
||||
Search.ts_query(term: @term, ts_config: ts_config, weight_filter: weight_filter)
|
||||
end
|
||||
|
||||
def wrap_rows(query)
|
||||
"SELECT *, row_number() over() row_number FROM (#{query.to_sql}) xxx"
|
||||
end
|
||||
|
||||
def aggregate_post_sql(opts)
|
||||
min_or_max = @order == :latest ? "max" : "min"
|
||||
|
||||
query =
|
||||
if @order == :likes
|
||||
# likes are a pain to aggregate so skip
|
||||
posts_query(limit, private_messages: opts[:private_messages])
|
||||
.select('topics.id', "posts.post_number")
|
||||
else
|
||||
posts_query(limit, aggregate_search: true, private_messages: opts[:private_messages])
|
||||
.select('topics.id', "#{min_or_max}(posts.post_number) post_number")
|
||||
.group('topics.id')
|
||||
end
|
||||
|
||||
query.includes(topic: topic_eager_loads)
|
||||
min_id = Search.min_post_id
|
||||
if min_id > 0
|
||||
low_set = query.dup.where("post_search_data.post_id < #{min_id}")
|
||||
high_set = query.where("post_search_data.post_id >= #{min_id}")
|
||||
|
||||
return { default: wrap_rows(high_set), remaining: wrap_rows(low_set) }
|
||||
end
|
||||
|
||||
# double wrapping so we get correct row numbers
|
||||
{ default: wrap_rows(query) }
|
||||
end
|
||||
|
||||
def aggregate_posts(post_sql)
|
||||
return [] unless post_sql
|
||||
|
||||
posts_eager_loads(Post)
|
||||
.joins("JOIN (#{post_sql}) x ON x.id = posts.topic_id AND x.post_number = posts.post_number")
|
||||
.order('row_number')
|
||||
end
|
||||
|
||||
def aggregate_search(opts = {})
|
||||
post_sql = aggregate_post_sql(opts)
|
||||
|
||||
added = 0
|
||||
|
||||
aggregate_posts(post_sql[:default]).each do |p|
|
||||
@results.add(p)
|
||||
added += 1
|
||||
end
|
||||
|
||||
if added < limit
|
||||
aggregate_posts(post_sql[:remaining]).each { |p| @results.add(p) }
|
||||
end
|
||||
end
|
||||
|
||||
def private_messages_search
|
||||
raise Discourse::InvalidAccess.new("anonymous can not search PMs") unless @guardian.user
|
||||
|
||||
aggregate_search(private_messages: true)
|
||||
end
|
||||
|
||||
def topic_search
|
||||
if @search_context.is_a?(Topic)
|
||||
posts = posts_eager_loads(posts_query(limit))
|
||||
.where('posts.topic_id = ?', @search_context.id)
|
||||
|
||||
posts.each do |post|
|
||||
@results.add(post)
|
||||
end
|
||||
else
|
||||
aggregate_search
|
||||
end
|
||||
end
|
||||
|
||||
def posts_eager_loads(query)
|
||||
query = query.includes(:user)
|
||||
topic_eager_loads = [:category]
|
||||
|
||||
if SiteSetting.tagging_enabled
|
||||
topic_eager_loads << :tags
|
||||
end
|
||||
|
||||
query.includes(topic: topic_eager_loads)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -423,147 +423,147 @@ class TopicQuery
|
|||
|
||||
protected
|
||||
|
||||
def per_page_setting
|
||||
@options[:slow_platform] ? 15 : 30
|
||||
end
|
||||
def per_page_setting
|
||||
@options[:slow_platform] ? 15 : 30
|
||||
end
|
||||
|
||||
def private_messages_for(user, type)
|
||||
options = @options
|
||||
options.reverse_merge!(per_page: per_page_setting)
|
||||
def private_messages_for(user, type)
|
||||
options = @options
|
||||
options.reverse_merge!(per_page: per_page_setting)
|
||||
|
||||
result = Topic.includes(:tags)
|
||||
result = Topic.includes(:tags)
|
||||
|
||||
if type == :group
|
||||
result = result.includes(:allowed_users)
|
||||
result = result.where("
|
||||
topics.id IN (
|
||||
SELECT topic_id FROM topic_allowed_groups
|
||||
WHERE (
|
||||
group_id IN (
|
||||
SELECT group_id
|
||||
FROM group_users
|
||||
WHERE user_id = #{user.id.to_i}
|
||||
OR #{user.staff?}
|
||||
)
|
||||
)
|
||||
AND group_id IN (SELECT id FROM groups WHERE name ilike ?)
|
||||
)",
|
||||
@options[:group_name]
|
||||
)
|
||||
elsif type == :user
|
||||
result = result.includes(:allowed_users)
|
||||
result = result.where("topics.id IN (SELECT topic_id FROM topic_allowed_users WHERE user_id = #{user.id.to_i})")
|
||||
elsif type == :all
|
||||
result = result.includes(:allowed_users)
|
||||
result = result.where("topics.id IN (
|
||||
SELECT topic_id
|
||||
FROM topic_allowed_users
|
||||
if type == :group
|
||||
result = result.includes(:allowed_users)
|
||||
result = result.where("
|
||||
topics.id IN (
|
||||
SELECT topic_id FROM topic_allowed_groups
|
||||
WHERE (
|
||||
group_id IN (
|
||||
SELECT group_id
|
||||
FROM group_users
|
||||
WHERE user_id = #{user.id.to_i}
|
||||
UNION ALL
|
||||
SELECT topic_id FROM topic_allowed_groups
|
||||
WHERE group_id IN (
|
||||
SELECT group_id FROM group_users WHERE user_id = #{user.id.to_i}
|
||||
)
|
||||
)")
|
||||
end
|
||||
|
||||
result = result.joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{user.id.to_i})")
|
||||
.order("topics.bumped_at DESC")
|
||||
.private_messages
|
||||
|
||||
result = result.limit(options[:per_page]) unless options[:limit] == false
|
||||
result = result.visible if options[:visible] || @user.nil? || @user.regular?
|
||||
|
||||
if options[:page]
|
||||
offset = options[:page].to_i * options[:per_page]
|
||||
result = result.offset(offset) if offset > 0
|
||||
end
|
||||
result
|
||||
OR #{user.staff?}
|
||||
)
|
||||
)
|
||||
AND group_id IN (SELECT id FROM groups WHERE name ilike ?)
|
||||
)",
|
||||
@options[:group_name]
|
||||
)
|
||||
elsif type == :user
|
||||
result = result.includes(:allowed_users)
|
||||
result = result.where("topics.id IN (SELECT topic_id FROM topic_allowed_users WHERE user_id = #{user.id.to_i})")
|
||||
elsif type == :all
|
||||
result = result.includes(:allowed_users)
|
||||
result = result.where("topics.id IN (
|
||||
SELECT topic_id
|
||||
FROM topic_allowed_users
|
||||
WHERE user_id = #{user.id.to_i}
|
||||
UNION ALL
|
||||
SELECT topic_id FROM topic_allowed_groups
|
||||
WHERE group_id IN (
|
||||
SELECT group_id FROM group_users WHERE user_id = #{user.id.to_i}
|
||||
)
|
||||
)")
|
||||
end
|
||||
|
||||
def apply_shared_drafts(result, category_id, options)
|
||||
drafts_category_id = SiteSetting.shared_drafts_category.to_i
|
||||
viewing_shared = category_id && category_id == drafts_category_id
|
||||
result = result.joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{user.id.to_i})")
|
||||
.order("topics.bumped_at DESC")
|
||||
.private_messages
|
||||
|
||||
if guardian.can_create_shared_draft?
|
||||
if options[:destination_category_id]
|
||||
destination_category_id = get_category_id(options[:destination_category_id])
|
||||
topic_ids = SharedDraft.where(category_id: destination_category_id).pluck(:topic_id)
|
||||
return result.where(id: topic_ids)
|
||||
elsif viewing_shared
|
||||
result = result.includes(:shared_draft).references(:shared_draft)
|
||||
else
|
||||
return result.where('topics.category_id != ?', drafts_category_id)
|
||||
end
|
||||
result = result.limit(options[:per_page]) unless options[:limit] == false
|
||||
result = result.visible if options[:visible] || @user.nil? || @user.regular?
|
||||
|
||||
if options[:page]
|
||||
offset = options[:page].to_i * options[:per_page]
|
||||
result = result.offset(offset) if offset > 0
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def apply_shared_drafts(result, category_id, options)
|
||||
drafts_category_id = SiteSetting.shared_drafts_category.to_i
|
||||
viewing_shared = category_id && category_id == drafts_category_id
|
||||
|
||||
if guardian.can_create_shared_draft?
|
||||
if options[:destination_category_id]
|
||||
destination_category_id = get_category_id(options[:destination_category_id])
|
||||
topic_ids = SharedDraft.where(category_id: destination_category_id).pluck(:topic_id)
|
||||
return result.where(id: topic_ids)
|
||||
elsif viewing_shared
|
||||
result = result.includes(:shared_draft).references(:shared_draft)
|
||||
else
|
||||
return result.where('topics.category_id != ?', drafts_category_id)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def apply_ordering(result, options)
|
||||
sort_column = SORTABLE_MAPPING[options[:order]] || 'default'
|
||||
sort_dir = (options[:ascending] == "true") ? "ASC" : "DESC"
|
||||
result
|
||||
end
|
||||
|
||||
# If we are sorting in the default order desc, we should consider including pinned
|
||||
# topics. Otherwise, just use bumped_at.
|
||||
if sort_column == 'default'
|
||||
if sort_dir == 'DESC'
|
||||
# If something requires a custom order, for example "unread" which sorts the least read
|
||||
# to the top, do nothing
|
||||
return result if options[:unordered]
|
||||
end
|
||||
sort_column = 'bumped_at'
|
||||
def apply_ordering(result, options)
|
||||
sort_column = SORTABLE_MAPPING[options[:order]] || 'default'
|
||||
sort_dir = (options[:ascending] == "true") ? "ASC" : "DESC"
|
||||
|
||||
# If we are sorting in the default order desc, we should consider including pinned
|
||||
# topics. Otherwise, just use bumped_at.
|
||||
if sort_column == 'default'
|
||||
if sort_dir == 'DESC'
|
||||
# If something requires a custom order, for example "unread" which sorts the least read
|
||||
# to the top, do nothing
|
||||
return result if options[:unordered]
|
||||
end
|
||||
|
||||
# If we are sorting by category, actually use the name
|
||||
if sort_column == 'category_id'
|
||||
# TODO forces a table scan, slow
|
||||
return result.references(:categories).order(TopicQuerySQL.order_by_category_sql(sort_dir))
|
||||
end
|
||||
|
||||
if sort_column == 'op_likes'
|
||||
return result.includes(:first_post).order("(SELECT like_count FROM posts p3 WHERE p3.topic_id = topics.id AND p3.post_number = 1) #{sort_dir}")
|
||||
end
|
||||
|
||||
if sort_column.start_with?('custom_fields')
|
||||
field = sort_column.split('.')[1]
|
||||
return result.order("(SELECT CASE WHEN EXISTS (SELECT true FROM topic_custom_fields tcf WHERE tcf.topic_id::integer = topics.id::integer AND tcf.name = '#{field}') THEN (SELECT value::integer FROM topic_custom_fields tcf WHERE tcf.topic_id::integer = topics.id::integer AND tcf.name = '#{field}') ELSE 0 END) #{sort_dir}")
|
||||
end
|
||||
|
||||
result.order("topics.#{sort_column} #{sort_dir}")
|
||||
sort_column = 'bumped_at'
|
||||
end
|
||||
|
||||
def get_category_id(category_id_or_slug)
|
||||
return nil unless category_id_or_slug
|
||||
category_id = category_id_or_slug.to_i
|
||||
category_id = Category.where(slug: category_id_or_slug).pluck(:id).first if category_id == 0
|
||||
category_id
|
||||
# If we are sorting by category, actually use the name
|
||||
if sort_column == 'category_id'
|
||||
# TODO forces a table scan, slow
|
||||
return result.references(:categories).order(TopicQuerySQL.order_by_category_sql(sort_dir))
|
||||
end
|
||||
|
||||
# Create results based on a bunch of default options
|
||||
def default_results(options = {})
|
||||
options.reverse_merge!(@options)
|
||||
options.reverse_merge!(per_page: per_page_setting)
|
||||
if sort_column == 'op_likes'
|
||||
return result.includes(:first_post).order("(SELECT like_count FROM posts p3 WHERE p3.topic_id = topics.id AND p3.post_number = 1) #{sort_dir}")
|
||||
end
|
||||
|
||||
# Whether to return visible topics
|
||||
options[:visible] = true if @user.nil? || @user.regular?
|
||||
options[:visible] = false if @user && @user.id == options[:filtered_to_user]
|
||||
if sort_column.start_with?('custom_fields')
|
||||
field = sort_column.split('.')[1]
|
||||
return result.order("(SELECT CASE WHEN EXISTS (SELECT true FROM topic_custom_fields tcf WHERE tcf.topic_id::integer = topics.id::integer AND tcf.name = '#{field}') THEN (SELECT value::integer FROM topic_custom_fields tcf WHERE tcf.topic_id::integer = topics.id::integer AND tcf.name = '#{field}') ELSE 0 END) #{sort_dir}")
|
||||
end
|
||||
|
||||
# Start with a list of all topics
|
||||
result = Topic.unscoped
|
||||
result.order("topics.#{sort_column} #{sort_dir}")
|
||||
end
|
||||
|
||||
if @user
|
||||
result = result.joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{@user.id.to_i})")
|
||||
.references('tu')
|
||||
end
|
||||
def get_category_id(category_id_or_slug)
|
||||
return nil unless category_id_or_slug
|
||||
category_id = category_id_or_slug.to_i
|
||||
category_id = Category.where(slug: category_id_or_slug).pluck(:id).first if category_id == 0
|
||||
category_id
|
||||
end
|
||||
|
||||
category_id = get_category_id(options[:category])
|
||||
@options[:category_id] = category_id
|
||||
if category_id
|
||||
if options[:no_subcategories]
|
||||
result = result.where('categories.id = ?', category_id)
|
||||
else
|
||||
sql = <<~SQL
|
||||
# Create results based on a bunch of default options
|
||||
def default_results(options = {})
|
||||
options.reverse_merge!(@options)
|
||||
options.reverse_merge!(per_page: per_page_setting)
|
||||
|
||||
# Whether to return visible topics
|
||||
options[:visible] = true if @user.nil? || @user.regular?
|
||||
options[:visible] = false if @user && @user.id == options[:filtered_to_user]
|
||||
|
||||
# Start with a list of all topics
|
||||
result = Topic.unscoped
|
||||
|
||||
if @user
|
||||
result = result.joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{@user.id.to_i})")
|
||||
.references('tu')
|
||||
end
|
||||
|
||||
category_id = get_category_id(options[:category])
|
||||
@options[:category_id] = category_id
|
||||
if category_id
|
||||
if options[:no_subcategories]
|
||||
result = result.where('categories.id = ?', category_id)
|
||||
else
|
||||
sql = <<~SQL
|
||||
categories.id IN (
|
||||
SELECT c2.id FROM categories c2 WHERE c2.parent_category_id = :category_id
|
||||
UNION ALL
|
||||
|
@ -573,356 +573,356 @@ class TopicQuery
|
|||
SELECT c3.topic_id FROM categories c3 WHERE c3.parent_category_id = :category_id
|
||||
)
|
||||
SQL
|
||||
result = result.where(sql, category_id: category_id)
|
||||
end
|
||||
result = result.references(:categories)
|
||||
result = result.where(sql, category_id: category_id)
|
||||
end
|
||||
result = result.references(:categories)
|
||||
|
||||
if !@options[:order]
|
||||
# category default sort order
|
||||
sort_order, sort_ascending = Category.where(id: category_id).pluck(:sort_order, :sort_ascending).first
|
||||
if sort_order
|
||||
options[:order] = sort_order
|
||||
options[:ascending] = !!sort_ascending ? 'true' : 'false'
|
||||
end
|
||||
if !@options[:order]
|
||||
# category default sort order
|
||||
sort_order, sort_ascending = Category.where(id: category_id).pluck(:sort_order, :sort_ascending).first
|
||||
if sort_order
|
||||
options[:order] = sort_order
|
||||
options[:ascending] = !!sort_ascending ? 'true' : 'false'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# ALL TAGS: something like this?
|
||||
# Topic.joins(:tags).where('tags.name in (?)', @options[:tags]).group('topic_id').having('count(*)=?', @options[:tags].size).select('topic_id')
|
||||
# ALL TAGS: something like this?
|
||||
# Topic.joins(:tags).where('tags.name in (?)', @options[:tags]).group('topic_id').having('count(*)=?', @options[:tags].size).select('topic_id')
|
||||
|
||||
if SiteSetting.tagging_enabled
|
||||
result = result.preload(:tags)
|
||||
if SiteSetting.tagging_enabled
|
||||
result = result.preload(:tags)
|
||||
|
||||
if @options[:tags] && @options[:tags].size > 0
|
||||
if @options[:tags] && @options[:tags].size > 0
|
||||
|
||||
if @options[:match_all_tags]
|
||||
# ALL of the given tags:
|
||||
tags_count = @options[:tags].length
|
||||
@options[:tags] = Tag.where(name: @options[:tags]).pluck(:id) unless @options[:tags][0].is_a?(Integer)
|
||||
if @options[:match_all_tags]
|
||||
# ALL of the given tags:
|
||||
tags_count = @options[:tags].length
|
||||
@options[:tags] = Tag.where(name: @options[:tags]).pluck(:id) unless @options[:tags][0].is_a?(Integer)
|
||||
|
||||
if tags_count == @options[:tags].length
|
||||
@options[:tags].each_with_index do |tag, index|
|
||||
sql_alias = ['t', index].join
|
||||
result = result.joins("INNER JOIN topic_tags #{sql_alias} ON #{sql_alias}.topic_id = topics.id AND #{sql_alias}.tag_id = #{tag}")
|
||||
end
|
||||
else
|
||||
result = result.none # don't return any results unless all tags exist in the database
|
||||
if tags_count == @options[:tags].length
|
||||
@options[:tags].each_with_index do |tag, index|
|
||||
sql_alias = ['t', index].join
|
||||
result = result.joins("INNER JOIN topic_tags #{sql_alias} ON #{sql_alias}.topic_id = topics.id AND #{sql_alias}.tag_id = #{tag}")
|
||||
end
|
||||
else
|
||||
# ANY of the given tags:
|
||||
result = result.joins(:tags)
|
||||
if @options[:tags][0].is_a?(Integer)
|
||||
result = result.where("tags.id in (?)", @options[:tags])
|
||||
else
|
||||
result = result.where("tags.name in (?)", @options[:tags])
|
||||
end
|
||||
result = result.none # don't return any results unless all tags exist in the database
|
||||
end
|
||||
elsif @options[:no_tags]
|
||||
# the following will do: ("topics"."id" NOT IN (SELECT DISTINCT "topic_tags"."topic_id" FROM "topic_tags"))
|
||||
result = result.where.not(id: TopicTag.distinct.pluck(:topic_id))
|
||||
end
|
||||
end
|
||||
|
||||
result = apply_ordering(result, options)
|
||||
result = result.listable_topics.includes(:category)
|
||||
result = apply_shared_drafts(result, category_id, options)
|
||||
|
||||
if options[:exclude_category_ids] && options[:exclude_category_ids].is_a?(Array) && options[:exclude_category_ids].size > 0
|
||||
result = result.where("categories.id NOT IN (?)", options[:exclude_category_ids]).references(:categories)
|
||||
end
|
||||
|
||||
# Don't include the category topics if excluded
|
||||
if options[:no_definitions]
|
||||
result = result.where('COALESCE(categories.topic_id, 0) <> topics.id')
|
||||
end
|
||||
|
||||
result = result.limit(options[:per_page]) unless options[:limit] == false
|
||||
result = result.visible if options[:visible]
|
||||
result = result.where.not(topics: { id: options[:except_topic_ids] }).references(:topics) if options[:except_topic_ids]
|
||||
|
||||
if options[:page]
|
||||
offset = options[:page].to_i * options[:per_page]
|
||||
result = result.offset(offset) if offset > 0
|
||||
end
|
||||
|
||||
if options[:topic_ids]
|
||||
result = result.where('topics.id in (?)', options[:topic_ids]).references(:topics)
|
||||
end
|
||||
|
||||
if search = options[:search]
|
||||
result = result.where("topics.id in (select pp.topic_id from post_search_data pd join posts pp on pp.id = pd.post_id where pd.search_data @@ #{Search.ts_query(term: search.to_s)})")
|
||||
end
|
||||
|
||||
# NOTE protect against SYM attack can be removed with Ruby 2.2
|
||||
#
|
||||
state = options[:state]
|
||||
if @user && state &&
|
||||
TopicUser.notification_levels.keys.map(&:to_s).include?(state)
|
||||
level = TopicUser.notification_levels[state.to_sym]
|
||||
result = result.where('topics.id IN (
|
||||
SELECT topic_id
|
||||
FROM topic_users
|
||||
WHERE user_id = ? AND
|
||||
notification_level = ?)', @user.id, level)
|
||||
end
|
||||
|
||||
require_deleted_clause = true
|
||||
|
||||
if before = options[:before]
|
||||
if (before = before.to_i) > 0
|
||||
result = result.where('topics.created_at < ?', before.to_i.days.ago)
|
||||
end
|
||||
end
|
||||
|
||||
if bumped_before = options[:bumped_before]
|
||||
if (bumped_before = bumped_before.to_i) > 0
|
||||
result = result.where('topics.bumped_at < ?', bumped_before.to_i.days.ago)
|
||||
end
|
||||
end
|
||||
|
||||
if status = options[:status]
|
||||
case status
|
||||
when 'open'
|
||||
result = result.where('NOT topics.closed AND NOT topics.archived')
|
||||
when 'closed'
|
||||
result = result.where('topics.closed')
|
||||
when 'archived'
|
||||
result = result.where('topics.archived')
|
||||
when 'listed'
|
||||
result = result.where('topics.visible')
|
||||
when 'unlisted'
|
||||
result = result.where('NOT topics.visible')
|
||||
when 'deleted'
|
||||
guardian = @guardian
|
||||
if guardian.is_staff?
|
||||
result = result.where('topics.deleted_at IS NOT NULL')
|
||||
require_deleted_clause = false
|
||||
else
|
||||
# ANY of the given tags:
|
||||
result = result.joins(:tags)
|
||||
if @options[:tags][0].is_a?(Integer)
|
||||
result = result.where("tags.id in (?)", @options[:tags])
|
||||
else
|
||||
result = result.where("tags.name in (?)", @options[:tags])
|
||||
end
|
||||
end
|
||||
elsif @options[:no_tags]
|
||||
# the following will do: ("topics"."id" NOT IN (SELECT DISTINCT "topic_tags"."topic_id" FROM "topic_tags"))
|
||||
result = result.where.not(id: TopicTag.distinct.pluck(:topic_id))
|
||||
end
|
||||
|
||||
if (filter = options[:filter]) && @user
|
||||
action =
|
||||
if filter == "bookmarked"
|
||||
PostActionType.types[:bookmark]
|
||||
elsif filter == "liked"
|
||||
PostActionType.types[:like]
|
||||
end
|
||||
if action
|
||||
result = result.where('topics.id IN (SELECT pp.topic_id
|
||||
FROM post_actions pa
|
||||
JOIN posts pp ON pp.id = pa.post_id
|
||||
WHERE pa.user_id = :user_id AND
|
||||
pa.post_action_type_id = :action AND
|
||||
pa.deleted_at IS NULL
|
||||
)', user_id: @user.id,
|
||||
action: action
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
result = result.where('topics.deleted_at IS NULL') if require_deleted_clause
|
||||
result = result.where('topics.posts_count <= ?', options[:max_posts]) if options[:max_posts].present?
|
||||
result = result.where('topics.posts_count >= ?', options[:min_posts]) if options[:min_posts].present?
|
||||
|
||||
result = TopicQuery.apply_custom_filters(result, self)
|
||||
|
||||
@guardian.filter_allowed_categories(result)
|
||||
end
|
||||
|
||||
def remove_muted_topics(list, user)
|
||||
if user
|
||||
list = list.where('COALESCE(tu.notification_level,1) > :muted', muted: TopicUser.notification_levels[:muted])
|
||||
end
|
||||
result = apply_ordering(result, options)
|
||||
result = result.listable_topics.includes(:category)
|
||||
result = apply_shared_drafts(result, category_id, options)
|
||||
|
||||
if options[:exclude_category_ids] && options[:exclude_category_ids].is_a?(Array) && options[:exclude_category_ids].size > 0
|
||||
result = result.where("categories.id NOT IN (?)", options[:exclude_category_ids]).references(:categories)
|
||||
end
|
||||
|
||||
# Don't include the category topics if excluded
|
||||
if options[:no_definitions]
|
||||
result = result.where('COALESCE(categories.topic_id, 0) <> topics.id')
|
||||
end
|
||||
|
||||
result = result.limit(options[:per_page]) unless options[:limit] == false
|
||||
result = result.visible if options[:visible]
|
||||
result = result.where.not(topics: { id: options[:except_topic_ids] }).references(:topics) if options[:except_topic_ids]
|
||||
|
||||
if options[:page]
|
||||
offset = options[:page].to_i * options[:per_page]
|
||||
result = result.offset(offset) if offset > 0
|
||||
end
|
||||
|
||||
if options[:topic_ids]
|
||||
result = result.where('topics.id in (?)', options[:topic_ids]).references(:topics)
|
||||
end
|
||||
|
||||
if search = options[:search]
|
||||
result = result.where("topics.id in (select pp.topic_id from post_search_data pd join posts pp on pp.id = pd.post_id where pd.search_data @@ #{Search.ts_query(term: search.to_s)})")
|
||||
end
|
||||
|
||||
# NOTE protect against SYM attack can be removed with Ruby 2.2
|
||||
#
|
||||
state = options[:state]
|
||||
if @user && state &&
|
||||
TopicUser.notification_levels.keys.map(&:to_s).include?(state)
|
||||
level = TopicUser.notification_levels[state.to_sym]
|
||||
result = result.where('topics.id IN (
|
||||
SELECT topic_id
|
||||
FROM topic_users
|
||||
WHERE user_id = ? AND
|
||||
notification_level = ?)', @user.id, level)
|
||||
end
|
||||
|
||||
require_deleted_clause = true
|
||||
|
||||
if before = options[:before]
|
||||
if (before = before.to_i) > 0
|
||||
result = result.where('topics.created_at < ?', before.to_i.days.ago)
|
||||
end
|
||||
end
|
||||
|
||||
if bumped_before = options[:bumped_before]
|
||||
if (bumped_before = bumped_before.to_i) > 0
|
||||
result = result.where('topics.bumped_at < ?', bumped_before.to_i.days.ago)
|
||||
end
|
||||
end
|
||||
|
||||
if status = options[:status]
|
||||
case status
|
||||
when 'open'
|
||||
result = result.where('NOT topics.closed AND NOT topics.archived')
|
||||
when 'closed'
|
||||
result = result.where('topics.closed')
|
||||
when 'archived'
|
||||
result = result.where('topics.archived')
|
||||
when 'listed'
|
||||
result = result.where('topics.visible')
|
||||
when 'unlisted'
|
||||
result = result.where('NOT topics.visible')
|
||||
when 'deleted'
|
||||
guardian = @guardian
|
||||
if guardian.is_staff?
|
||||
result = result.where('topics.deleted_at IS NOT NULL')
|
||||
require_deleted_clause = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (filter = options[:filter]) && @user
|
||||
action =
|
||||
if filter == "bookmarked"
|
||||
PostActionType.types[:bookmark]
|
||||
elsif filter == "liked"
|
||||
PostActionType.types[:like]
|
||||
end
|
||||
if action
|
||||
result = result.where('topics.id IN (SELECT pp.topic_id
|
||||
FROM post_actions pa
|
||||
JOIN posts pp ON pp.id = pa.post_id
|
||||
WHERE pa.user_id = :user_id AND
|
||||
pa.post_action_type_id = :action AND
|
||||
pa.deleted_at IS NULL
|
||||
)', user_id: @user.id,
|
||||
action: action
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
result = result.where('topics.deleted_at IS NULL') if require_deleted_clause
|
||||
result = result.where('topics.posts_count <= ?', options[:max_posts]) if options[:max_posts].present?
|
||||
result = result.where('topics.posts_count >= ?', options[:min_posts]) if options[:min_posts].present?
|
||||
|
||||
result = TopicQuery.apply_custom_filters(result, self)
|
||||
|
||||
@guardian.filter_allowed_categories(result)
|
||||
end
|
||||
|
||||
def remove_muted_topics(list, user)
|
||||
if user
|
||||
list = list.where('COALESCE(tu.notification_level,1) > :muted', muted: TopicUser.notification_levels[:muted])
|
||||
end
|
||||
|
||||
list
|
||||
end
|
||||
def remove_muted_categories(list, user, opts = nil)
|
||||
category_id = get_category_id(opts[:exclude]) if opts
|
||||
|
||||
if user
|
||||
list = list.references("cu")
|
||||
.where("
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM category_users cu
|
||||
WHERE cu.user_id = :user_id
|
||||
AND cu.category_id = topics.category_id
|
||||
AND cu.notification_level = :muted
|
||||
AND cu.category_id <> :category_id
|
||||
AND (tu.notification_level IS NULL OR tu.notification_level < :tracking)
|
||||
)", user_id: user.id,
|
||||
muted: CategoryUser.notification_levels[:muted],
|
||||
tracking: TopicUser.notification_levels[:tracking],
|
||||
category_id: category_id || -1)
|
||||
end
|
||||
|
||||
list
|
||||
end
|
||||
def remove_muted_tags(list, user, opts = nil)
|
||||
if user.nil? || !SiteSetting.tagging_enabled || !SiteSetting.remove_muted_tags_from_latest
|
||||
list
|
||||
end
|
||||
def remove_muted_categories(list, user, opts = nil)
|
||||
category_id = get_category_id(opts[:exclude]) if opts
|
||||
|
||||
if user
|
||||
list = list.references("cu")
|
||||
.where("
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM category_users cu
|
||||
WHERE cu.user_id = :user_id
|
||||
AND cu.category_id = topics.category_id
|
||||
AND cu.notification_level = :muted
|
||||
AND cu.category_id <> :category_id
|
||||
AND (tu.notification_level IS NULL OR tu.notification_level < :tracking)
|
||||
)", user_id: user.id,
|
||||
muted: CategoryUser.notification_levels[:muted],
|
||||
tracking: TopicUser.notification_levels[:tracking],
|
||||
category_id: category_id || -1)
|
||||
end
|
||||
|
||||
list
|
||||
end
|
||||
def remove_muted_tags(list, user, opts = nil)
|
||||
if user.nil? || !SiteSetting.tagging_enabled || !SiteSetting.remove_muted_tags_from_latest
|
||||
else
|
||||
if !TagUser.lookup(user, :muted).exists?
|
||||
list
|
||||
else
|
||||
if !TagUser.lookup(user, :muted).exists?
|
||||
list
|
||||
showing_tag = if opts[:filter]
|
||||
f = opts[:filter].split('/')
|
||||
f[0] == 'tags' ? f[1] : nil
|
||||
else
|
||||
showing_tag = if opts[:filter]
|
||||
f = opts[:filter].split('/')
|
||||
f[0] == 'tags' ? f[1] : nil
|
||||
else
|
||||
nil
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
if TagUser.lookup(user, :muted).joins(:tag).where('tags.name = ?', showing_tag).exists?
|
||||
list # if viewing the topic list for a muted tag, show all the topics
|
||||
else
|
||||
muted_tag_ids = TagUser.lookup(user, :muted).pluck(:tag_id)
|
||||
list = list.where("
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM topic_tags tt
|
||||
WHERE tt.tag_id NOT IN (:tag_ids)
|
||||
AND tt.topic_id = topics.id
|
||||
) OR NOT EXISTS (SELECT 1 FROM topic_tags tt WHERE tt.topic_id = topics.id)", tag_ids: muted_tag_ids)
|
||||
end
|
||||
if TagUser.lookup(user, :muted).joins(:tag).where('tags.name = ?', showing_tag).exists?
|
||||
list # if viewing the topic list for a muted tag, show all the topics
|
||||
else
|
||||
muted_tag_ids = TagUser.lookup(user, :muted).pluck(:tag_id)
|
||||
list = list.where("
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM topic_tags tt
|
||||
WHERE tt.tag_id NOT IN (:tag_ids)
|
||||
AND tt.topic_id = topics.id
|
||||
) OR NOT EXISTS (SELECT 1 FROM topic_tags tt WHERE tt.topic_id = topics.id)", tag_ids: muted_tag_ids)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def new_messages(params)
|
||||
TopicQuery.new_filter(messages_for_groups_or_user(params[:my_group_ids]), Time.at(SiteSetting.min_new_topics_time).to_datetime)
|
||||
.limit(params[:count])
|
||||
end
|
||||
def new_messages(params)
|
||||
TopicQuery.new_filter(messages_for_groups_or_user(params[:my_group_ids]), Time.at(SiteSetting.min_new_topics_time).to_datetime)
|
||||
.limit(params[:count])
|
||||
end
|
||||
|
||||
def unread_messages(params)
|
||||
TopicQuery.unread_filter(
|
||||
messages_for_groups_or_user(params[:my_group_ids]),
|
||||
@user&.id,
|
||||
staff: @user&.staff?)
|
||||
.limit(params[:count])
|
||||
end
|
||||
def unread_messages(params)
|
||||
TopicQuery.unread_filter(
|
||||
messages_for_groups_or_user(params[:my_group_ids]),
|
||||
@user&.id,
|
||||
staff: @user&.staff?)
|
||||
.limit(params[:count])
|
||||
end
|
||||
|
||||
def related_messages_user(params)
|
||||
messages = messages_for_user.limit(params[:count])
|
||||
messages = allowed_messages(messages, params)
|
||||
end
|
||||
def related_messages_user(params)
|
||||
messages = messages_for_user.limit(params[:count])
|
||||
messages = allowed_messages(messages, params)
|
||||
end
|
||||
|
||||
def related_messages_group(params)
|
||||
messages = messages_for_groups_or_user(params[:my_group_ids]).limit(params[:count])
|
||||
messages = allowed_messages(messages, params)
|
||||
end
|
||||
def related_messages_group(params)
|
||||
messages = messages_for_groups_or_user(params[:my_group_ids]).limit(params[:count])
|
||||
messages = allowed_messages(messages, params)
|
||||
end
|
||||
|
||||
def allowed_messages(messages, params)
|
||||
user_ids = (params[:target_user_ids] || [])
|
||||
group_ids = ((params[:target_group_ids] - params[:my_group_ids]) || [])
|
||||
|
||||
if user_ids.present?
|
||||
messages =
|
||||
messages.joins("
|
||||
LEFT JOIN topic_allowed_users ta2
|
||||
ON topics.id = ta2.topic_id
|
||||
AND ta2.user_id IN (#{sanitize_sql_array(user_ids)})
|
||||
")
|
||||
end
|
||||
|
||||
if group_ids.present?
|
||||
messages =
|
||||
messages.joins("
|
||||
LEFT JOIN topic_allowed_groups tg2
|
||||
ON topics.id = tg2.topic_id
|
||||
AND tg2.group_id IN (#{sanitize_sql_array(group_ids)})
|
||||
")
|
||||
end
|
||||
def allowed_messages(messages, params)
|
||||
user_ids = (params[:target_user_ids] || [])
|
||||
group_ids = ((params[:target_group_ids] - params[:my_group_ids]) || [])
|
||||
|
||||
if user_ids.present?
|
||||
messages =
|
||||
if user_ids.present? && group_ids.present?
|
||||
messages.where("ta2.topic_id IS NOT NULL OR tg2.topic_id IS NOT NULL")
|
||||
elsif user_ids.present?
|
||||
messages.where("ta2.topic_id IS NOT NULL")
|
||||
elsif group_ids.present?
|
||||
messages.where("tg2.topic_id IS NOT NULL")
|
||||
end
|
||||
messages.joins("
|
||||
LEFT JOIN topic_allowed_users ta2
|
||||
ON topics.id = ta2.topic_id
|
||||
AND ta2.user_id IN (#{sanitize_sql_array(user_ids)})
|
||||
")
|
||||
end
|
||||
|
||||
def messages_for_groups_or_user(group_ids)
|
||||
if group_ids.present?
|
||||
base_messages
|
||||
.joins("
|
||||
LEFT JOIN (
|
||||
SELECT * FROM topic_allowed_groups _tg
|
||||
LEFT JOIN group_users gu
|
||||
ON gu.user_id = #{@user.id.to_i}
|
||||
AND gu.group_id = _tg.group_id
|
||||
WHERE gu.group_id IN (#{sanitize_sql_array(group_ids)})
|
||||
) tg ON topics.id = tg.topic_id
|
||||
")
|
||||
.where("tg.topic_id IS NOT NULL")
|
||||
else
|
||||
messages_for_user
|
||||
if group_ids.present?
|
||||
messages =
|
||||
messages.joins("
|
||||
LEFT JOIN topic_allowed_groups tg2
|
||||
ON topics.id = tg2.topic_id
|
||||
AND tg2.group_id IN (#{sanitize_sql_array(group_ids)})
|
||||
")
|
||||
end
|
||||
|
||||
messages =
|
||||
if user_ids.present? && group_ids.present?
|
||||
messages.where("ta2.topic_id IS NOT NULL OR tg2.topic_id IS NOT NULL")
|
||||
elsif user_ids.present?
|
||||
messages.where("ta2.topic_id IS NOT NULL")
|
||||
elsif group_ids.present?
|
||||
messages.where("tg2.topic_id IS NOT NULL")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def messages_for_user
|
||||
def messages_for_groups_or_user(group_ids)
|
||||
if group_ids.present?
|
||||
base_messages
|
||||
.joins("
|
||||
LEFT JOIN topic_allowed_users ta
|
||||
ON topics.id = ta.topic_id
|
||||
AND ta.user_id = #{@user.id.to_i}
|
||||
LEFT JOIN (
|
||||
SELECT * FROM topic_allowed_groups _tg
|
||||
LEFT JOIN group_users gu
|
||||
ON gu.user_id = #{@user.id.to_i}
|
||||
AND gu.group_id = _tg.group_id
|
||||
WHERE gu.group_id IN (#{sanitize_sql_array(group_ids)})
|
||||
) tg ON topics.id = tg.topic_id
|
||||
")
|
||||
.where("ta.topic_id IS NOT NULL")
|
||||
.where("tg.topic_id IS NOT NULL")
|
||||
else
|
||||
messages_for_user
|
||||
end
|
||||
end
|
||||
|
||||
def messages_for_user
|
||||
base_messages
|
||||
.joins("
|
||||
LEFT JOIN topic_allowed_users ta
|
||||
ON topics.id = ta.topic_id
|
||||
AND ta.user_id = #{@user.id.to_i}
|
||||
")
|
||||
.where("ta.topic_id IS NOT NULL")
|
||||
end
|
||||
|
||||
def base_messages
|
||||
query = Topic
|
||||
.where('topics.archetype = ?', Archetype.private_message)
|
||||
.joins("LEFT JOIN topic_users tu ON topics.id = tu.topic_id AND tu.user_id = #{@user.id.to_i}")
|
||||
|
||||
query = query.includes(:tags) if SiteSetting.tagging_enabled
|
||||
query.order('topics.bumped_at DESC')
|
||||
end
|
||||
|
||||
def random_suggested(topic, count, excluded_topic_ids = [])
|
||||
result = default_results(unordered: true, per_page: count).where(closed: false, archived: false)
|
||||
|
||||
if SiteSetting.limit_suggested_to_category
|
||||
excluded_topic_ids += Category.where(id: topic.category_id).pluck(:id)
|
||||
else
|
||||
excluded_topic_ids += Category.topic_ids.to_a
|
||||
end
|
||||
result = result.where("topics.id NOT IN (?)", excluded_topic_ids) unless excluded_topic_ids.empty?
|
||||
|
||||
result = remove_muted_categories(result, @user)
|
||||
|
||||
# If we are in a category, prefer it for the random results
|
||||
if topic.category_id
|
||||
result = result.order("CASE WHEN topics.category_id = #{topic.category_id.to_i} THEN 0 ELSE 1 END")
|
||||
end
|
||||
|
||||
def base_messages
|
||||
query = Topic
|
||||
.where('topics.archetype = ?', Archetype.private_message)
|
||||
.joins("LEFT JOIN topic_users tu ON topics.id = tu.topic_id AND tu.user_id = #{@user.id.to_i}")
|
||||
# Best effort, it over selects, however if you have a high number
|
||||
# of muted categories there is tiny chance we will not select enough
|
||||
# in particular this can happen if current category is empty and tons
|
||||
# of muted, big edge case
|
||||
#
|
||||
# we over select in case cache is stale
|
||||
max = (count * 1.3).to_i
|
||||
ids = SiteSetting.limit_suggested_to_category ? [] : RandomTopicSelector.next(max)
|
||||
ids.concat(RandomTopicSelector.next(max, topic.category))
|
||||
|
||||
query = query.includes(:tags) if SiteSetting.tagging_enabled
|
||||
query.order('topics.bumped_at DESC')
|
||||
result.where(id: ids.uniq)
|
||||
end
|
||||
|
||||
def suggested_ordering(result, options)
|
||||
# Prefer unread in the same category
|
||||
if options[:topic] && options[:topic].category_id
|
||||
result = result.order("CASE WHEN topics.category_id = #{options[:topic].category_id.to_i} THEN 0 ELSE 1 END")
|
||||
end
|
||||
|
||||
def random_suggested(topic, count, excluded_topic_ids = [])
|
||||
result = default_results(unordered: true, per_page: count).where(closed: false, archived: false)
|
||||
|
||||
if SiteSetting.limit_suggested_to_category
|
||||
excluded_topic_ids += Category.where(id: topic.category_id).pluck(:id)
|
||||
else
|
||||
excluded_topic_ids += Category.topic_ids.to_a
|
||||
end
|
||||
result = result.where("topics.id NOT IN (?)", excluded_topic_ids) unless excluded_topic_ids.empty?
|
||||
|
||||
result = remove_muted_categories(result, @user)
|
||||
|
||||
# If we are in a category, prefer it for the random results
|
||||
if topic.category_id
|
||||
result = result.order("CASE WHEN topics.category_id = #{topic.category_id.to_i} THEN 0 ELSE 1 END")
|
||||
end
|
||||
|
||||
# Best effort, it over selects, however if you have a high number
|
||||
# of muted categories there is tiny chance we will not select enough
|
||||
# in particular this can happen if current category is empty and tons
|
||||
# of muted, big edge case
|
||||
#
|
||||
# we over select in case cache is stale
|
||||
max = (count * 1.3).to_i
|
||||
ids = SiteSetting.limit_suggested_to_category ? [] : RandomTopicSelector.next(max)
|
||||
ids.concat(RandomTopicSelector.next(max, topic.category))
|
||||
|
||||
result.where(id: ids.uniq)
|
||||
end
|
||||
|
||||
def suggested_ordering(result, options)
|
||||
# Prefer unread in the same category
|
||||
if options[:topic] && options[:topic].category_id
|
||||
result = result.order("CASE WHEN topics.category_id = #{options[:topic].category_id.to_i} THEN 0 ELSE 1 END")
|
||||
end
|
||||
|
||||
result.order('topics.bumped_at DESC')
|
||||
end
|
||||
result.order('topics.bumped_at DESC')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sanitize_sql_array(input)
|
||||
ActiveRecord::Base.send(:sanitize_sql_array, input.join(','))
|
||||
end
|
||||
def sanitize_sql_array(input)
|
||||
ActiveRecord::Base.send(:sanitize_sql_array, input.join(','))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,48 +12,48 @@ class TopicRetriever
|
|||
|
||||
private
|
||||
|
||||
def invalid_url?
|
||||
!EmbeddableHost.url_allowed?(@embed_url)
|
||||
def invalid_url?
|
||||
!EmbeddableHost.url_allowed?(@embed_url)
|
||||
end
|
||||
|
||||
def retrieved_recently?
|
||||
# We can disable the throttle for some users, such as staff
|
||||
return false if @opts[:no_throttle]
|
||||
|
||||
# Throttle other users to once every 60 seconds
|
||||
retrieved_key = "retrieved_topic"
|
||||
if $redis.setnx(retrieved_key, "1")
|
||||
$redis.expire(retrieved_key, 60)
|
||||
return false
|
||||
end
|
||||
|
||||
def retrieved_recently?
|
||||
# We can disable the throttle for some users, such as staff
|
||||
return false if @opts[:no_throttle]
|
||||
true
|
||||
end
|
||||
|
||||
# Throttle other users to once every 60 seconds
|
||||
retrieved_key = "retrieved_topic"
|
||||
if $redis.setnx(retrieved_key, "1")
|
||||
$redis.expire(retrieved_key, 60)
|
||||
return false
|
||||
end
|
||||
def perform_retrieve
|
||||
# It's possible another process or job found the embed already. So if that happened bail out.
|
||||
return if TopicEmbed.where(embed_url: @embed_url).exists?
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def perform_retrieve
|
||||
# It's possible another process or job found the embed already. So if that happened bail out.
|
||||
# First check RSS if that is enabled
|
||||
if SiteSetting.feed_polling_enabled?
|
||||
Jobs::PollFeed.new.execute({})
|
||||
return if TopicEmbed.where(embed_url: @embed_url).exists?
|
||||
|
||||
# First check RSS if that is enabled
|
||||
if SiteSetting.feed_polling_enabled?
|
||||
Jobs::PollFeed.new.execute({})
|
||||
return if TopicEmbed.where(embed_url: @embed_url).exists?
|
||||
end
|
||||
|
||||
fetch_http
|
||||
end
|
||||
|
||||
def fetch_http
|
||||
if @author_username.nil?
|
||||
username = SiteSetting.embed_by_username.downcase
|
||||
else
|
||||
username = @author_username
|
||||
end
|
||||
fetch_http
|
||||
end
|
||||
|
||||
user = User.where(username_lower: username.downcase).first
|
||||
return if user.blank?
|
||||
|
||||
TopicEmbed.import_remote(user, @embed_url)
|
||||
def fetch_http
|
||||
if @author_username.nil?
|
||||
username = SiteSetting.embed_by_username.downcase
|
||||
else
|
||||
username = @author_username
|
||||
end
|
||||
|
||||
user = User.where(username_lower: username.downcase).first
|
||||
return if user.blank?
|
||||
|
||||
TopicEmbed.import_remote(user, @embed_url)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -27,156 +27,156 @@ class TopicsBulkAction
|
|||
|
||||
private
|
||||
|
||||
def find_group
|
||||
return unless @options[:group]
|
||||
def find_group
|
||||
return unless @options[:group]
|
||||
|
||||
group = Group.where('name ilike ?', @options[:group]).first
|
||||
raise Discourse::InvalidParameters.new(:group) unless group
|
||||
unless group.group_users.where(user_id: @user.id).exists?
|
||||
raise Discourse::InvalidParameters.new(:group)
|
||||
end
|
||||
group
|
||||
group = Group.where('name ilike ?', @options[:group]).first
|
||||
raise Discourse::InvalidParameters.new(:group) unless group
|
||||
unless group.group_users.where(user_id: @user.id).exists?
|
||||
raise Discourse::InvalidParameters.new(:group)
|
||||
end
|
||||
group
|
||||
end
|
||||
|
||||
def move_messages_to_inbox
|
||||
group = find_group
|
||||
topics.each do |t|
|
||||
if guardian.can_see?(t) && t.private_message?
|
||||
if group
|
||||
GroupArchivedMessage.move_to_inbox!(group.id, t)
|
||||
else
|
||||
UserArchivedMessage.move_to_inbox!(@user.id, t)
|
||||
end
|
||||
def move_messages_to_inbox
|
||||
group = find_group
|
||||
topics.each do |t|
|
||||
if guardian.can_see?(t) && t.private_message?
|
||||
if group
|
||||
GroupArchivedMessage.move_to_inbox!(group.id, t)
|
||||
else
|
||||
UserArchivedMessage.move_to_inbox!(@user.id, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def archive_messages
|
||||
group = find_group
|
||||
topics.each do |t|
|
||||
if guardian.can_see?(t) && t.private_message?
|
||||
if group
|
||||
GroupArchivedMessage.archive!(group.id, t)
|
||||
else
|
||||
UserArchivedMessage.archive!(@user.id, t)
|
||||
end
|
||||
def archive_messages
|
||||
group = find_group
|
||||
topics.each do |t|
|
||||
if guardian.can_see?(t) && t.private_message?
|
||||
if group
|
||||
GroupArchivedMessage.archive!(group.id, t)
|
||||
else
|
||||
UserArchivedMessage.archive!(@user.id, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def dismiss_posts
|
||||
sql = "
|
||||
UPDATE topic_users tu
|
||||
SET highest_seen_post_number = t.highest_post_number , last_read_post_number = highest_post_number
|
||||
FROM topics t
|
||||
WHERE t.id = tu.topic_id AND tu.user_id = :user_id AND t.id IN (:topic_ids)
|
||||
"
|
||||
def dismiss_posts
|
||||
sql = "
|
||||
UPDATE topic_users tu
|
||||
SET highest_seen_post_number = t.highest_post_number , last_read_post_number = highest_post_number
|
||||
FROM topics t
|
||||
WHERE t.id = tu.topic_id AND tu.user_id = :user_id AND t.id IN (:topic_ids)
|
||||
"
|
||||
|
||||
Topic.exec_sql(sql, user_id: @user.id, topic_ids: @topic_ids)
|
||||
@changed_ids.concat @topic_ids
|
||||
end
|
||||
Topic.exec_sql(sql, user_id: @user.id, topic_ids: @topic_ids)
|
||||
@changed_ids.concat @topic_ids
|
||||
end
|
||||
|
||||
def reset_read
|
||||
PostTiming.destroy_for(@user.id, @topic_ids)
|
||||
end
|
||||
def reset_read
|
||||
PostTiming.destroy_for(@user.id, @topic_ids)
|
||||
end
|
||||
|
||||
def change_category
|
||||
topics.each do |t|
|
||||
if guardian.can_edit?(t)
|
||||
@changed_ids << t.id if t.change_category_to_id(@operation[:category_id])
|
||||
end
|
||||
def change_category
|
||||
topics.each do |t|
|
||||
if guardian.can_edit?(t)
|
||||
@changed_ids << t.id if t.change_category_to_id(@operation[:category_id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def change_notification_level
|
||||
topics.each do |t|
|
||||
if guardian.can_see?(t)
|
||||
TopicUser.change(@user, t.id, notification_level: @operation[:notification_level_id].to_i)
|
||||
@changed_ids << t.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def close
|
||||
topics.each do |t|
|
||||
if guardian.can_moderate?(t)
|
||||
t.update_status('closed', true, @user)
|
||||
@changed_ids << t.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unlist
|
||||
topics.each do |t|
|
||||
if guardian.can_moderate?(t)
|
||||
t.update_status('visible', false, @user)
|
||||
@changed_ids << t.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def relist
|
||||
topics.each do |t|
|
||||
if guardian.can_moderate?(t)
|
||||
t.update_status('visible', true, @user)
|
||||
@changed_ids << t.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def archive
|
||||
topics.each do |t|
|
||||
if guardian.can_moderate?(t)
|
||||
t.update_status('archived', true, @user)
|
||||
@changed_ids << t.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def delete
|
||||
topics.each do |t|
|
||||
if guardian.can_delete?(t)
|
||||
PostDestroyer.new(@user, t.ordered_posts.first).destroy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def change_tags
|
||||
tags = @operation[:tags]
|
||||
tags = DiscourseTagging.tags_for_saving(tags, guardian) if tags.present?
|
||||
|
||||
topics.each do |t|
|
||||
if guardian.can_edit?(t)
|
||||
if tags.present?
|
||||
DiscourseTagging.tag_topic_by_names(t, guardian, tags)
|
||||
else
|
||||
t.tags = []
|
||||
end
|
||||
@changed_ids << t.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def append_tags
|
||||
tags = @operation[:tags]
|
||||
tags = DiscourseTagging.tags_for_saving(tags, guardian) if tags.present?
|
||||
|
||||
topics.each do |t|
|
||||
if guardian.can_edit?(t)
|
||||
if tags.present?
|
||||
DiscourseTagging.tag_topic_by_names(t, guardian, tags, append: true)
|
||||
end
|
||||
def change_notification_level
|
||||
topics.each do |t|
|
||||
if guardian.can_see?(t)
|
||||
TopicUser.change(@user, t.id, notification_level: @operation[:notification_level_id].to_i)
|
||||
@changed_ids << t.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def guardian
|
||||
@guardian ||= Guardian.new(@user)
|
||||
def close
|
||||
topics.each do |t|
|
||||
if guardian.can_moderate?(t)
|
||||
t.update_status('closed', true, @user)
|
||||
@changed_ids << t.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def topics
|
||||
@topics ||= Topic.where(id: @topic_ids)
|
||||
def unlist
|
||||
topics.each do |t|
|
||||
if guardian.can_moderate?(t)
|
||||
t.update_status('visible', false, @user)
|
||||
@changed_ids << t.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def relist
|
||||
topics.each do |t|
|
||||
if guardian.can_moderate?(t)
|
||||
t.update_status('visible', true, @user)
|
||||
@changed_ids << t.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def archive
|
||||
topics.each do |t|
|
||||
if guardian.can_moderate?(t)
|
||||
t.update_status('archived', true, @user)
|
||||
@changed_ids << t.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def delete
|
||||
topics.each do |t|
|
||||
if guardian.can_delete?(t)
|
||||
PostDestroyer.new(@user, t.ordered_posts.first).destroy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def change_tags
|
||||
tags = @operation[:tags]
|
||||
tags = DiscourseTagging.tags_for_saving(tags, guardian) if tags.present?
|
||||
|
||||
topics.each do |t|
|
||||
if guardian.can_edit?(t)
|
||||
if tags.present?
|
||||
DiscourseTagging.tag_topic_by_names(t, guardian, tags)
|
||||
else
|
||||
t.tags = []
|
||||
end
|
||||
@changed_ids << t.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def append_tags
|
||||
tags = @operation[:tags]
|
||||
tags = DiscourseTagging.tags_for_saving(tags, guardian) if tags.present?
|
||||
|
||||
topics.each do |t|
|
||||
if guardian.can_edit?(t)
|
||||
if tags.present?
|
||||
DiscourseTagging.tag_topic_by_names(t, guardian, tags, append: true)
|
||||
end
|
||||
@changed_ids << t.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def guardian
|
||||
@guardian ||= Guardian.new(@user)
|
||||
end
|
||||
|
||||
def topics
|
||||
@topics ||= Topic.where(id: @topic_ids)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -10,23 +10,23 @@ class CensoredWordsValidator < ActiveModel::EachValidator
|
|||
|
||||
private
|
||||
|
||||
def censor_words(value, regexp)
|
||||
censored_words = value.scan(regexp)
|
||||
censored_words.flatten!
|
||||
censored_words.compact!
|
||||
censored_words.map!(&:strip)
|
||||
censored_words.select!(&:present?)
|
||||
censored_words.uniq!
|
||||
censored_words
|
||||
end
|
||||
def censor_words(value, regexp)
|
||||
censored_words = value.scan(regexp)
|
||||
censored_words.flatten!
|
||||
censored_words.compact!
|
||||
censored_words.map!(&:strip)
|
||||
censored_words.select!(&:present?)
|
||||
censored_words.uniq!
|
||||
censored_words
|
||||
end
|
||||
|
||||
def join_censored_words(censored_words)
|
||||
censored_words.map!(&:downcase)
|
||||
censored_words.uniq!
|
||||
censored_words.join(", ".freeze)
|
||||
end
|
||||
def join_censored_words(censored_words)
|
||||
censored_words.map!(&:downcase)
|
||||
censored_words.uniq!
|
||||
censored_words.join(", ".freeze)
|
||||
end
|
||||
|
||||
def censored_words_regexp
|
||||
WordWatcher.word_matcher_regexp :censor
|
||||
end
|
||||
def censored_words_regexp
|
||||
WordWatcher.word_matcher_regexp :censor
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,15 +30,15 @@ class POP3PollingEnabledSettingValidator
|
|||
|
||||
private
|
||||
|
||||
def authentication_works?
|
||||
@authentication_works ||= begin
|
||||
pop3 = Net::POP3.new(SiteSetting.pop3_polling_host, SiteSetting.pop3_polling_port)
|
||||
pop3.enable_ssl(OpenSSL::SSL::VERIFY_NONE) if SiteSetting.pop3_polling_ssl
|
||||
pop3.auth_only(SiteSetting.pop3_polling_username, SiteSetting.pop3_polling_password)
|
||||
rescue Net::POPAuthenticationError
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
def authentication_works?
|
||||
@authentication_works ||= begin
|
||||
pop3 = Net::POP3.new(SiteSetting.pop3_polling_host, SiteSetting.pop3_polling_port)
|
||||
pop3.enable_ssl(OpenSSL::SSL::VERIFY_NONE) if SiteSetting.pop3_polling_ssl
|
||||
pop3.auth_only(SiteSetting.pop3_polling_username, SiteSetting.pop3_polling_password)
|
||||
rescue Net::POPAuthenticationError
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,17 +6,17 @@ class TopicTitleLengthValidator < ActiveModel::EachValidator
|
|||
|
||||
private
|
||||
|
||||
def title_validator(record)
|
||||
length_range =
|
||||
if record.user.try(:admin?)
|
||||
1..SiteSetting.max_topic_title_length
|
||||
elsif record.private_message?
|
||||
SiteSetting.private_message_title_length
|
||||
else
|
||||
SiteSetting.topic_title_length
|
||||
end
|
||||
def title_validator(record)
|
||||
length_range =
|
||||
if record.user.try(:admin?)
|
||||
1..SiteSetting.max_topic_title_length
|
||||
elsif record.private_message?
|
||||
SiteSetting.private_message_title_length
|
||||
else
|
||||
SiteSetting.topic_title_length
|
||||
end
|
||||
|
||||
ActiveModel::Validations::LengthValidator.new(attributes: :title, in: length_range, allow_blank: true)
|
||||
end
|
||||
ActiveModel::Validations::LengthValidator.new(attributes: :title, in: length_range, allow_blank: true)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -563,49 +563,49 @@ module DiscourseNarrativeBot
|
|||
|
||||
private
|
||||
|
||||
def name
|
||||
@user.username.titleize
|
||||
end
|
||||
def name
|
||||
@user.username.titleize
|
||||
end
|
||||
|
||||
def logo_group(size, width, height)
|
||||
return unless SiteSetting.logo_small_url.present?
|
||||
def logo_group(size, width, height)
|
||||
return unless SiteSetting.logo_small_url.present?
|
||||
|
||||
begin
|
||||
uri = URI(SiteSetting.logo_small_url)
|
||||
begin
|
||||
uri = URI(SiteSetting.logo_small_url)
|
||||
|
||||
logo_uri =
|
||||
if uri.host.blank? || uri.scheme.blank?
|
||||
URI("#{Discourse.base_url}/#{uri.path}")
|
||||
else
|
||||
uri
|
||||
end
|
||||
logo_uri =
|
||||
if uri.host.blank? || uri.scheme.blank?
|
||||
URI("#{Discourse.base_url}/#{uri.path}")
|
||||
else
|
||||
uri
|
||||
end
|
||||
|
||||
<<~URL
|
||||
<<~URL
|
||||
<g transform="translate(#{width / 2 - (size / 2)} #{height})">
|
||||
<image height="#{size}px" width="#{size}px" #{base64_image_link(logo_uri)}/>
|
||||
</g>
|
||||
URL
|
||||
rescue URI::InvalidURIError
|
||||
''
|
||||
end
|
||||
rescue URI::InvalidURIError
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
def base64_image_link(url)
|
||||
if image = fetch_image(url)
|
||||
"xlink:href=\"data:image/png;base64,#{Base64.strict_encode64(image)}\""
|
||||
else
|
||||
""
|
||||
end
|
||||
def base64_image_link(url)
|
||||
if image = fetch_image(url)
|
||||
"xlink:href=\"data:image/png;base64,#{Base64.strict_encode64(image)}\""
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_image(url)
|
||||
URI(url).open('rb', redirect: true, allow_redirections: :all).read
|
||||
rescue OpenURI::HTTPError
|
||||
# Ignore if fetching image returns a non 200 response
|
||||
end
|
||||
def fetch_image(url)
|
||||
URI(url).open('rb', redirect: true, allow_redirections: :all).read
|
||||
rescue OpenURI::HTTPError
|
||||
# Ignore if fetching image returns a non 200 response
|
||||
end
|
||||
|
||||
def avatar_url
|
||||
UrlHelper.absolute(Discourse.base_uri + @user.avatar_template.gsub('{size}', '250'))
|
||||
end
|
||||
def avatar_url
|
||||
UrlHelper.absolute(Discourse.base_uri + @user.avatar_template.gsub('{size}', '250'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,102 +8,102 @@ class QuandoraQuestion
|
|||
@question = JSON.parse question_json
|
||||
end
|
||||
|
||||
def topic
|
||||
topic = {}
|
||||
topic[:id] = @question['uid']
|
||||
topic[:author_id] = @question['author']['uid']
|
||||
topic[:title] = unescape @question['title']
|
||||
topic[:raw] = unescape @question['content']
|
||||
topic[:created_at] = Time.parse @question['created']
|
||||
topic
|
||||
end
|
||||
def topic
|
||||
topic = {}
|
||||
topic[:id] = @question['uid']
|
||||
topic[:author_id] = @question['author']['uid']
|
||||
topic[:title] = unescape @question['title']
|
||||
topic[:raw] = unescape @question['content']
|
||||
topic[:created_at] = Time.parse @question['created']
|
||||
topic
|
||||
end
|
||||
|
||||
def users
|
||||
users = {}
|
||||
user = user_from_author @question['author']
|
||||
users[user[:id]] = user
|
||||
replies.each do |reply|
|
||||
user = user_from_author reply[:author]
|
||||
users[user[:id]] = user
|
||||
end
|
||||
users.values.to_a
|
||||
end
|
||||
def users
|
||||
users = {}
|
||||
user = user_from_author @question['author']
|
||||
users[user[:id]] = user
|
||||
replies.each do |reply|
|
||||
user = user_from_author reply[:author]
|
||||
users[user[:id]] = user
|
||||
end
|
||||
users.values.to_a
|
||||
end
|
||||
|
||||
def user_from_author(author)
|
||||
email = author['email']
|
||||
email = "#{author['uid']}@noemail.com" unless email
|
||||
def user_from_author(author)
|
||||
email = author['email']
|
||||
email = "#{author['uid']}@noemail.com" unless email
|
||||
|
||||
user = {}
|
||||
user[:id] = author['uid']
|
||||
user[:name] = "#{author['firstName']} #{author['lastName']}"
|
||||
user[:email] = email
|
||||
user[:staged] = true
|
||||
user
|
||||
end
|
||||
user = {}
|
||||
user[:id] = author['uid']
|
||||
user[:name] = "#{author['firstName']} #{author['lastName']}"
|
||||
user[:email] = email
|
||||
user[:staged] = true
|
||||
user
|
||||
end
|
||||
|
||||
def replies
|
||||
posts = []
|
||||
answers = @question['answersList']
|
||||
comments = @question['comments']
|
||||
comments.each_with_index do |comment, i|
|
||||
posts << post_from_comment(comment, i, @question)
|
||||
end
|
||||
answers.each do |answer|
|
||||
posts << post_from_answer(answer)
|
||||
comments = answer['comments']
|
||||
comments.each_with_index do |comment, i|
|
||||
posts << post_from_comment(comment, i, answer)
|
||||
end
|
||||
end
|
||||
order_replies posts
|
||||
end
|
||||
def replies
|
||||
posts = []
|
||||
answers = @question['answersList']
|
||||
comments = @question['comments']
|
||||
comments.each_with_index do |comment, i|
|
||||
posts << post_from_comment(comment, i, @question)
|
||||
end
|
||||
answers.each do |answer|
|
||||
posts << post_from_answer(answer)
|
||||
comments = answer['comments']
|
||||
comments.each_with_index do |comment, i|
|
||||
posts << post_from_comment(comment, i, answer)
|
||||
end
|
||||
end
|
||||
order_replies posts
|
||||
end
|
||||
|
||||
def order_replies(posts)
|
||||
posts = posts.sort_by { |p| p[:created_at] }
|
||||
posts.each_with_index do |p, i|
|
||||
p[:post_number] = i + 2
|
||||
end
|
||||
posts.each do |p|
|
||||
parent = posts.select { |pp| pp[:id] == p[:parent_id] }
|
||||
p[:reply_to_post_number] = parent[0][:post_number] if parent.size > 0
|
||||
end
|
||||
posts
|
||||
end
|
||||
def order_replies(posts)
|
||||
posts = posts.sort_by { |p| p[:created_at] }
|
||||
posts.each_with_index do |p, i|
|
||||
p[:post_number] = i + 2
|
||||
end
|
||||
posts.each do |p|
|
||||
parent = posts.select { |pp| pp[:id] == p[:parent_id] }
|
||||
p[:reply_to_post_number] = parent[0][:post_number] if parent.size > 0
|
||||
end
|
||||
posts
|
||||
end
|
||||
|
||||
def post_from_answer(answer)
|
||||
post = {}
|
||||
post[:id] = answer['uid']
|
||||
post[:parent_id] = @question['uid']
|
||||
post[:author] = answer['author']
|
||||
post[:author_id] = answer['author']['uid']
|
||||
post[:raw] = unescape answer['content']
|
||||
post[:created_at] = Time.parse answer['created']
|
||||
post
|
||||
end
|
||||
def post_from_answer(answer)
|
||||
post = {}
|
||||
post[:id] = answer['uid']
|
||||
post[:parent_id] = @question['uid']
|
||||
post[:author] = answer['author']
|
||||
post[:author_id] = answer['author']['uid']
|
||||
post[:raw] = unescape answer['content']
|
||||
post[:created_at] = Time.parse answer['created']
|
||||
post
|
||||
end
|
||||
|
||||
def post_from_comment(comment, index, parent)
|
||||
if comment['created']
|
||||
created_at = Time.parse comment['created']
|
||||
else
|
||||
created_at = Time.parse parent['created']
|
||||
end
|
||||
parent_id = parent['uid']
|
||||
parent_id = "#{parent['uid']}-#{index - 1}" if index > 0
|
||||
post = {}
|
||||
id = "#{parent['uid']}-#{index}"
|
||||
post[:id] = id
|
||||
post[:parent_id] = parent_id
|
||||
post[:author] = comment['author']
|
||||
post[:author_id] = comment['author']['uid']
|
||||
post[:raw] = unescape comment['text']
|
||||
post[:created_at] = created_at
|
||||
post
|
||||
end
|
||||
def post_from_comment(comment, index, parent)
|
||||
if comment['created']
|
||||
created_at = Time.parse comment['created']
|
||||
else
|
||||
created_at = Time.parse parent['created']
|
||||
end
|
||||
parent_id = parent['uid']
|
||||
parent_id = "#{parent['uid']}-#{index - 1}" if index > 0
|
||||
post = {}
|
||||
id = "#{parent['uid']}-#{index}"
|
||||
post[:id] = id
|
||||
post[:parent_id] = parent_id
|
||||
post[:author] = comment['author']
|
||||
post[:author_id] = comment['author']['uid']
|
||||
post[:raw] = unescape comment['text']
|
||||
post[:created_at] = created_at
|
||||
post
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unescape(html)
|
||||
return nil unless html
|
||||
CGI.unescapeHTML html
|
||||
end
|
||||
def unescape(html)
|
||||
return nil unless html
|
||||
CGI.unescapeHTML html
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,81 +18,81 @@ class SocialcastMessage
|
|||
}
|
||||
}
|
||||
|
||||
def initialize(message_json)
|
||||
@parsed_json = JSON.parse message_json
|
||||
end
|
||||
def initialize(message_json)
|
||||
@parsed_json = JSON.parse message_json
|
||||
end
|
||||
|
||||
def topic
|
||||
topic = {}
|
||||
topic[:id] = @parsed_json['id']
|
||||
topic[:author_id] = @parsed_json['user']['id']
|
||||
topic[:title] = title
|
||||
topic[:raw] = @parsed_json['body']
|
||||
topic[:created_at] = Time.parse @parsed_json['created_at']
|
||||
topic[:tags] = tags
|
||||
topic[:category] = category
|
||||
topic
|
||||
end
|
||||
def topic
|
||||
topic = {}
|
||||
topic[:id] = @parsed_json['id']
|
||||
topic[:author_id] = @parsed_json['user']['id']
|
||||
topic[:title] = title
|
||||
topic[:raw] = @parsed_json['body']
|
||||
topic[:created_at] = Time.parse @parsed_json['created_at']
|
||||
topic[:tags] = tags
|
||||
topic[:category] = category
|
||||
topic
|
||||
end
|
||||
|
||||
def title
|
||||
CreateTitle.from_body @parsed_json['body']
|
||||
end
|
||||
def title
|
||||
CreateTitle.from_body @parsed_json['body']
|
||||
end
|
||||
|
||||
def tags
|
||||
tags = []
|
||||
if group
|
||||
if TAGS_AND_CATEGORIES[group]
|
||||
tags = TAGS_AND_CATEGORIES[group][:tags]
|
||||
else
|
||||
tags << group
|
||||
end
|
||||
end
|
||||
tags << DEFAULT_TAG
|
||||
tags
|
||||
end
|
||||
def tags
|
||||
tags = []
|
||||
if group
|
||||
if TAGS_AND_CATEGORIES[group]
|
||||
tags = TAGS_AND_CATEGORIES[group][:tags]
|
||||
else
|
||||
tags << group
|
||||
end
|
||||
end
|
||||
tags << DEFAULT_TAG
|
||||
tags
|
||||
end
|
||||
|
||||
def category
|
||||
category = DEFAULT_CATEGORY
|
||||
if group && TAGS_AND_CATEGORIES[group]
|
||||
category = TAGS_AND_CATEGORIES[group][:category]
|
||||
end
|
||||
category
|
||||
end
|
||||
def category
|
||||
category = DEFAULT_CATEGORY
|
||||
if group && TAGS_AND_CATEGORIES[group]
|
||||
category = TAGS_AND_CATEGORIES[group][:category]
|
||||
end
|
||||
category
|
||||
end
|
||||
|
||||
def group
|
||||
@parsed_json['group']['groupname'].downcase if @parsed_json['group'] && @parsed_json['group']['groupname']
|
||||
end
|
||||
def group
|
||||
@parsed_json['group']['groupname'].downcase if @parsed_json['group'] && @parsed_json['group']['groupname']
|
||||
end
|
||||
|
||||
def url
|
||||
@parsed_json['url']
|
||||
end
|
||||
def url
|
||||
@parsed_json['url']
|
||||
end
|
||||
|
||||
def message_type
|
||||
@parsed_json['message_type']
|
||||
end
|
||||
def message_type
|
||||
@parsed_json['message_type']
|
||||
end
|
||||
|
||||
def replies
|
||||
posts = []
|
||||
comments = @parsed_json['comments']
|
||||
comments.each do |comment|
|
||||
posts << post_from_comment(comment)
|
||||
end
|
||||
posts
|
||||
end
|
||||
def replies
|
||||
posts = []
|
||||
comments = @parsed_json['comments']
|
||||
comments.each do |comment|
|
||||
posts << post_from_comment(comment)
|
||||
end
|
||||
posts
|
||||
end
|
||||
|
||||
def post_from_comment(comment)
|
||||
post = {}
|
||||
post[:id] = comment['id']
|
||||
post[:author_id] = comment['user']['id']
|
||||
post[:raw] = comment['text']
|
||||
post[:created_at] = Time.parse comment['created_at']
|
||||
post
|
||||
end
|
||||
def post_from_comment(comment)
|
||||
post = {}
|
||||
post[:id] = comment['id']
|
||||
post[:author_id] = comment['user']['id']
|
||||
post[:raw] = comment['text']
|
||||
post[:created_at] = Time.parse comment['created_at']
|
||||
post
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def unescape(html)
|
||||
return nil unless html
|
||||
CGI.unescapeHTML html
|
||||
end
|
||||
def unescape(html)
|
||||
return nil unless html
|
||||
CGI.unescapeHTML html
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,17 +8,17 @@ class SocialcastUser
|
|||
@parsed_json = JSON.parse user_json
|
||||
end
|
||||
|
||||
def user
|
||||
email = @parsed_json['contact_info']['email']
|
||||
email = "#{@parsed_json['id']}@noemail.com" unless email
|
||||
def user
|
||||
email = @parsed_json['contact_info']['email']
|
||||
email = "#{@parsed_json['id']}@noemail.com" unless email
|
||||
|
||||
user = {}
|
||||
user[:id] = @parsed_json['id']
|
||||
user[:name] = @parsed_json['name']
|
||||
user[:username] = @parsed_json['username']
|
||||
user[:email] = email
|
||||
user[:staged] = true
|
||||
user
|
||||
end
|
||||
user = {}
|
||||
user[:id] = @parsed_json['id']
|
||||
user[:name] = @parsed_json['name']
|
||||
user[:username] = @parsed_json['username']
|
||||
user[:email] = email
|
||||
user[:staged] = true
|
||||
user
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -28,226 +28,226 @@ class ImportScripts::Vanilla < ImportScripts::Base
|
|||
|
||||
private
|
||||
|
||||
def check_file_exist
|
||||
raise ArgumentError.new("File does not exist: #{@vanilla_file}") unless File.exist?(@vanilla_file)
|
||||
end
|
||||
def check_file_exist
|
||||
raise ArgumentError.new("File does not exist: #{@vanilla_file}") unless File.exist?(@vanilla_file)
|
||||
end
|
||||
|
||||
def parse_file
|
||||
puts "parsing file..."
|
||||
file = read_file
|
||||
def parse_file
|
||||
puts "parsing file..."
|
||||
file = read_file
|
||||
|
||||
# TODO: parse header & validate version number
|
||||
header = file.readline
|
||||
# TODO: parse header & validate version number
|
||||
header = file.readline
|
||||
|
||||
until file.eof?
|
||||
line = file.readline
|
||||
next if line.blank?
|
||||
next if line.start_with?("//")
|
||||
until file.eof?
|
||||
line = file.readline
|
||||
next if line.blank?
|
||||
next if line.start_with?("//")
|
||||
|
||||
if m = /^Table: (\w+)/.match(line)
|
||||
# extract table name
|
||||
table = m[1].underscore.pluralize
|
||||
# read the data until an empty line
|
||||
data = []
|
||||
# first line is the table definition, turn that into a proper csv header
|
||||
data << file.readline.split(",").map { |c| c.split(":")[0].underscore }.join(",")
|
||||
until (line = file.readline).blank?
|
||||
data << line.strip
|
||||
end
|
||||
# PERF: don't parse useless tables
|
||||
useless_tables = ["user_meta"]
|
||||
useless_tables << "activities" unless @use_lastest_activity_as_user_bio
|
||||
next if useless_tables.include?(table)
|
||||
# parse the data
|
||||
puts "parsing #{table}..."
|
||||
parsed_data = CSV.parse(data.join("\n"), headers: true, header_converters: :symbol).map { |row| row.to_hash }
|
||||
instance_variable_set("@#{table}".to_sym, parsed_data)
|
||||
if m = /^Table: (\w+)/.match(line)
|
||||
# extract table name
|
||||
table = m[1].underscore.pluralize
|
||||
# read the data until an empty line
|
||||
data = []
|
||||
# first line is the table definition, turn that into a proper csv header
|
||||
data << file.readline.split(",").map { |c| c.split(":")[0].underscore }.join(",")
|
||||
until (line = file.readline).blank?
|
||||
data << line.strip
|
||||
end
|
||||
# PERF: don't parse useless tables
|
||||
useless_tables = ["user_meta"]
|
||||
useless_tables << "activities" unless @use_lastest_activity_as_user_bio
|
||||
next if useless_tables.include?(table)
|
||||
# parse the data
|
||||
puts "parsing #{table}..."
|
||||
parsed_data = CSV.parse(data.join("\n"), headers: true, header_converters: :symbol).map { |row| row.to_hash }
|
||||
instance_variable_set("@#{table}".to_sym, parsed_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def read_file
|
||||
puts "reading file..."
|
||||
string = File.read(@vanilla_file).gsub("\\N", "")
|
||||
.gsub(/\\$\n/m, "\\n")
|
||||
.gsub("\\,", ",")
|
||||
.gsub(/(?<!\\)\\"/, '""')
|
||||
.gsub(/\\\\\\"/, '\\""')
|
||||
StringIO.new(string)
|
||||
end
|
||||
def read_file
|
||||
puts "reading file..."
|
||||
string = File.read(@vanilla_file).gsub("\\N", "")
|
||||
.gsub(/\\$\n/m, "\\n")
|
||||
.gsub("\\,", ",")
|
||||
.gsub(/(?<!\\)\\"/, '""')
|
||||
.gsub(/\\\\\\"/, '\\""')
|
||||
StringIO.new(string)
|
||||
end
|
||||
|
||||
def import_users
|
||||
puts "", "importing users..."
|
||||
def import_users
|
||||
puts "", "importing users..."
|
||||
|
||||
admin_role_id = @roles.select { |r| r[:name] == "Administrator" }.first[:role_id]
|
||||
moderator_role_id = @roles.select { |r| r[:name] == "Moderator" }.first[:role_id]
|
||||
admin_role_id = @roles.select { |r| r[:name] == "Administrator" }.first[:role_id]
|
||||
moderator_role_id = @roles.select { |r| r[:name] == "Moderator" }.first[:role_id]
|
||||
|
||||
activities = (@activities || []).reject { |a| a[:activity_user_id] != a[:regarding_user_id] }
|
||||
activities = (@activities || []).reject { |a| a[:activity_user_id] != a[:regarding_user_id] }
|
||||
|
||||
create_users(@users) do |user|
|
||||
next if user[:name] == "[Deleted User]"
|
||||
create_users(@users) do |user|
|
||||
next if user[:name] == "[Deleted User]"
|
||||
|
||||
if @use_lastest_activity_as_user_bio
|
||||
last_activity = activities.select { |a| user[:user_id] == a[:activity_user_id] }.last
|
||||
bio_raw = last_activity.try(:[], :story) || ""
|
||||
else
|
||||
bio_raw = user[:discovery_text]
|
||||
end
|
||||
|
||||
u = {
|
||||
id: user[:user_id],
|
||||
email: user[:email],
|
||||
username: user[:name],
|
||||
created_at: parse_date(user[:date_inserted]),
|
||||
bio_raw: clean_up(bio_raw),
|
||||
avatar_url: user[:photo],
|
||||
moderator: @user_roles.select { |ur| ur[:user_id] == user[:user_id] }.map { |ur| ur[:role_id] }.include?(moderator_role_id),
|
||||
admin: @user_roles.select { |ur| ur[:user_id] == user[:user_id] }.map { |ur| ur[:role_id] }.include?(admin_role_id),
|
||||
}
|
||||
|
||||
u
|
||||
if @use_lastest_activity_as_user_bio
|
||||
last_activity = activities.select { |a| user[:user_id] == a[:activity_user_id] }.last
|
||||
bio_raw = last_activity.try(:[], :story) || ""
|
||||
else
|
||||
bio_raw = user[:discovery_text]
|
||||
end
|
||||
end
|
||||
|
||||
def import_categories
|
||||
puts "", "importing categories..."
|
||||
|
||||
# save some information about the root category
|
||||
@root_category = @categories.select { |c| c[:category_id] == "-1" }.first
|
||||
@root_category_created_at = parse_date(@root_category[:date_inserted])
|
||||
|
||||
# removes root category
|
||||
@categories.reject! { |c| c[:category_id] == "-1" }
|
||||
|
||||
# adds root's child categories
|
||||
first_level_categories = @categories.select { |c| c[:parent_category_id] == "-1" }
|
||||
if first_level_categories.count > 0
|
||||
puts "", "importing first-level categories..."
|
||||
create_categories(first_level_categories) { |category| import_category(category) }
|
||||
|
||||
# adds other categories
|
||||
second_level_categories = @categories.select { |c| c[:parent_category_id] != "-1" }
|
||||
if second_level_categories.count > 0
|
||||
puts "", "importing second-level categories..."
|
||||
create_categories(second_level_categories) { |category| import_category(category) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def import_category(category)
|
||||
c = {
|
||||
id: category[:category_id],
|
||||
name: category[:name],
|
||||
user_id: user_id_from_imported_user_id(category[:insert_user_id]) || Discourse::SYSTEM_USER_ID,
|
||||
position: category[:sort].to_i,
|
||||
created_at: parse_category_date(category[:date_inserted]),
|
||||
description: clean_up(category[:description]),
|
||||
u = {
|
||||
id: user[:user_id],
|
||||
email: user[:email],
|
||||
username: user[:name],
|
||||
created_at: parse_date(user[:date_inserted]),
|
||||
bio_raw: clean_up(bio_raw),
|
||||
avatar_url: user[:photo],
|
||||
moderator: @user_roles.select { |ur| ur[:user_id] == user[:user_id] }.map { |ur| ur[:role_id] }.include?(moderator_role_id),
|
||||
admin: @user_roles.select { |ur| ur[:user_id] == user[:user_id] }.map { |ur| ur[:role_id] }.include?(admin_role_id),
|
||||
}
|
||||
if category[:parent_category_id] != "-1"
|
||||
c[:parent_category_id] = category_id_from_imported_category_id(category[:parent_category_id])
|
||||
end
|
||||
c
|
||||
|
||||
u
|
||||
end
|
||||
end
|
||||
|
||||
def parse_category_date(date)
|
||||
date == "0000-00-00 00:00:00" ? @root_category_created_at : parse_date(date)
|
||||
end
|
||||
def import_categories
|
||||
puts "", "importing categories..."
|
||||
|
||||
def import_topics
|
||||
puts "", "importing topics..."
|
||||
# save some information about the root category
|
||||
@root_category = @categories.select { |c| c[:category_id] == "-1" }.first
|
||||
@root_category_created_at = parse_date(@root_category[:date_inserted])
|
||||
|
||||
create_posts(@discussions) do |discussion|
|
||||
{
|
||||
id: "discussion#" + discussion[:discussion_id],
|
||||
user_id: user_id_from_imported_user_id(discussion[:insert_user_id]) || Discourse::SYSTEM_USER_ID,
|
||||
title: discussion[:name],
|
||||
category: category_id_from_imported_category_id(discussion[:category_id]),
|
||||
raw: clean_up(discussion[:body]),
|
||||
created_at: parse_date(discussion[:date_inserted]),
|
||||
}
|
||||
# removes root category
|
||||
@categories.reject! { |c| c[:category_id] == "-1" }
|
||||
|
||||
# adds root's child categories
|
||||
first_level_categories = @categories.select { |c| c[:parent_category_id] == "-1" }
|
||||
if first_level_categories.count > 0
|
||||
puts "", "importing first-level categories..."
|
||||
create_categories(first_level_categories) { |category| import_category(category) }
|
||||
|
||||
# adds other categories
|
||||
second_level_categories = @categories.select { |c| c[:parent_category_id] != "-1" }
|
||||
if second_level_categories.count > 0
|
||||
puts "", "importing second-level categories..."
|
||||
create_categories(second_level_categories) { |category| import_category(category) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def import_posts
|
||||
puts "", "importing posts..."
|
||||
|
||||
create_posts(@comments) do |comment|
|
||||
next unless t = topic_lookup_from_imported_post_id("discussion#" + comment[:discussion_id])
|
||||
|
||||
{
|
||||
id: "comment#" + comment[:comment_id],
|
||||
user_id: user_id_from_imported_user_id(comment[:insert_user_id]) || Discourse::SYSTEM_USER_ID,
|
||||
topic_id: t[:topic_id],
|
||||
raw: clean_up(comment[:body]),
|
||||
created_at: parse_date(comment[:date_inserted]),
|
||||
}
|
||||
end
|
||||
def import_category(category)
|
||||
c = {
|
||||
id: category[:category_id],
|
||||
name: category[:name],
|
||||
user_id: user_id_from_imported_user_id(category[:insert_user_id]) || Discourse::SYSTEM_USER_ID,
|
||||
position: category[:sort].to_i,
|
||||
created_at: parse_category_date(category[:date_inserted]),
|
||||
description: clean_up(category[:description]),
|
||||
}
|
||||
if category[:parent_category_id] != "-1"
|
||||
c[:parent_category_id] = category_id_from_imported_category_id(category[:parent_category_id])
|
||||
end
|
||||
c
|
||||
end
|
||||
|
||||
def import_private_topics
|
||||
puts "", "importing private topics..."
|
||||
def parse_category_date(date)
|
||||
date == "0000-00-00 00:00:00" ? @root_category_created_at : parse_date(date)
|
||||
end
|
||||
|
||||
create_posts(@conversations) do |conversation|
|
||||
next if conversation[:first_message_id].blank?
|
||||
def import_topics
|
||||
puts "", "importing topics..."
|
||||
|
||||
# list all other user ids in the conversation
|
||||
user_ids_in_conversation = @user_conversations.select { |uc| uc[:conversation_id] == conversation[:conversation_id] && uc[:user_id] != conversation[:insert_user_id] }
|
||||
.map { |uc| uc[:user_id] }
|
||||
# retrieve their emails
|
||||
user_emails_in_conversation = @users.select { |u| user_ids_in_conversation.include?(u[:user_id]) }
|
||||
.map { |u| u[:email] }
|
||||
# retrieve their usernames from the database
|
||||
target_usernames = User.where("email IN (?)", user_emails_in_conversation).pluck(:username).to_a
|
||||
|
||||
next if target_usernames.blank?
|
||||
|
||||
user = find_user_by_import_id(conversation[:insert_user_id]) || Discourse.system_user
|
||||
first_message = @conversation_messages.select { |cm| cm[:message_id] == conversation[:first_message_id] }.first
|
||||
|
||||
{
|
||||
archetype: Archetype.private_message,
|
||||
id: "conversation#" + conversation[:conversation_id],
|
||||
user_id: user.id,
|
||||
title: "Private message from #{user.username}",
|
||||
target_usernames: target_usernames,
|
||||
raw: clean_up(first_message[:body]),
|
||||
created_at: parse_date(conversation[:date_inserted]),
|
||||
}
|
||||
end
|
||||
create_posts(@discussions) do |discussion|
|
||||
{
|
||||
id: "discussion#" + discussion[:discussion_id],
|
||||
user_id: user_id_from_imported_user_id(discussion[:insert_user_id]) || Discourse::SYSTEM_USER_ID,
|
||||
title: discussion[:name],
|
||||
category: category_id_from_imported_category_id(discussion[:category_id]),
|
||||
raw: clean_up(discussion[:body]),
|
||||
created_at: parse_date(discussion[:date_inserted]),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def import_private_posts
|
||||
puts "", "importing private posts..."
|
||||
def import_posts
|
||||
puts "", "importing posts..."
|
||||
|
||||
first_message_ids = Set.new(@conversations.map { |c| c[:first_message_id] }.to_a)
|
||||
@conversation_messages.reject! { |cm| first_message_ids.include?(cm[:message_id]) }
|
||||
create_posts(@comments) do |comment|
|
||||
next unless t = topic_lookup_from_imported_post_id("discussion#" + comment[:discussion_id])
|
||||
|
||||
create_posts(@conversation_messages) do |message|
|
||||
next unless t = topic_lookup_from_imported_post_id("conversation#" + message[:conversation_id])
|
||||
|
||||
{
|
||||
archetype: Archetype.private_message,
|
||||
id: "message#" + message[:message_id],
|
||||
user_id: user_id_from_imported_user_id(message[:insert_user_id]) || Discourse::SYSTEM_USER_ID,
|
||||
topic_id: t[:topic_id],
|
||||
raw: clean_up(message[:body]),
|
||||
created_at: parse_date(message[:date_inserted]),
|
||||
}
|
||||
end
|
||||
{
|
||||
id: "comment#" + comment[:comment_id],
|
||||
user_id: user_id_from_imported_user_id(comment[:insert_user_id]) || Discourse::SYSTEM_USER_ID,
|
||||
topic_id: t[:topic_id],
|
||||
raw: clean_up(comment[:body]),
|
||||
created_at: parse_date(comment[:date_inserted]),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def parse_date(date)
|
||||
DateTime.strptime(date, "%Y-%m-%d %H:%M:%S")
|
||||
end
|
||||
def import_private_topics
|
||||
puts "", "importing private topics..."
|
||||
|
||||
def clean_up(raw)
|
||||
return "" if raw.blank?
|
||||
raw.gsub("\\n", "\n")
|
||||
.gsub(/<\/?pre\s*>/i, "\n```\n")
|
||||
.gsub(/<\/?code\s*>/i, "`")
|
||||
.gsub("<", "<")
|
||||
.gsub(">", ">")
|
||||
create_posts(@conversations) do |conversation|
|
||||
next if conversation[:first_message_id].blank?
|
||||
|
||||
# list all other user ids in the conversation
|
||||
user_ids_in_conversation = @user_conversations.select { |uc| uc[:conversation_id] == conversation[:conversation_id] && uc[:user_id] != conversation[:insert_user_id] }
|
||||
.map { |uc| uc[:user_id] }
|
||||
# retrieve their emails
|
||||
user_emails_in_conversation = @users.select { |u| user_ids_in_conversation.include?(u[:user_id]) }
|
||||
.map { |u| u[:email] }
|
||||
# retrieve their usernames from the database
|
||||
target_usernames = User.where("email IN (?)", user_emails_in_conversation).pluck(:username).to_a
|
||||
|
||||
next if target_usernames.blank?
|
||||
|
||||
user = find_user_by_import_id(conversation[:insert_user_id]) || Discourse.system_user
|
||||
first_message = @conversation_messages.select { |cm| cm[:message_id] == conversation[:first_message_id] }.first
|
||||
|
||||
{
|
||||
archetype: Archetype.private_message,
|
||||
id: "conversation#" + conversation[:conversation_id],
|
||||
user_id: user.id,
|
||||
title: "Private message from #{user.username}",
|
||||
target_usernames: target_usernames,
|
||||
raw: clean_up(first_message[:body]),
|
||||
created_at: parse_date(conversation[:date_inserted]),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def import_private_posts
|
||||
puts "", "importing private posts..."
|
||||
|
||||
first_message_ids = Set.new(@conversations.map { |c| c[:first_message_id] }.to_a)
|
||||
@conversation_messages.reject! { |cm| first_message_ids.include?(cm[:message_id]) }
|
||||
|
||||
create_posts(@conversation_messages) do |message|
|
||||
next unless t = topic_lookup_from_imported_post_id("conversation#" + message[:conversation_id])
|
||||
|
||||
{
|
||||
archetype: Archetype.private_message,
|
||||
id: "message#" + message[:message_id],
|
||||
user_id: user_id_from_imported_user_id(message[:insert_user_id]) || Discourse::SYSTEM_USER_ID,
|
||||
topic_id: t[:topic_id],
|
||||
raw: clean_up(message[:body]),
|
||||
created_at: parse_date(message[:date_inserted]),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def parse_date(date)
|
||||
DateTime.strptime(date, "%Y-%m-%d %H:%M:%S")
|
||||
end
|
||||
|
||||
def clean_up(raw)
|
||||
return "" if raw.blank?
|
||||
raw.gsub("\\n", "\n")
|
||||
.gsub(/<\/?pre\s*>/i, "\n```\n")
|
||||
.gsub(/<\/?code\s*>/i, "`")
|
||||
.gsub("<", "<")
|
||||
.gsub(">", ">")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue