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