diff --git a/app/controllers/admin/badges_controller.rb b/app/controllers/admin/badges_controller.rb index 53089c8e51e..e54860d0855 100644 --- a/app/controllers/admin/badges_controller.rb +++ b/app/controllers/admin/badges_controller.rb @@ -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 diff --git a/app/controllers/admin/embeddable_hosts_controller.rb b/app/controllers/admin/embeddable_hosts_controller.rb index 667db524fe8..e70061f0c27 100644 --- a/app/controllers/admin/embeddable_hosts_controller.rb +++ b/app/controllers/admin/embeddable_hosts_controller.rb @@ -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 diff --git a/app/controllers/admin/embedding_controller.rb b/app/controllers/admin/embedding_controller.rb index ebac31e576e..8f86a9a22d2 100644 --- a/app/controllers/admin/embedding_controller.rb +++ b/app/controllers/admin/embedding_controller.rb @@ -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 diff --git a/app/controllers/admin/screened_ip_addresses_controller.rb b/app/controllers/admin/screened_ip_addresses_controller.rb index 775f115195f..6e92777233b 100644 --- a/app/controllers/admin/screened_ip_addresses_controller.rb +++ b/app/controllers/admin/screened_ip_addresses_controller.rb @@ -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 diff --git a/app/controllers/admin/site_texts_controller.rb b/app/controllers/admin/site_texts_controller.rb index 33e636926d5..ffc3633203c 100644 --- a/app/controllers/admin/site_texts_controller.rb +++ b/app/controllers/admin/site_texts_controller.rb @@ -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 diff --git a/app/controllers/admin/themes_controller.rb b/app/controllers/admin/themes_controller.rb index a40ec46998d..ebc74980e09 100644 --- a/app/controllers/admin/themes_controller.rb +++ b/app/controllers/admin/themes_controller.rb @@ -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 diff --git a/app/controllers/admin/user_fields_controller.rb b/app/controllers/admin/user_fields_controller.rb index 2af8832e333..4ac32bb9118 100644 --- a/app/controllers/admin/user_fields_controller.rb +++ b/app/controllers/admin/user_fields_controller.rb @@ -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 diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 7b7f42096c0..66b5daf9267 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -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 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d75e4152c82..b9929a9e283 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -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 diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index f9216fc56be..b9e86e72760 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -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 diff --git a/app/controllers/clicks_controller.rb b/app/controllers/clicks_controller.rb index 9602a486256..1aa3a29289e 100644 --- a/app/controllers/clicks_controller.rb +++ b/app/controllers/clicks_controller.rb @@ -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 diff --git a/app/controllers/embed_controller.rb b/app/controllers/embed_controller.rb index 686019e8504..d43754709b2 100644 --- a/app/controllers/embed_controller.rb +++ b/app/controllers/embed_controller.rb @@ -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 diff --git a/app/controllers/exceptions_controller.rb b/app/controllers/exceptions_controller.rb index 33cbad301b1..540add85c49 100644 --- a/app/controllers/exceptions_controller.rb +++ b/app/controllers/exceptions_controller.rb @@ -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 diff --git a/app/controllers/export_csv_controller.rb b/app/controllers/export_csv_controller.rb index 836ad48d70d..851d1643681 100644 --- a/app/controllers/export_csv_controller.rb +++ b/app/controllers/export_csv_controller.rb @@ -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 diff --git a/app/controllers/finish_installation_controller.rb b/app/controllers/finish_installation_controller.rb index 4640677ebca..c896724a494 100644 --- a/app/controllers/finish_installation_controller.rb +++ b/app/controllers/finish_installation_controller.rb @@ -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 diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 10933affcfe..c2c51ddf1e2 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -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 diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index 7fbc06a2b06..50d715933b2 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -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 diff --git a/app/controllers/post_actions_controller.rb b/app/controllers/post_actions_controller.rb index fa0314b999c..3e5b0c8e845 100644 --- a/app/controllers/post_actions_controller.rb +++ b/app/controllers/post_actions_controller.rb @@ -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 diff --git a/app/controllers/queued_posts_controller.rb b/app/controllers/queued_posts_controller.rb index 3360b41f7b9..7db439e929b 100644 --- a/app/controllers/queued_posts_controller.rb +++ b/app/controllers/queued_posts_controller.rb @@ -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 diff --git a/app/controllers/stylesheets_controller.rb b/app/controllers/stylesheets_controller.rb index 7377d479893..f7344a33930 100644 --- a/app/controllers/stylesheets_controller.rb +++ b/app/controllers/stylesheets_controller.rb @@ -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 diff --git a/app/controllers/tag_groups_controller.rb b/app/controllers/tag_groups_controller.rb index 65748f83780..4fd4f0f6e0a 100644 --- a/app/controllers/tag_groups_controller.rb +++ b/app/controllers/tag_groups_controller.rb @@ -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 diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index a673bb06d08..b2916e8c9db 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -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 diff --git a/app/controllers/user_badges_controller.rb b/app/controllers/user_badges_controller.rb index b6b415d69a5..999c987647c 100644 --- a/app/controllers/user_badges_controller.rb +++ b/app/controllers/user_badges_controller.rb @@ -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 diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 548ce754c65..3c97ce94d77 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -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 diff --git a/app/controllers/webhooks_controller.rb b/app/controllers/webhooks_controller.rb index b6e1ad2093c..5480fe33680 100644 --- a/app/controllers/webhooks_controller.rb +++ b/app/controllers/webhooks_controller.rb @@ -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 diff --git a/app/jobs/regular/export_csv_file.rb b/app/jobs/regular/export_csv_file.rb index 2ebfb75df7d..d32e2b822cd 100644 --- a/app/jobs/regular/export_csv_file.rb +++ b/app/jobs/regular/export_csv_file.rb @@ -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 diff --git a/app/jobs/regular/pull_hotlinked_images.rb b/app/jobs/regular/pull_hotlinked_images.rb index ce88cd6546e..d6e03f782d4 100644 --- a/app/jobs/regular/pull_hotlinked_images.rb +++ b/app/jobs/regular/pull_hotlinked_images.rb @@ -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 diff --git a/app/models/badge.rb b/app/models/badge.rb index 60da562949d..9a64949980b 100644 --- a/app/models/badge.rb +++ b/app/models/badge.rb @@ -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 diff --git a/app/models/category_list.rb b/app/models/category_list.rb index 76511d5b8bc..e8bf8aefab8 100644 --- a/app/models/category_list.rb +++ b/app/models/category_list.rb @@ -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 diff --git a/app/models/concerns/trashable.rb b/app/models/concerns/trashable.rb index 0a8dae3f501..3d79754980e 100644 --- a/app/models/concerns/trashable.rb +++ b/app/models/concerns/trashable.rb @@ -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 diff --git a/app/models/embeddable_host.rb b/app/models/embeddable_host.rb index c6f0effbbe1..aed2a253627 100644 --- a/app/models/embeddable_host.rb +++ b/app/models/embeddable_host.rb @@ -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 diff --git a/app/models/group.rb b/app/models/group.rb index 1a895775ded..fe0c647e394 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -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 diff --git a/app/models/group_archived_message.rb b/app/models/group_archived_message.rb index 778a3944c18..729ad8b82ad 100644 --- a/app/models/group_archived_message.rb +++ b/app/models/group_archived_message.rb @@ -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 diff --git a/app/models/post_analyzer.rb b/app/models/post_analyzer.rb index 5d069c72eb9..5de5a83f8ec 100644 --- a/app/models/post_analyzer.rb +++ b/app/models/post_analyzer.rb @@ -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 diff --git a/app/models/queued_post.rb b/app/models/queued_post.rb index fb4a390f7fc..1f2afa75fec 100644 --- a/app/models/queued_post.rb +++ b/app/models/queued_post.rb @@ -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 diff --git a/app/models/topic_featured_users.rb b/app/models/topic_featured_users.rb index d3f705149be..d8a8a406cb1 100644 --- a/app/models/topic_featured_users.rb +++ b/app/models/topic_featured_users.rb @@ -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 diff --git a/app/models/topic_timer.rb b/app/models/topic_timer.rb index 39f882dbafa..e481ec1b26e 100644 --- a/app/models/topic_timer.rb +++ b/app/models/topic_timer.rb @@ -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 diff --git a/app/models/unsubscribe_key.rb b/app/models/unsubscribe_key.rb index 967d8f34a13..7f5ee933cba 100644 --- a/app/models/unsubscribe_key.rb +++ b/app/models/unsubscribe_key.rb @@ -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 diff --git a/app/models/user_archived_message.rb b/app/models/user_archived_message.rb index 1edd3863c48..6c2c604042f 100644 --- a/app/models/user_archived_message.rb +++ b/app/models/user_archived_message.rb @@ -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 diff --git a/app/models/user_badge.rb b/app/models/user_badge.rb index 53979ec9cf8..b0b4ce354c5 100644 --- a/app/models/user_badge.rb +++ b/app/models/user_badge.rb @@ -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 diff --git a/app/models/user_option.rb b/app/models/user_option.rb index 828ac58c602..5077d8dc364 100644 --- a/app/models/user_option.rb +++ b/app/models/user_option.rb @@ -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 diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb index c2c016741e6..8bea527b713 100644 --- a/app/models/web_hook.rb +++ b/app/models/web_hook.rb @@ -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 diff --git a/app/serializers/admin_user_action_serializer.rb b/app/serializers/admin_user_action_serializer.rb index dfe2cbc6314..4bc1db5f5a3 100644 --- a/app/serializers/admin_user_action_serializer.rb +++ b/app/serializers/admin_user_action_serializer.rb @@ -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 diff --git a/app/serializers/basic_group_serializer.rb b/app/serializers/basic_group_serializer.rb index 26fb3ea2dec..6ac6f23b490 100644 --- a/app/serializers/basic_group_serializer.rb +++ b/app/serializers/basic_group_serializer.rb @@ -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 diff --git a/app/serializers/listable_topic_serializer.rb b/app/serializers/listable_topic_serializer.rb index aeecddbeaef..40b29f63836 100644 --- a/app/serializers/listable_topic_serializer.rb +++ b/app/serializers/listable_topic_serializer.rb @@ -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 diff --git a/app/serializers/post_action_type_serializer.rb b/app/serializers/post_action_type_serializer.rb index 718c84a5b7e..9ea2847e9aa 100644 --- a/app/serializers/post_action_type_serializer.rb +++ b/app/serializers/post_action_type_serializer.rb @@ -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 diff --git a/app/serializers/post_revision_serializer.rb b/app/serializers/post_revision_serializer.rb index e8c7e9f16ee..6bfb7dbb033 100644 --- a/app/serializers/post_revision_serializer.rb +++ b/app/serializers/post_revision_serializer.rb @@ -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 diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb index e66fa720fd6..d389cecafef 100644 --- a/app/serializers/post_serializer.rb +++ b/app/serializers/post_serializer.rb @@ -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 diff --git a/app/serializers/topic_flag_type_serializer.rb b/app/serializers/topic_flag_type_serializer.rb index c0ac6951bd0..4caa98a6985 100644 --- a/app/serializers/topic_flag_type_serializer.rb +++ b/app/serializers/topic_flag_type_serializer.rb @@ -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 diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb index 44479aa7e0a..9475da548a1 100644 --- a/app/serializers/topic_view_serializer.rb +++ b/app/serializers/topic_view_serializer.rb @@ -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 diff --git a/app/services/group_action_logger.rb b/app/services/group_action_logger.rb index 58674016c37..bcd5e56908e 100644 --- a/app/services/group_action_logger.rb +++ b/app/services/group_action_logger.rb @@ -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 diff --git a/app/services/staff_action_logger.rb b/app/services/staff_action_logger.rb index f9205e075e1..d8360735806 100644 --- a/app/services/staff_action_logger.rb +++ b/app/services/staff_action_logger.rb @@ -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 diff --git a/app/services/user_anonymizer.rb b/app/services/user_anonymizer.rb index 7184f1ac87b..45fd0f341d6 100644 --- a/app/services/user_anonymizer.rb +++ b/app/services/user_anonymizer.rb @@ -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] diff --git a/lib/auth/facebook_authenticator.rb b/lib/auth/facebook_authenticator.rb index 50e717d7395..f4ea2f1b852 100644 --- a/lib/auth/facebook_authenticator.rb +++ b/lib/auth/facebook_authenticator.rb @@ -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 diff --git a/lib/auth/twitter_authenticator.rb b/lib/auth/twitter_authenticator.rb index 36a16b1cf9d..b4ef82d9ebd 100644 --- a/lib/auth/twitter_authenticator.rb +++ b/lib/auth/twitter_authenticator.rb @@ -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 diff --git a/lib/common_passwords/common_passwords.rb b/lib/common_passwords/common_passwords.rb index 0045cc8a366..bec6fca43f4 100644 --- a/lib/common_passwords/common_passwords.rb +++ b/lib/common_passwords/common_passwords.rb @@ -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 diff --git a/lib/composer_messages_finder.rb b/lib/composer_messages_finder.rb index 76c582dd572..a539df70653 100644 --- a/lib/composer_messages_finder.rb +++ b/lib/composer_messages_finder.rb @@ -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 diff --git a/lib/email_updater.rb b/lib/email_updater.rb index d3d8eaf339b..dd7670f75b6 100644 --- a/lib/email_updater.rb +++ b/lib/email_updater.rb @@ -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 diff --git a/lib/file_helper.rb b/lib/file_helper.rb index 7ef59523907..4fc4df68e48 100644 --- a/lib/file_helper.rb +++ b/lib/file_helper.rb @@ -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 diff --git a/lib/flag_query.rb b/lib/flag_query.rb index 11db2aa887d..1d833200d83 100644 --- a/lib/flag_query.rb +++ b/lib/flag_query.rb @@ -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 diff --git a/lib/freedom_patches/rack_patches.rb b/lib/freedom_patches/rack_patches.rb index 890435d9b9c..4cdc2dce11b 100644 --- a/lib/freedom_patches/rack_patches.rb +++ b/lib/freedom_patches/rack_patches.rb @@ -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 diff --git a/lib/i18n/backend/discourse_i18n.rb b/lib/i18n/backend/discourse_i18n.rb index 1f33dd60e0d..fec8150c79f 100644 --- a/lib/i18n/backend/discourse_i18n.rb +++ b/lib/i18n/backend/discourse_i18n.rb @@ -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 diff --git a/lib/i18n/duplicate_key_finder.rb b/lib/i18n/duplicate_key_finder.rb index 745b5ce2933..f23221d13e9 100644 --- a/lib/i18n/duplicate_key_finder.rb +++ b/lib/i18n/duplicate_key_finder.rb @@ -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 diff --git a/lib/inline_oneboxer.rb b/lib/inline_oneboxer.rb index aafe3b338cb..7e1ccfa29fd 100644 --- a/lib/inline_oneboxer.rb +++ b/lib/inline_oneboxer.rb @@ -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 diff --git a/lib/onebox/engine/whitelisted_generic_onebox.rb b/lib/onebox/engine/whitelisted_generic_onebox.rb index f48ad2f214e..64b22dae9cc 100644 --- a/lib/onebox/engine/whitelisted_generic_onebox.rb +++ b/lib/onebox/engine/whitelisted_generic_onebox.rb @@ -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 diff --git a/lib/oneboxer.rb b/lib/oneboxer.rb index dc5b6491572..2f99c0f8145 100644 --- a/lib/oneboxer.rb +++ b/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 || "#{url}" - { onebox: html, preview: html } - end - - def self.local_upload_html(url) - case File.extname(URI(url).path || "") - when /^\.(mov|mp4|webm|ogv)$/i - "" - when /^\.(mp3|ogg|wav|m4a)$/i - "" - 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 || "#{url}" + { 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 + "" + when /^\.(mp3|ogg|wav|m4a)$/i + "" + 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 diff --git a/lib/retrieve_title.rb b/lib/retrieve_title.rb index c8e5f8a7ed9..62c4a4022d4 100644 --- a/lib/retrieve_title.rb +++ b/lib/retrieve_title.rb @@ -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 diff --git a/lib/search.rb b/lib/search.rb index 40f80a245c4..054f7e5fd05 100644 --- a/lib/search.rb +++ b/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 diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 563eedf67a0..ad73a7848fd 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -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 diff --git a/lib/topic_retriever.rb b/lib/topic_retriever.rb index 82f815d9f7c..1addac713f5 100644 --- a/lib/topic_retriever.rb +++ b/lib/topic_retriever.rb @@ -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 diff --git a/lib/topics_bulk_action.rb b/lib/topics_bulk_action.rb index 2d16fb5c219..b0b5e91b7a9 100644 --- a/lib/topics_bulk_action.rb +++ b/lib/topics_bulk_action.rb @@ -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 diff --git a/lib/validators/censored_words_validator.rb b/lib/validators/censored_words_validator.rb index 96749c845cd..b2c70bc931b 100644 --- a/lib/validators/censored_words_validator.rb +++ b/lib/validators/censored_words_validator.rb @@ -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 diff --git a/lib/validators/pop3_polling_enabled_setting_validator.rb b/lib/validators/pop3_polling_enabled_setting_validator.rb index d002f4eee7f..0b79dc8d889 100644 --- a/lib/validators/pop3_polling_enabled_setting_validator.rb +++ b/lib/validators/pop3_polling_enabled_setting_validator.rb @@ -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 diff --git a/lib/validators/topic_title_length_validator.rb b/lib/validators/topic_title_length_validator.rb index bdee42c81c8..6e4a1d17017 100644 --- a/lib/validators/topic_title_length_validator.rb +++ b/lib/validators/topic_title_length_validator.rb @@ -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 diff --git a/plugins/discourse-narrative-bot/lib/discourse_narrative_bot/certificate_generator.rb b/plugins/discourse-narrative-bot/lib/discourse_narrative_bot/certificate_generator.rb index bb1f4dbeffc..2fad2f84776 100644 --- a/plugins/discourse-narrative-bot/lib/discourse_narrative_bot/certificate_generator.rb +++ b/plugins/discourse-narrative-bot/lib/discourse_narrative_bot/certificate_generator.rb @@ -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 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 diff --git a/script/import_scripts/quandora/quandora_question.rb b/script/import_scripts/quandora/quandora_question.rb index 6105ef895d5..9eb59c9e303 100644 --- a/script/import_scripts/quandora/quandora_question.rb +++ b/script/import_scripts/quandora/quandora_question.rb @@ -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 diff --git a/script/import_scripts/socialcast/socialcast_message.rb b/script/import_scripts/socialcast/socialcast_message.rb index a82149f2ab1..e121c5695d0 100644 --- a/script/import_scripts/socialcast/socialcast_message.rb +++ b/script/import_scripts/socialcast/socialcast_message.rb @@ -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 diff --git a/script/import_scripts/socialcast/socialcast_user.rb b/script/import_scripts/socialcast/socialcast_user.rb index 54107b46363..fb4217318c7 100644 --- a/script/import_scripts/socialcast/socialcast_user.rb +++ b/script/import_scripts/socialcast/socialcast_user.rb @@ -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 diff --git a/script/import_scripts/vanilla.rb b/script/import_scripts/vanilla.rb index 94712905599..0a9b057eec2 100644 --- a/script/import_scripts/vanilla.rb +++ b/script/import_scripts/vanilla.rb @@ -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(/(? 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