Merge branch 'master' of github.com:discourse/discourse into bbpress-missing-display-name
This commit is contained in:
commit
e307bbccf9
|
@ -15,7 +15,7 @@ To learn more about the philosophy and goals of the project, [visit **discourse.
|
||||||
<a href="http://discuss.howtogeek.com"><img src="https://www.discourse.org/faq/17/how-to-geek-discourse.png" width="720px"></a>
|
<a href="http://discuss.howtogeek.com"><img src="https://www.discourse.org/faq/17/how-to-geek-discourse.png" width="720px"></a>
|
||||||
<a href="https://talk.turtlerockstudios.com/"><img src="https://www.discourse.org/faq/17/turtle-rock-discourse.png" width="720px"></a>
|
<a href="https://talk.turtlerockstudios.com/"><img src="https://www.discourse.org/faq/17/turtle-rock-discourse.png" width="720px"></a>
|
||||||
|
|
||||||
<a href="https://discuss.atom.io"><img src="https://www.discourse.org/faq/15/nexus-7-2013-mobile-discourse.png?v=2" alt="Atom" width="430px"></a>
|
<a href="https://discuss.atom.io"><img src="https://www.discourse.org/faq/17/nexus-7-2013-mobile-discourse.png" alt="Atom" width="430px"></a>
|
||||||
<a href="//discourse.soylent.com"><img src="https://www.discourse.org/faq/15/iphone-5s-mobile-discourse.png" alt="Soylent" width="270px"></a>
|
<a href="//discourse.soylent.com"><img src="https://www.discourse.org/faq/15/iphone-5s-mobile-discourse.png" alt="Soylent" width="270px"></a>
|
||||||
|
|
||||||
Browse [lots more notable Discourse instances](http://www.discourse.org/faq/customers/).
|
Browse [lots more notable Discourse instances](http://www.discourse.org/faq/customers/).
|
||||||
|
|
|
@ -6,6 +6,10 @@ export default Em.Component.extend(UploadMixin, {
|
||||||
tagName: "span",
|
tagName: "span",
|
||||||
uploadUrl: "/invites/upload_csv",
|
uploadUrl: "/invites/upload_csv",
|
||||||
|
|
||||||
|
validateUploadedFilesOptions() {
|
||||||
|
return { csvOnly: true };
|
||||||
|
},
|
||||||
|
|
||||||
@computed("uploading")
|
@computed("uploading")
|
||||||
uploadButtonText(uploading) {
|
uploadButtonText(uploading) {
|
||||||
return uploading ? I18n.t("uploading") : I18n.t("user.invited.bulk_invite.text");
|
return uploading ? I18n.t("uploading") : I18n.t("user.invited.bulk_invite.text");
|
||||||
|
|
|
@ -192,6 +192,11 @@ export function validateUploadedFile(file, opts) {
|
||||||
bootbox.alert(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: authorizedImagesExtensions() }));
|
bootbox.alert(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: authorizedImagesExtensions() }));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (opts["csvOnly"]) {
|
||||||
|
if (!(/\.csv$/i).test(name)) {
|
||||||
|
bootbox.alert(I18n.t('user.invited.bulk_invite.error'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!authorizesAllExtensions() && !isAuthorizedFile(name)) {
|
if (!authorizesAllExtensions() && !isAuthorizedFile(name)) {
|
||||||
bootbox.alert(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: authorizedExtensions() }));
|
bootbox.alert(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: authorizedExtensions() }));
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
import { register } from 'pretty-text/engines/discourse-markdown/bbcode';
|
import { register } from 'pretty-text/engines/discourse-markdown/bbcode';
|
||||||
|
import { registerOption } from 'pretty-text/pretty-text';
|
||||||
|
import { performEmojiUnescape } from 'pretty-text/emoji';
|
||||||
|
|
||||||
|
registerOption((siteSettings, opts) => {
|
||||||
|
opts.enableEmoji = siteSettings.enable_emoji;
|
||||||
|
opts.emojiSet = siteSettings.emoji_set;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
export function setup(helper) {
|
export function setup(helper) {
|
||||||
register(helper, 'quote', {noWrap: true, singlePara: true}, (contents, bbParams, options) => {
|
register(helper, 'quote', {noWrap: true, singlePara: true}, (contents, bbParams, options) => {
|
||||||
const params = {'class': 'quote'};
|
const params = {'class': 'quote'};
|
||||||
let username = null;
|
let username = null;
|
||||||
|
const opts = helper.getOptions();
|
||||||
|
|
||||||
if (bbParams) {
|
if (bbParams) {
|
||||||
const paramsSplit = bbParams.split(/\,\s*/);
|
const paramsSplit = bbParams.split(/\,\s*/);
|
||||||
|
@ -52,7 +61,16 @@ export function setup(helper) {
|
||||||
if (postNumber > 0) { href += "/" + postNumber; }
|
if (postNumber > 0) { href += "/" + postNumber; }
|
||||||
// get rid of username said stuff
|
// get rid of username said stuff
|
||||||
header.pop();
|
header.pop();
|
||||||
header.push(['a', {'href': href}, topicInfo.title]);
|
|
||||||
|
let title = topicInfo.title;
|
||||||
|
|
||||||
|
if (opts.enableEmoji) {
|
||||||
|
title = performEmojiUnescape(topicInfo.title, {
|
||||||
|
getURL: opts.getURL, emojiSet: opts.emojiSet
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
header.push(['a', {'href': href}, title]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,7 @@ fieldset {
|
||||||
|
|
||||||
pre code {
|
pre code {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
tab-size: 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO figure out a clean place to put stuff like this
|
// TODO figure out a clean place to put stuff like this
|
||||||
|
|
|
@ -95,7 +95,14 @@ class Admin::BackupsController < Admin::AdminController
|
||||||
|
|
||||||
def readonly
|
def readonly
|
||||||
enable = params.fetch(:enable).to_s == "true"
|
enable = params.fetch(:enable).to_s == "true"
|
||||||
enable ? Discourse.enable_readonly_mode(user_enabled: true) : Discourse.disable_readonly_mode(user_enabled: true)
|
readonly_mode_key = Discourse::USER_READONLY_MODE_KEY
|
||||||
|
|
||||||
|
if enable
|
||||||
|
Discourse.enable_readonly_mode(readonly_mode_key)
|
||||||
|
else
|
||||||
|
Discourse.disable_readonly_mode(readonly_mode_key)
|
||||||
|
end
|
||||||
|
|
||||||
render nothing: true
|
render nothing: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ class Admin::UsersController < Admin::AdminController
|
||||||
def delete_all_posts
|
def delete_all_posts
|
||||||
@user = User.find_by(id: params[:user_id])
|
@user = User.find_by(id: params[:user_id])
|
||||||
@user.delete_all_posts!(guardian)
|
@user.delete_all_posts!(guardian)
|
||||||
|
# staff action logs will have an entry for each post
|
||||||
render nothing: true
|
render nothing: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -182,6 +183,8 @@ class Admin::UsersController < Admin::AdminController
|
||||||
@user.trust_level_locked = new_lock == "true"
|
@user.trust_level_locked = new_lock == "true"
|
||||||
@user.save
|
@user.save
|
||||||
|
|
||||||
|
StaffActionLogger.new(current_user).log_lock_trust_level(@user)
|
||||||
|
|
||||||
unless @user.trust_level_locked
|
unless @user.trust_level_locked
|
||||||
p = Promotion.new(@user)
|
p = Promotion.new(@user)
|
||||||
2.times{ p.review }
|
2.times{ p.review }
|
||||||
|
@ -210,12 +213,14 @@ class Admin::UsersController < Admin::AdminController
|
||||||
def activate
|
def activate
|
||||||
guardian.ensure_can_activate!(@user)
|
guardian.ensure_can_activate!(@user)
|
||||||
@user.activate
|
@user.activate
|
||||||
|
StaffActionLogger.new(current_user).log_user_activate(@user, I18n.t('user.activated_by_staff'))
|
||||||
render json: success_json
|
render json: success_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def deactivate
|
def deactivate
|
||||||
guardian.ensure_can_deactivate!(@user)
|
guardian.ensure_can_deactivate!(@user)
|
||||||
@user.deactivate
|
@user.deactivate
|
||||||
|
StaffActionLogger.new(current_user).log_user_deactivate(@user, I18n.t('user.deactivated_by_staff'))
|
||||||
refresh_browser @user
|
refresh_browser @user
|
||||||
render nothing: true
|
render nothing: true
|
||||||
end
|
end
|
||||||
|
|
|
@ -156,9 +156,9 @@ class InvitesController < ApplicationController
|
||||||
|
|
||||||
Scheduler::Defer.later("Upload CSV") do
|
Scheduler::Defer.later("Upload CSV") do
|
||||||
begin
|
begin
|
||||||
data = if extension == ".csv"
|
data = if extension.downcase == ".csv"
|
||||||
path = Invite.create_csv(file, name)
|
path = Invite.create_csv(file, name)
|
||||||
Jobs.enqueue(:bulk_invite, filename: "#{name}.csv", current_user_id: current_user.id)
|
Jobs.enqueue(:bulk_invite, filename: "#{name}#{extension}", current_user_id: current_user.id)
|
||||||
{url: path}
|
{url: path}
|
||||||
else
|
else
|
||||||
failed_json.merge(errors: [I18n.t("bulk_invite.file_should_be_csv")])
|
failed_json.merge(errors: [I18n.t("bulk_invite.file_should_be_csv")])
|
||||||
|
|
|
@ -17,7 +17,7 @@ class MetadataController < ApplicationController
|
||||||
name: SiteSetting.title,
|
name: SiteSetting.title,
|
||||||
short_name: SiteSetting.title,
|
short_name: SiteSetting.title,
|
||||||
display: 'standalone',
|
display: 'standalone',
|
||||||
orientation: 'portrait',
|
orientation: 'any',
|
||||||
start_url: "#{Discourse.base_uri}/",
|
start_url: "#{Discourse.base_uri}/",
|
||||||
background_color: "##{ColorScheme.hex_for_name('secondary')}",
|
background_color: "##{ColorScheme.hex_for_name('secondary')}",
|
||||||
theme_color: "##{ColorScheme.hex_for_name('header_background')}",
|
theme_color: "##{ColorScheme.hex_for_name('header_background')}",
|
||||||
|
|
|
@ -3,6 +3,7 @@ require_dependency 'email/message_builder'
|
||||||
require_dependency 'age_words'
|
require_dependency 'age_words'
|
||||||
|
|
||||||
class UserNotifications < ActionMailer::Base
|
class UserNotifications < ActionMailer::Base
|
||||||
|
include UserNotificationsHelper
|
||||||
helper :application
|
helper :application
|
||||||
default charset: 'UTF-8'
|
default charset: 'UTF-8'
|
||||||
|
|
||||||
|
@ -106,20 +107,27 @@ class UserNotifications < ActionMailer::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
@popular_topics = topics_for_digest[0,SiteSetting.digest_topics]
|
@popular_topics = topics_for_digest[0,SiteSetting.digest_topics]
|
||||||
@other_new_for_you = topics_for_digest.size > SiteSetting.digest_topics ? topics_for_digest[SiteSetting.digest_topics..-1] : []
|
|
||||||
|
|
||||||
@popular_posts = if SiteSetting.digest_posts > 0
|
|
||||||
Post.order("posts.score DESC")
|
|
||||||
.for_mailing_list(user, min_date)
|
|
||||||
.where('posts.post_type = ?', Post.types[:regular])
|
|
||||||
.where('posts.deleted_at IS NULL AND posts.hidden = false AND posts.user_deleted = false')
|
|
||||||
.where("posts.post_number > ? AND posts.score > ?", 1, ScoreCalculator.default_score_weights[:like_score] * 5.0)
|
|
||||||
.limit(SiteSetting.digest_posts)
|
|
||||||
else
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
|
|
||||||
if @popular_topics.present?
|
if @popular_topics.present?
|
||||||
|
@other_new_for_you = topics_for_digest.size > SiteSetting.digest_topics ? topics_for_digest[SiteSetting.digest_topics..-1] : []
|
||||||
|
|
||||||
|
@popular_posts = if SiteSetting.digest_posts > 0
|
||||||
|
Post.order("posts.score DESC")
|
||||||
|
.for_mailing_list(user, min_date)
|
||||||
|
.where('posts.post_type = ?', Post.types[:regular])
|
||||||
|
.where('posts.deleted_at IS NULL AND posts.hidden = false AND posts.user_deleted = false')
|
||||||
|
.where("posts.post_number > ? AND posts.score > ?", 1, ScoreCalculator.default_score_weights[:like_score] * 5.0)
|
||||||
|
.limit(SiteSetting.digest_posts)
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
@excerpts = {}
|
||||||
|
|
||||||
|
@popular_topics.map do |t|
|
||||||
|
@excerpts[t.first_post.id] = email_excerpt(t.first_post.cooked) if t.first_post.present?
|
||||||
|
end
|
||||||
|
|
||||||
# Try to find 3 interesting stats for the top of the digest
|
# Try to find 3 interesting stats for the top of the digest
|
||||||
|
|
||||||
new_topics_count = Topic.new_since_last_seen(user, min_date).count
|
new_topics_count = Topic.new_since_last_seen(user, min_date).count
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require_dependency 'slug'
|
||||||
|
|
||||||
class Badge < ActiveRecord::Base
|
class Badge < ActiveRecord::Base
|
||||||
# NOTE: These badge ids are not in order! They are grouped logically.
|
# NOTE: These badge ids are not in order! They are grouped logically.
|
||||||
# When picking an id, *search* for it.
|
# When picking an id, *search* for it.
|
||||||
|
@ -119,6 +121,10 @@ class Badge < ActiveRecord::Base
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def awarded_for_trust_level?
|
||||||
|
id <= 4
|
||||||
|
end
|
||||||
|
|
||||||
def reset_grant_count!
|
def reset_grant_count!
|
||||||
self.grant_count = UserBadge.where(badge_id: id).count
|
self.grant_count = UserBadge.where(badge_id: id).count
|
||||||
save!
|
save!
|
||||||
|
@ -208,6 +214,7 @@ SQL
|
||||||
def i18n_name
|
def i18n_name
|
||||||
self.name.downcase.tr(' ', '_')
|
self.name.downcase.tr(' ', '_')
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
|
|
|
@ -145,14 +145,14 @@ class GlobalSetting
|
||||||
attr_accessor :provider
|
attr_accessor :provider
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.configure!
|
||||||
if Rails.env == "test"
|
if Rails.env == "test"
|
||||||
@provider = BlankProvider.new
|
@provider = BlankProvider.new
|
||||||
else
|
else
|
||||||
@provider =
|
@provider =
|
||||||
FileProvider.from(File.expand_path('../../../config/discourse.conf', __FILE__)) ||
|
FileProvider.from(File.expand_path('../../../config/discourse.conf', __FILE__)) ||
|
||||||
EnvProvider.new
|
EnvProvider.new
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
load_defaults
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -55,7 +55,10 @@ class UserHistory < ActiveRecord::Base
|
||||||
rate_limited_like: 37, # not used anymore
|
rate_limited_like: 37, # not used anymore
|
||||||
revoke_email: 38,
|
revoke_email: 38,
|
||||||
deactivate_user: 39,
|
deactivate_user: 39,
|
||||||
wizard_step: 40
|
wizard_step: 40,
|
||||||
|
lock_trust_level: 41,
|
||||||
|
unlock_trust_level: 42,
|
||||||
|
activate_user: 43
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -91,7 +94,10 @@ class UserHistory < ActiveRecord::Base
|
||||||
:revoke_moderation,
|
:revoke_moderation,
|
||||||
:backup_operation,
|
:backup_operation,
|
||||||
:revoke_email,
|
:revoke_email,
|
||||||
:deactivate_user]
|
:deactivate_user,
|
||||||
|
:lock_trust_level,
|
||||||
|
:unlock_trust_level,
|
||||||
|
:activate_user]
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.staff_action_ids
|
def self.staff_action_ids
|
||||||
|
|
|
@ -274,7 +274,7 @@ class BadgeGranter
|
||||||
/*where*/
|
/*where*/
|
||||||
RETURNING id, user_id, granted_at
|
RETURNING id, user_id, granted_at
|
||||||
)
|
)
|
||||||
select w.*, username, locale FROM w
|
select w.*, username, locale, (u.admin OR u.moderator) AS staff FROM w
|
||||||
JOIN users u on u.id = w.user_id
|
JOIN users u on u.id = w.user_id
|
||||||
"
|
"
|
||||||
|
|
||||||
|
@ -315,6 +315,8 @@ class BadgeGranter
|
||||||
# Make this variable in this scope
|
# Make this variable in this scope
|
||||||
notification = nil
|
notification = nil
|
||||||
|
|
||||||
|
next if (row.staff && badge.awarded_for_trust_level?)
|
||||||
|
|
||||||
I18n.with_locale(notification_locale) do
|
I18n.with_locale(notification_locale) do
|
||||||
notification = Notification.create!(
|
notification = Notification.create!(
|
||||||
user_id: row.user_id,
|
user_id: row.user_id,
|
||||||
|
|
|
@ -14,7 +14,6 @@ class StaffActionLogger
|
||||||
raise Discourse::InvalidParameters.new(:deleted_user) unless deleted_user && deleted_user.is_a?(User)
|
raise Discourse::InvalidParameters.new(:deleted_user) unless deleted_user && deleted_user.is_a?(User)
|
||||||
UserHistory.create( params(opts).merge({
|
UserHistory.create( params(opts).merge({
|
||||||
action: UserHistory.actions[:delete_user],
|
action: UserHistory.actions[:delete_user],
|
||||||
email: deleted_user.email,
|
|
||||||
ip_address: deleted_user.ip_address.to_s,
|
ip_address: deleted_user.ip_address.to_s,
|
||||||
details: [:id, :username, :name, :created_at, :trust_level, :last_seen_at, :last_emailed_at].map { |x| "#{x}: #{deleted_user.send(x)}" }.join("\n")
|
details: [:id, :username, :name, :created_at, :trust_level, :last_seen_at, :last_emailed_at].map { |x| "#{x}: #{deleted_user.send(x)}" }.join("\n")
|
||||||
}))
|
}))
|
||||||
|
@ -96,6 +95,14 @@ class StaffActionLogger
|
||||||
}))
|
}))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def log_lock_trust_level(user, opts={})
|
||||||
|
raise Discourse::InvalidParameters.new(:user) unless user && user.is_a?(User)
|
||||||
|
UserHistory.create!( params(opts).merge({
|
||||||
|
action: UserHistory.actions[user.trust_level_locked ? :lock_trust_level : :unlock_trust_level],
|
||||||
|
target_user_id: user.id
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
|
||||||
def log_site_setting_change(setting_name, previous_value, new_value, opts={})
|
def log_site_setting_change(setting_name, previous_value, new_value, opts={})
|
||||||
raise Discourse::InvalidParameters.new(:setting_name) unless setting_name.present? && SiteSetting.respond_to?(setting_name)
|
raise Discourse::InvalidParameters.new(:setting_name) unless setting_name.present? && SiteSetting.respond_to?(setting_name)
|
||||||
UserHistory.create( params(opts).merge({
|
UserHistory.create( params(opts).merge({
|
||||||
|
@ -353,6 +360,15 @@ class StaffActionLogger
|
||||||
}))
|
}))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def log_user_activate(user, reason, opts={})
|
||||||
|
raise Discourse::InvalidParameters.new(:user) unless user
|
||||||
|
UserHistory.create(params(opts).merge({
|
||||||
|
action: UserHistory.actions[:activate_user],
|
||||||
|
target_user_id: user.id,
|
||||||
|
details: reason
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
|
||||||
def log_wizard_step(step, opts={})
|
def log_wizard_step(step, opts={})
|
||||||
raise Discourse::InvalidParameters.new(:step) unless step
|
raise Discourse::InvalidParameters.new(:step) unless step
|
||||||
UserHistory.create(params(opts).merge({
|
UserHistory.create(params(opts).merge({
|
||||||
|
|
|
@ -17,8 +17,11 @@ class UserBlocker
|
||||||
unless @user.blocked?
|
unless @user.blocked?
|
||||||
@user.blocked = true
|
@user.blocked = true
|
||||||
if @user.save
|
if @user.save
|
||||||
SystemMessage.create(@user, @opts[:message] || :blocked_by_staff)
|
message_type = @opts[:message] || :blocked_by_staff
|
||||||
StaffActionLogger.new(@by_user).log_block_user(@user) if @by_user
|
post = SystemMessage.create(@user, message_type)
|
||||||
|
if post && @by_user
|
||||||
|
StaffActionLogger.new(@by_user).log_block_user(@user, {context: "#{message_type}: '#{post.topic&.title rescue ''}'"})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
|
|
|
@ -149,7 +149,7 @@ body, table, td, th, h1, h2, h3 {font-family: Helvetica, Arial, sans-serif !impo
|
||||||
<p style="color:inherit;font-size:14px;font-weight:400;line-height:1.3;margin:0 0 8px 0;padding:0;word-wrap:normal;"><%= t.user.username -%></p>
|
<p style="color:inherit;font-size:14px;font-weight:400;line-height:1.3;margin:0 0 8px 0;padding:0;word-wrap:normal;"><%= t.user.username -%></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<%- if show_image_with_url(t.image_url) && t.featured_link.nil? -%>
|
<%- if show_image_with_url(t.image_url) && t.featured_link.nil? && !(@excerpts[t.first_post&.id]||"").include?(t.image_url) -%>
|
||||||
<td style="margin:0;padding:0 16px 0 8px;text-align:right;" align="right">
|
<td style="margin:0;padding:0 16px 0 8px;text-align:right;" align="right">
|
||||||
<img src="<%= url_for_email(t.image_url) -%>" height="64" style="margin:auto;max-height:64px;max-width:100%;outline:0;text-align:right;text-decoration:none;">
|
<img src="<%= url_for_email(t.image_url) -%>" height="64" style="margin:auto;max-height:64px;max-width:100%;outline:0;text-align:right;text-decoration:none;">
|
||||||
</td>
|
</td>
|
||||||
|
@ -163,7 +163,7 @@ body, table, td, th, h1, h2, h3 {font-family: Helvetica, Arial, sans-serif !impo
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="post-excerpt" style="color:#0a0a0a;font-size:14px;padding:0 16px 0 16px;text-align:left;width:100%;font-weight:normal;">
|
<td class="post-excerpt" style="color:#0a0a0a;font-size:14px;padding:0 16px 0 16px;text-align:left;width:100%;font-weight:normal;">
|
||||||
<%= email_excerpt(t.first_post.cooked) %>
|
<%= @excerpts[t.first_post.id] %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -6,8 +6,15 @@ require_relative '../lib/discourse_event'
|
||||||
require_relative '../lib/discourse_plugin'
|
require_relative '../lib/discourse_plugin'
|
||||||
require_relative '../lib/discourse_plugin_registry'
|
require_relative '../lib/discourse_plugin_registry'
|
||||||
|
|
||||||
|
require_relative '../lib/plugin_gem'
|
||||||
|
|
||||||
# Global config
|
# Global config
|
||||||
require_relative '../app/models/global_setting'
|
require_relative '../app/models/global_setting'
|
||||||
|
GlobalSetting.configure!
|
||||||
|
unless Rails.env.test? && ENV['LOAD_PLUGINS'] != "1"
|
||||||
|
require_relative '../lib/custom_setting_providers'
|
||||||
|
end
|
||||||
|
GlobalSetting.load_defaults
|
||||||
|
|
||||||
require 'pry-rails' if Rails.env.development?
|
require 'pry-rails' if Rails.env.development?
|
||||||
|
|
||||||
|
@ -15,8 +22,10 @@ if defined?(Bundler)
|
||||||
Bundler.require(*Rails.groups(assets: %w(development test profile)))
|
Bundler.require(*Rails.groups(assets: %w(development test profile)))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
module Discourse
|
module Discourse
|
||||||
class Application < Rails::Application
|
class Application < Rails::Application
|
||||||
|
|
||||||
def config.database_configuration
|
def config.database_configuration
|
||||||
if Rails.env.production?
|
if Rails.env.production?
|
||||||
GlobalSetting.database_config
|
GlobalSetting.database_config
|
||||||
|
|
|
@ -833,6 +833,7 @@ en:
|
||||||
none: "You haven't invited anyone here yet. You can send individual invites, or invite a bunch of people at once by <a href='https://meta.discourse.org/t/send-bulk-invites/16468'>uploading a CSV file</a>."
|
none: "You haven't invited anyone here yet. You can send individual invites, or invite a bunch of people at once by <a href='https://meta.discourse.org/t/send-bulk-invites/16468'>uploading a CSV file</a>."
|
||||||
text: "Bulk Invite from File"
|
text: "Bulk Invite from File"
|
||||||
success: "File uploaded successfully, you will be notified via message when the process is complete."
|
success: "File uploaded successfully, you will be notified via message when the process is complete."
|
||||||
|
error: "Sorry, file should be of csv format."
|
||||||
|
|
||||||
password:
|
password:
|
||||||
title: "Password"
|
title: "Password"
|
||||||
|
@ -2409,8 +2410,8 @@ en:
|
||||||
backups: "backups"
|
backups: "backups"
|
||||||
traffic_short: "Traffic"
|
traffic_short: "Traffic"
|
||||||
traffic: "Application web requests"
|
traffic: "Application web requests"
|
||||||
page_views: "API Requests"
|
page_views: "Pageviews"
|
||||||
page_views_short: "API Requests"
|
page_views_short: "Pageviews"
|
||||||
show_traffic_report: "Show Detailed Traffic Report"
|
show_traffic_report: "Show Detailed Traffic Report"
|
||||||
|
|
||||||
reports:
|
reports:
|
||||||
|
@ -2914,6 +2915,10 @@ en:
|
||||||
deleted_tag: "deleted tag"
|
deleted_tag: "deleted tag"
|
||||||
renamed_tag: "renamed tag"
|
renamed_tag: "renamed tag"
|
||||||
revoke_email: "revoke email"
|
revoke_email: "revoke email"
|
||||||
|
lock_trust_level: "lock trust level"
|
||||||
|
unlock_trust_level: "unlock trust level"
|
||||||
|
activate_user: "activate user"
|
||||||
|
deactivate_user: "deactivate user"
|
||||||
screened_emails:
|
screened_emails:
|
||||||
title: "Screened Emails"
|
title: "Screened Emails"
|
||||||
description: "When someone tries to create a new account, the following email addresses will be checked and the registration will be blocked, or some other action performed."
|
description: "When someone tries to create a new account, the following email addresses will be checked and the registration will be blocked, or some other action performed."
|
||||||
|
|
|
@ -53,6 +53,7 @@ en:
|
||||||
purge_reason: "Automatically deleted as abandoned, deactivated account"
|
purge_reason: "Automatically deleted as abandoned, deactivated account"
|
||||||
disable_remote_images_download_reason: "Remote images download was disabled because there wasn't enough disk space available."
|
disable_remote_images_download_reason: "Remote images download was disabled because there wasn't enough disk space available."
|
||||||
anonymous: "Anonymous"
|
anonymous: "Anonymous"
|
||||||
|
remove_posts_deleted_by_author: "Deleted by author"
|
||||||
|
|
||||||
emails:
|
emails:
|
||||||
incoming:
|
incoming:
|
||||||
|
@ -1615,6 +1616,8 @@ en:
|
||||||
user:
|
user:
|
||||||
no_accounts_associated: "No accounts associated"
|
no_accounts_associated: "No accounts associated"
|
||||||
deactivated: "Was deactivated due to too many bounced emails to '%{email}'."
|
deactivated: "Was deactivated due to too many bounced emails to '%{email}'."
|
||||||
|
deactivated_by_staff: "Deactivated by staff"
|
||||||
|
activated_by_staff: "Activated by staff"
|
||||||
username:
|
username:
|
||||||
short: "must be at least %{min} characters"
|
short: "must be at least %{min} characters"
|
||||||
long: "must be no more than %{max} characters"
|
long: "must be no more than %{max} characters"
|
||||||
|
|
|
@ -701,7 +701,7 @@ files:
|
||||||
default: 3072
|
default: 3072
|
||||||
authorized_extensions:
|
authorized_extensions:
|
||||||
client: true
|
client: true
|
||||||
default: 'jpg|jpeg|png|gif|csv'
|
default: 'jpg|jpeg|png|gif'
|
||||||
refresh: true
|
refresh: true
|
||||||
type: list
|
type: list
|
||||||
crawl_images:
|
crawl_images:
|
||||||
|
|
|
@ -52,7 +52,7 @@ class PostgreSQLFallbackHandler
|
||||||
logger.warn "#{log_prefix}: Master server is active. Reconnecting..."
|
logger.warn "#{log_prefix}: Master server is active. Reconnecting..."
|
||||||
|
|
||||||
self.master_up(key)
|
self.master_up(key)
|
||||||
Discourse.disable_readonly_mode
|
Discourse.disable_readonly_mode(Discourse::PG_READONLY_MODE_KEY)
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
logger.warn "#{log_prefix}: Connection to master PostgreSQL server failed with '#{e.message}'"
|
logger.warn "#{log_prefix}: Connection to master PostgreSQL server failed with '#{e.message}'"
|
||||||
|
@ -103,7 +103,7 @@ module ActiveRecord
|
||||||
}))
|
}))
|
||||||
|
|
||||||
verify_replica(connection)
|
verify_replica(connection)
|
||||||
Discourse.enable_readonly_mode
|
Discourse.enable_readonly_mode(Discourse::PG_READONLY_MODE_KEY)
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
connection = postgresql_connection(config)
|
connection = postgresql_connection(config)
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Support for plugins to register custom setting providers. They can do this
|
||||||
|
# by having a file, `register_provider.rb` in their root that will be run
|
||||||
|
# at this point.
|
||||||
|
|
||||||
|
Dir.glob(File.join(File.dirname(__FILE__), '../plugins', '*', "register_provider.rb")) do |p|
|
||||||
|
require p
|
||||||
|
end
|
|
@ -113,24 +113,6 @@ module Discourse
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.last_read_only
|
|
||||||
@last_read_only ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.recently_readonly?
|
|
||||||
read_only = last_read_only[$redis.namespace]
|
|
||||||
return false unless read_only
|
|
||||||
read_only > 15.seconds.ago
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.received_readonly!
|
|
||||||
last_read_only[$redis.namespace] = Time.zone.now
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.clear_readonly!
|
|
||||||
last_read_only[$redis.namespace] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.disabled_plugin_names
|
def self.disabled_plugin_names
|
||||||
plugins.select { |p| !p.enabled? }.map(&:name)
|
plugins.select { |p| !p.enabled? }.map(&:name)
|
||||||
end
|
end
|
||||||
|
@ -210,43 +192,66 @@ module Discourse
|
||||||
base_url_no_prefix + base_uri
|
base_url_no_prefix + base_uri
|
||||||
end
|
end
|
||||||
|
|
||||||
READONLY_MODE_KEY_TTL ||= 60
|
READONLY_MODE_KEY_TTL ||= 60
|
||||||
READONLY_MODE_KEY ||= 'readonly_mode'.freeze
|
READONLY_MODE_KEY ||= 'readonly_mode'.freeze
|
||||||
|
PG_READONLY_MODE_KEY ||= 'readonly_mode:postgres'.freeze
|
||||||
USER_READONLY_MODE_KEY ||= 'readonly_mode:user'.freeze
|
USER_READONLY_MODE_KEY ||= 'readonly_mode:user'.freeze
|
||||||
|
|
||||||
def self.enable_readonly_mode(user_enabled: false)
|
READONLY_KEYS ||= [
|
||||||
if user_enabled
|
READONLY_MODE_KEY,
|
||||||
$redis.set(USER_READONLY_MODE_KEY, 1)
|
PG_READONLY_MODE_KEY,
|
||||||
|
USER_READONLY_MODE_KEY
|
||||||
|
]
|
||||||
|
|
||||||
|
def self.enable_readonly_mode(key = READONLY_MODE_KEY)
|
||||||
|
if key == USER_READONLY_MODE_KEY
|
||||||
|
$redis.set(key, 1)
|
||||||
else
|
else
|
||||||
$redis.setex(READONLY_MODE_KEY, READONLY_MODE_KEY_TTL, 1)
|
$redis.setex(key, READONLY_MODE_KEY_TTL, 1)
|
||||||
keep_readonly_mode
|
keep_readonly_mode(key)
|
||||||
end
|
end
|
||||||
|
|
||||||
MessageBus.publish(readonly_channel, true)
|
MessageBus.publish(readonly_channel, true)
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.keep_readonly_mode
|
def self.keep_readonly_mode(key)
|
||||||
# extend the expiry by 1 minute every 30 seconds
|
# extend the expiry by 1 minute every 30 seconds
|
||||||
unless Rails.env.test?
|
unless Rails.env.test?
|
||||||
Thread.new do
|
Thread.new do
|
||||||
while readonly_mode?
|
while readonly_mode?
|
||||||
$redis.expire(READONLY_MODE_KEY, READONLY_MODE_KEY_TTL)
|
$redis.expire(key, READONLY_MODE_KEY_TTL)
|
||||||
sleep 30.seconds
|
sleep 30.seconds
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.disable_readonly_mode(user_enabled: false)
|
def self.disable_readonly_mode(key = READONLY_MODE_KEY)
|
||||||
key = user_enabled ? USER_READONLY_MODE_KEY : READONLY_MODE_KEY
|
|
||||||
$redis.del(key)
|
$redis.del(key)
|
||||||
MessageBus.publish(readonly_channel, false)
|
MessageBus.publish(readonly_channel, false)
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.readonly_mode?
|
def self.readonly_mode?
|
||||||
recently_readonly? || !!$redis.get(READONLY_MODE_KEY) || !!$redis.get(USER_READONLY_MODE_KEY)
|
recently_readonly? || READONLY_KEYS.any? { |key| !!$redis.get(key) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.last_read_only
|
||||||
|
@last_read_only ||= {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.recently_readonly?
|
||||||
|
return false unless read_only = last_read_only[$redis.namespace]
|
||||||
|
read_only > 15.seconds.ago
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.received_readonly!
|
||||||
|
last_read_only[$redis.namespace] = Time.zone.now
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.clear_readonly!
|
||||||
|
last_read_only[$redis.namespace] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.request_refresh!
|
def self.request_refresh!
|
||||||
|
|
|
@ -156,7 +156,7 @@ module Email
|
||||||
elsif bounce_score >= SiteSetting.bounce_score_threshold
|
elsif bounce_score >= SiteSetting.bounce_score_threshold
|
||||||
# NOTE: we check bounce_score before sending emails, nothing to do
|
# NOTE: we check bounce_score before sending emails, nothing to do
|
||||||
# here other than log it happened.
|
# here other than log it happened.
|
||||||
reason = I18n.t("user.email.revoked", email: user.email, date: user.user_stat.reset_bounce_score_after)
|
reason = I18n.t("user.email.revoked", date: user.user_stat.reset_bounce_score_after)
|
||||||
StaffActionLogger.new(Discourse.system_user).log_revoke_email(user, reason)
|
StaffActionLogger.new(Discourse.system_user).log_revoke_email(user, reason)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -239,6 +239,8 @@ module Email
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_from_field(mail)
|
def parse_from_field(mail)
|
||||||
|
return unless mail[:from]
|
||||||
|
|
||||||
if mail[:from].errors.blank?
|
if mail[:from].errors.blank?
|
||||||
mail[:from].address_list.addresses.each do |address_field|
|
mail[:from].address_list.addresses.each do |address_field|
|
||||||
address_field.decoded
|
address_field.decoded
|
||||||
|
|
|
@ -363,27 +363,7 @@ JS
|
||||||
#
|
#
|
||||||
# This is a very rough initial implementation
|
# This is a very rough initial implementation
|
||||||
def gem(name, version, opts = {})
|
def gem(name, version, opts = {})
|
||||||
gems_path = File.dirname(path) + "/gems/#{RUBY_VERSION}"
|
PluginGem.load(path, name, version, opts)
|
||||||
spec_path = gems_path + "/specifications"
|
|
||||||
spec_file = spec_path + "/#{name}-#{version}.gemspec"
|
|
||||||
unless File.exists? spec_file
|
|
||||||
command = "gem install #{name} -v #{version} -i #{gems_path} --no-document --ignore-dependencies"
|
|
||||||
if opts[:source]
|
|
||||||
command << " --source #{opts[:source]}"
|
|
||||||
end
|
|
||||||
puts command
|
|
||||||
puts `#{command}`
|
|
||||||
end
|
|
||||||
if File.exists? spec_file
|
|
||||||
spec = Gem::Specification.load spec_file
|
|
||||||
spec.activate
|
|
||||||
unless opts[:require] == false
|
|
||||||
require opts[:require_name] ? opts[:require_name] : name
|
|
||||||
end
|
|
||||||
else
|
|
||||||
puts "You are specifying the gem #{name} in #{path}, however it does not exist!"
|
|
||||||
exit(-1)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def enabled_site_setting(setting=nil)
|
def enabled_site_setting(setting=nil)
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
module PluginGem
|
||||||
|
def self.load(path, name, version, opts=nil)
|
||||||
|
opts ||= {}
|
||||||
|
|
||||||
|
gems_path = File.dirname(path) + "/gems/#{RUBY_VERSION}"
|
||||||
|
spec_path = gems_path + "/specifications"
|
||||||
|
spec_file = spec_path + "/#{name}-#{version}.gemspec"
|
||||||
|
unless File.exists? spec_file
|
||||||
|
command = "gem install #{name} -v #{version} -i #{gems_path} --no-document --ignore-dependencies"
|
||||||
|
if opts[:source]
|
||||||
|
command << " --source #{opts[:source]}"
|
||||||
|
end
|
||||||
|
puts command
|
||||||
|
puts `#{command}`
|
||||||
|
end
|
||||||
|
if File.exists? spec_file
|
||||||
|
spec = Gem::Specification.load spec_file
|
||||||
|
spec.activate
|
||||||
|
unless opts[:require] == false
|
||||||
|
require opts[:require_name] ? opts[:require_name] : name
|
||||||
|
end
|
||||||
|
else
|
||||||
|
puts "You are specifying the gem #{name} in #{path}, however it does not exist!"
|
||||||
|
exit(-1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -29,7 +29,7 @@ class PostDestroyer
|
||||||
pa.post_action_type_id IN (?)
|
pa.post_action_type_id IN (?)
|
||||||
)", PostActionType.notify_flag_type_ids)
|
)", PostActionType.notify_flag_type_ids)
|
||||||
.each do |post|
|
.each do |post|
|
||||||
PostDestroyer.new(Discourse.system_user, post).destroy
|
PostDestroyer.new(Discourse.system_user, post, {context: I18n.t('remove_posts_deleted_by_author')}).destroy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ module PrettyText
|
||||||
topic = Topic.find_by(id: topic_id)
|
topic = Topic.find_by(id: topic_id)
|
||||||
if topic && Guardian.new.can_see?(topic)
|
if topic && Guardian.new.can_see?(topic)
|
||||||
{
|
{
|
||||||
title: topic.title,
|
title: Rack::Utils.escape_html(topic.title),
|
||||||
href: topic.url
|
href: topic.url
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,6 +16,8 @@ class ImportScripts::Bbpress < ImportScripts::Base
|
||||||
BATCH_SIZE ||= 1000
|
BATCH_SIZE ||= 1000
|
||||||
BB_PRESS_PW ||= ENV['BBPRESS_PW'] || ""
|
BB_PRESS_PW ||= ENV['BBPRESS_PW'] || ""
|
||||||
BB_PRESS_USER ||= ENV['BBPRESS_USER'] || "root"
|
BB_PRESS_USER ||= ENV['BBPRESS_USER'] || "root"
|
||||||
|
BB_PRESS_PREFIX ||= ENV['BBPRESS_PREFIX'] || "wp_"
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
super
|
super
|
||||||
|
|
||||||
|
@ -37,12 +39,12 @@ class ImportScripts::Bbpress < ImportScripts::Base
|
||||||
puts "", "importing users..."
|
puts "", "importing users..."
|
||||||
|
|
||||||
last_user_id = -1
|
last_user_id = -1
|
||||||
total_users = bbpress_query("SELECT COUNT(*) count FROM wp_users WHERE user_email LIKE '%@%'").first["count"]
|
total_users = bbpress_query("SELECT COUNT(*) count FROM #{BB_PRESS_PREFIX}users WHERE user_email LIKE '%@%'").first["count"]
|
||||||
|
|
||||||
batches(BATCH_SIZE) do |offset|
|
batches(BATCH_SIZE) do |offset|
|
||||||
users = bbpress_query(<<-SQL
|
users = bbpress_query(<<-SQL
|
||||||
SELECT id, user_nicename, display_name, user_email, user_registered, user_url
|
SELECT id, user_nicename, display_name, user_email, user_registered, user_url
|
||||||
FROM wp_users
|
FROM #{BB_PRESS_PREFIX}users
|
||||||
WHERE user_email LIKE '%@%'
|
WHERE user_email LIKE '%@%'
|
||||||
AND id > #{last_user_id}
|
AND id > #{last_user_id}
|
||||||
ORDER BY id
|
ORDER BY id
|
||||||
|
@ -62,7 +64,7 @@ class ImportScripts::Bbpress < ImportScripts::Base
|
||||||
users_description = {}
|
users_description = {}
|
||||||
bbpress_query(<<-SQL
|
bbpress_query(<<-SQL
|
||||||
SELECT user_id, meta_value description
|
SELECT user_id, meta_value description
|
||||||
FROM wp_usermeta
|
FROM #{BB_PRESS_PREFIX}usermeta
|
||||||
WHERE user_id IN (#{user_ids_sql})
|
WHERE user_id IN (#{user_ids_sql})
|
||||||
AND meta_key = 'description'
|
AND meta_key = 'description'
|
||||||
SQL
|
SQL
|
||||||
|
@ -71,7 +73,7 @@ class ImportScripts::Bbpress < ImportScripts::Base
|
||||||
users_last_activity = {}
|
users_last_activity = {}
|
||||||
bbpress_query(<<-SQL
|
bbpress_query(<<-SQL
|
||||||
SELECT user_id, meta_value last_activity
|
SELECT user_id, meta_value last_activity
|
||||||
FROM wp_usermeta
|
FROM #{BB_PRESS_PREFIX}usermeta
|
||||||
WHERE user_id IN (#{user_ids_sql})
|
WHERE user_id IN (#{user_ids_sql})
|
||||||
AND meta_key = 'last_activity'
|
AND meta_key = 'last_activity'
|
||||||
SQL
|
SQL
|
||||||
|
@ -97,7 +99,7 @@ class ImportScripts::Bbpress < ImportScripts::Base
|
||||||
|
|
||||||
categories = bbpress_query(<<-SQL
|
categories = bbpress_query(<<-SQL
|
||||||
SELECT id, post_name, post_parent
|
SELECT id, post_name, post_parent
|
||||||
FROM wp_posts
|
FROM #{BB_PRESS_PREFIX}posts
|
||||||
WHERE post_type = 'forum'
|
WHERE post_type = 'forum'
|
||||||
AND LENGTH(COALESCE(post_name, '')) > 0
|
AND LENGTH(COALESCE(post_name, '')) > 0
|
||||||
ORDER BY post_parent, id
|
ORDER BY post_parent, id
|
||||||
|
@ -119,7 +121,7 @@ class ImportScripts::Bbpress < ImportScripts::Base
|
||||||
last_post_id = -1
|
last_post_id = -1
|
||||||
total_posts = bbpress_query(<<-SQL
|
total_posts = bbpress_query(<<-SQL
|
||||||
SELECT COUNT(*) count
|
SELECT COUNT(*) count
|
||||||
FROM wp_posts
|
FROM #{BB_PRESS_PREFIX}posts
|
||||||
WHERE post_status <> 'spam'
|
WHERE post_status <> 'spam'
|
||||||
AND post_type IN ('topic', 'reply')
|
AND post_type IN ('topic', 'reply')
|
||||||
SQL
|
SQL
|
||||||
|
@ -134,7 +136,7 @@ class ImportScripts::Bbpress < ImportScripts::Base
|
||||||
post_title,
|
post_title,
|
||||||
post_type,
|
post_type,
|
||||||
post_parent
|
post_parent
|
||||||
FROM wp_posts
|
FROM #{BB_PRESS_PREFIX}posts
|
||||||
WHERE post_status <> 'spam'
|
WHERE post_status <> 'spam'
|
||||||
AND post_type IN ('topic', 'reply')
|
AND post_type IN ('topic', 'reply')
|
||||||
AND id > #{last_post_id}
|
AND id > #{last_post_id}
|
||||||
|
@ -155,7 +157,7 @@ class ImportScripts::Bbpress < ImportScripts::Base
|
||||||
posts_likes = {}
|
posts_likes = {}
|
||||||
bbpress_query(<<-SQL
|
bbpress_query(<<-SQL
|
||||||
SELECT post_id, meta_value likes
|
SELECT post_id, meta_value likes
|
||||||
FROM wp_postmeta
|
FROM #{BB_PRESS_PREFIX}postmeta
|
||||||
WHERE post_id IN (#{post_ids_sql})
|
WHERE post_id IN (#{post_ids_sql})
|
||||||
AND meta_key = 'Likes'
|
AND meta_key = 'Likes'
|
||||||
SQL
|
SQL
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
require "mysql2"
|
||||||
|
require File.expand_path(File.dirname(__FILE__) + "/base.rb")
|
||||||
|
|
||||||
|
class ImportScripts::Drupal < ImportScripts::Base
|
||||||
|
|
||||||
|
DRUPAL_DB = ENV['DRUPAL_DB'] || "newsite3"
|
||||||
|
VID = ENV['DRUPAL_VID'] || 1
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
super
|
||||||
|
|
||||||
|
@client = Mysql2::Client.new(
|
||||||
|
host: "localhost",
|
||||||
|
username: "root",
|
||||||
|
#password: "password",
|
||||||
|
database: DRUPAL_DB
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def categories_query
|
||||||
|
@client.query("SELECT tid, name, description FROM term_data WHERE vid = #{VID}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute
|
||||||
|
create_users(@client.query("SELECT uid id, name, mail email, created FROM users;")) do |row|
|
||||||
|
{id: row['id'], username: row['name'], email: row['email'], created_at: Time.zone.at(row['created'])}
|
||||||
|
end
|
||||||
|
|
||||||
|
# You'll need to edit the following query for your Drupal install:
|
||||||
|
#
|
||||||
|
# * Drupal allows duplicate category names, so you may need to exclude some categories or rename them here.
|
||||||
|
# * Table name may be term_data.
|
||||||
|
# * May need to select a vid other than 1.
|
||||||
|
create_categories(categories_query) do |c|
|
||||||
|
{id: c['tid'], name: c['name'], description: c['description']}
|
||||||
|
end
|
||||||
|
|
||||||
|
# "Nodes" in Drupal are divided into types. Here we import two types,
|
||||||
|
# and will later import all the comments/replies for each node.
|
||||||
|
# You will need to figure out what the type names are on your install and edit the queries to match.
|
||||||
|
if ENV['DRUPAL_IMPORT_BLOG']
|
||||||
|
create_blog_topics
|
||||||
|
end
|
||||||
|
|
||||||
|
create_forum_topics
|
||||||
|
|
||||||
|
create_replies
|
||||||
|
|
||||||
|
begin
|
||||||
|
create_admin(email: 'neil.lalonde@discourse.org', username: UserNameSuggester.suggest('neil'))
|
||||||
|
rescue => e
|
||||||
|
puts '', "Failed to create admin user"
|
||||||
|
puts e.message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_blog_topics
|
||||||
|
puts '', "creating blog topics"
|
||||||
|
|
||||||
|
create_category({
|
||||||
|
name: 'Blog',
|
||||||
|
user_id: -1,
|
||||||
|
description: "Articles from the blog"
|
||||||
|
}, nil) unless Category.find_by_name('Blog')
|
||||||
|
|
||||||
|
results = @client.query("
|
||||||
|
SELECT n.nid nid,
|
||||||
|
n.title title,
|
||||||
|
n.uid uid,
|
||||||
|
n.created created,
|
||||||
|
n.sticky sticky,
|
||||||
|
nr.body body
|
||||||
|
FROM node n
|
||||||
|
LEFT JOIN node_revisions nr ON nr.vid=n.vid
|
||||||
|
WHERE n.type = 'blog'
|
||||||
|
AND n.status = 1
|
||||||
|
", cache_rows: false)
|
||||||
|
|
||||||
|
create_posts(results) do |row|
|
||||||
|
{
|
||||||
|
id: "nid:#{row['nid']}",
|
||||||
|
user_id: user_id_from_imported_user_id(row['uid']) || -1,
|
||||||
|
category: 'Blog',
|
||||||
|
raw: row['body'],
|
||||||
|
created_at: Time.zone.at(row['created']),
|
||||||
|
pinned_at: row['sticky'].to_i == 1 ? Time.zone.at(row['created']) : nil,
|
||||||
|
title: row['title'].try(:strip),
|
||||||
|
custom_fields: {import_id: "nid:#{row['nid']}"}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_forum_topics
|
||||||
|
puts '', "creating forum topics"
|
||||||
|
|
||||||
|
total_count = @client.query("
|
||||||
|
SELECT COUNT(*) count
|
||||||
|
FROM node n
|
||||||
|
LEFT JOIN forum f ON f.vid=n.vid
|
||||||
|
WHERE n.type = 'forum'
|
||||||
|
AND n.status = 1
|
||||||
|
").first['count']
|
||||||
|
|
||||||
|
batch_size = 1000
|
||||||
|
|
||||||
|
batches(batch_size) do |offset|
|
||||||
|
results = @client.query("
|
||||||
|
SELECT n.nid nid,
|
||||||
|
n.title title,
|
||||||
|
f.tid tid,
|
||||||
|
n.uid uid,
|
||||||
|
n.created created,
|
||||||
|
n.sticky sticky,
|
||||||
|
nr.body body
|
||||||
|
FROM node n
|
||||||
|
LEFT JOIN forum f ON f.vid=n.vid
|
||||||
|
LEFT JOIN node_revisions nr ON nr.vid=n.vid
|
||||||
|
WHERE node.type = 'forum'
|
||||||
|
AND node.status = 1
|
||||||
|
LIMIT #{batch_size}
|
||||||
|
OFFSET #{offset};
|
||||||
|
", cache_rows: false)
|
||||||
|
|
||||||
|
break if results.size < 1
|
||||||
|
|
||||||
|
next if all_records_exist? :posts, results.map {|p| "nid:#{p['nid']}"}
|
||||||
|
|
||||||
|
create_posts(results, total: total_count, offset: offset) do |row|
|
||||||
|
{
|
||||||
|
id: "nid:#{row['nid']}",
|
||||||
|
user_id: user_id_from_imported_user_id(row['uid']) || -1,
|
||||||
|
category: category_id_from_imported_category_id(row['tid']),
|
||||||
|
raw: row['body'],
|
||||||
|
created_at: Time.zone.at(row['created']),
|
||||||
|
pinned_at: row['sticky'].to_i == 1 ? Time.zone.at(row['created']) : nil,
|
||||||
|
title: row['title'].try(:strip)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_replies
|
||||||
|
puts '', "creating replies in topics"
|
||||||
|
|
||||||
|
if ENV['DRUPAL_IMPORT_BLOG']
|
||||||
|
node_types = "('forum','blog')"
|
||||||
|
else
|
||||||
|
node_types = "('forum')"
|
||||||
|
end
|
||||||
|
|
||||||
|
total_count = @client.query("
|
||||||
|
SELECT COUNT(*) count
|
||||||
|
FROM comments c
|
||||||
|
LEFT JOIN node n ON n.nid=c.nid
|
||||||
|
WHERE node.type IN #{node_types}
|
||||||
|
AND node.status = 1
|
||||||
|
AND comments.status=0;
|
||||||
|
").first['count']
|
||||||
|
|
||||||
|
batch_size = 1000
|
||||||
|
|
||||||
|
batches(batch_size) do |offset|
|
||||||
|
results = @client.query("
|
||||||
|
SELECT c.cid,
|
||||||
|
c.pid,
|
||||||
|
c.nid,
|
||||||
|
c.uid,
|
||||||
|
c.timestamp,
|
||||||
|
c.comment body
|
||||||
|
FROM comments c
|
||||||
|
LEFT JOIN node n ON n.nid=c.nid
|
||||||
|
WHERE n.type IN #{node_types}
|
||||||
|
AND n.status = 1
|
||||||
|
AND c.status=0
|
||||||
|
LIMIT #{batch_size}
|
||||||
|
OFFSET #{offset};
|
||||||
|
", cache_rows: false)
|
||||||
|
|
||||||
|
break if results.size < 1
|
||||||
|
|
||||||
|
next if all_records_exist? :posts, results.map {|p| "cid:#{p['cid']}"}
|
||||||
|
|
||||||
|
create_posts(results, total: total_count, offset: offset) do |row|
|
||||||
|
topic_mapping = topic_lookup_from_imported_post_id("nid:#{row['nid']}")
|
||||||
|
if topic_mapping && topic_id = topic_mapping[:topic_id]
|
||||||
|
h = {
|
||||||
|
id: "cid:#{row['cid']}",
|
||||||
|
topic_id: topic_id,
|
||||||
|
user_id: user_id_from_imported_user_id(row['uid']) || -1,
|
||||||
|
raw: row['body'],
|
||||||
|
created_at: Time.zone.at(row['timestamp']),
|
||||||
|
}
|
||||||
|
if row['pid']
|
||||||
|
parent = topic_lookup_from_imported_post_id("cid:#{row['pid']}")
|
||||||
|
h[:reply_to_post_number] = parent[:post_number] if parent and parent[:post_number] > 1
|
||||||
|
end
|
||||||
|
h
|
||||||
|
else
|
||||||
|
puts "No topic found for comment #{row['cid']}"
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
if __FILE__==$0
|
||||||
|
ImportScripts::Drupal.new.perform
|
||||||
|
end
|
|
@ -42,8 +42,13 @@ describe ActiveRecord::ConnectionHandling do
|
||||||
end
|
end
|
||||||
|
|
||||||
after do
|
after do
|
||||||
with_multisite_db(multisite_db) { Discourse.disable_readonly_mode }
|
pg_readonly_mode_key = Discourse::PG_READONLY_MODE_KEY
|
||||||
Discourse.disable_readonly_mode
|
|
||||||
|
with_multisite_db(multisite_db) do
|
||||||
|
Discourse.disable_readonly_mode(pg_readonly_mode_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
Discourse.disable_readonly_mode(pg_readonly_mode_key)
|
||||||
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Rails.env])
|
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Rails.env])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ describe Discourse do
|
||||||
context 'user enabled readonly mode' do
|
context 'user enabled readonly mode' do
|
||||||
it "adds a key in redis and publish a message through the message bus" do
|
it "adds a key in redis and publish a message through the message bus" do
|
||||||
expect($redis.get(user_readonly_mode_key)).to eq(nil)
|
expect($redis.get(user_readonly_mode_key)).to eq(nil)
|
||||||
message = MessageBus.track_publish { Discourse.enable_readonly_mode(user_enabled: true) }.first
|
message = MessageBus.track_publish { Discourse.enable_readonly_mode(user_readonly_mode_key) }.first
|
||||||
assert_readonly_mode(message, user_readonly_mode_key)
|
assert_readonly_mode(message, user_readonly_mode_key)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -160,10 +160,10 @@ describe Discourse do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns true when user enabled readonly mode key is present in redis" do
|
it "returns true when user enabled readonly mode key is present in redis" do
|
||||||
Discourse.enable_readonly_mode(user_enabled: true)
|
Discourse.enable_readonly_mode(user_readonly_mode_key)
|
||||||
expect(Discourse.readonly_mode?).to eq(true)
|
expect(Discourse.readonly_mode?).to eq(true)
|
||||||
|
|
||||||
Discourse.disable_readonly_mode(user_enabled: true)
|
Discourse.disable_readonly_mode(user_readonly_mode_key)
|
||||||
expect(Discourse.readonly_mode?).to eq(false)
|
expect(Discourse.readonly_mode?).to eq(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -383,37 +383,46 @@ describe Email::Receiver do
|
||||||
expect(Post.last.raw).to match(/discourse\.rb/)
|
expect(Post.last.raw).to match(/discourse\.rb/)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "handles forwarded emails" do
|
context "with forwarded emails enabled" do
|
||||||
SiteSetting.enable_forwarded_emails = true
|
before { SiteSetting.enable_forwarded_emails = true }
|
||||||
expect { process(:forwarded_email_1) }.to change(Topic, :count)
|
|
||||||
|
|
||||||
forwarded_post, last_post = *Post.last(2)
|
it "handles forwarded emails" do
|
||||||
|
expect { process(:forwarded_email_1) }.to change(Topic, :count)
|
||||||
|
|
||||||
expect(forwarded_post.user.email).to eq("some@one.com")
|
forwarded_post, last_post = *Post.last(2)
|
||||||
expect(last_post.user.email).to eq("ba@bar.com")
|
|
||||||
|
|
||||||
expect(forwarded_post.raw).to match(/XoXo/)
|
expect(forwarded_post.user.email).to eq("some@one.com")
|
||||||
expect(last_post.raw).to match(/can you have a look at this email below/)
|
expect(last_post.user.email).to eq("ba@bar.com")
|
||||||
|
|
||||||
expect(last_post.post_type).to eq(Post.types[:regular])
|
expect(forwarded_post.raw).to match(/XoXo/)
|
||||||
end
|
expect(last_post.raw).to match(/can you have a look at this email below/)
|
||||||
|
|
||||||
it "handles weirdly forwarded emails" do
|
expect(last_post.post_type).to eq(Post.types[:regular])
|
||||||
group.add(Fabricate(:user, email: "ba@bar.com"))
|
end
|
||||||
group.save
|
|
||||||
|
|
||||||
SiteSetting.enable_forwarded_emails = true
|
it "handles weirdly forwarded emails" do
|
||||||
expect { process(:forwarded_email_2) }.to change(Topic, :count)
|
group.add(Fabricate(:user, email: "ba@bar.com"))
|
||||||
|
group.save
|
||||||
|
|
||||||
forwarded_post, last_post = *Post.last(2)
|
SiteSetting.enable_forwarded_emails = true
|
||||||
|
expect { process(:forwarded_email_2) }.to change(Topic, :count)
|
||||||
|
|
||||||
expect(forwarded_post.user.email).to eq("some@one.com")
|
forwarded_post, last_post = *Post.last(2)
|
||||||
expect(last_post.user.email).to eq("ba@bar.com")
|
|
||||||
|
|
||||||
expect(forwarded_post.raw).to match(/XoXo/)
|
expect(forwarded_post.user.email).to eq("some@one.com")
|
||||||
expect(last_post.raw).to match(/can you have a look at this email below/)
|
expect(last_post.user.email).to eq("ba@bar.com")
|
||||||
|
|
||||||
|
expect(forwarded_post.raw).to match(/XoXo/)
|
||||||
|
expect(last_post.raw).to match(/can you have a look at this email below/)
|
||||||
|
|
||||||
|
expect(last_post.post_type).to eq(Post.types[:whisper])
|
||||||
|
end
|
||||||
|
|
||||||
|
# Who thought this was a good idea?!
|
||||||
|
it "doesn't blow up with localized email headers" do
|
||||||
|
expect { process(:forwarded_email_3) }.to change(Topic, :count)
|
||||||
|
end
|
||||||
|
|
||||||
expect(last_post.post_type).to eq(Post.types[:whisper])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,10 +10,10 @@ describe PrettyText do
|
||||||
|
|
||||||
describe "off topic quoting" do
|
describe "off topic quoting" do
|
||||||
it "can correctly populate topic title" do
|
it "can correctly populate topic title" do
|
||||||
topic = Fabricate(:topic, title: "this is a test topic")
|
topic = Fabricate(:topic, title: "this is a test topic :slight_smile:")
|
||||||
expected = <<HTML
|
expected = <<HTML
|
||||||
<aside class="quote" data-post="2" data-topic="#{topic.id}"><div class="title">
|
<aside class="quote" data-post="2" data-topic="#{topic.id}"><div class="title">
|
||||||
<div class="quote-controls"></div><a href="http://test.localhost/t/this-is-a-test-topic/#{topic.id}/2">This is a test topic</a>
|
<div class="quote-controls"></div><a href="http://test.localhost/t/this-is-a-test-topic-slight-smile/#{topic.id}/2">This is a test topic <img src="/images/emoji/emoji_one/slight_smile.png?v=3" title="slight_smile" alt="slight_smile" class="emoji"></a>
|
||||||
</div>
|
</div>
|
||||||
<blockquote><p>ddd</p></blockquote></aside>
|
<blockquote><p>ddd</p></blockquote></aside>
|
||||||
HTML
|
HTML
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
Message-ID: <60@foo.bar.mail>
|
||||||
|
From: Ba Bar <ba@bar.com>
|
||||||
|
To: Team <team@bar.com>
|
||||||
|
Date: Mon, 9 Dec 2016 13:37:42 +0100
|
||||||
|
Subject: Fwd: Ça Discourse ?
|
||||||
|
|
||||||
|
@team, can you have a look at this email below?
|
||||||
|
|
||||||
|
Objet: Ça Discourse ?
|
||||||
|
Date: 2017-01-04 11:27
|
||||||
|
De: Un Français <un@francais.fr>
|
||||||
|
À: ba@bar.com
|
||||||
|
|
||||||
|
Bonjour,
|
||||||
|
|
||||||
|
Ça Discourse bien aujourd'hui ?
|
||||||
|
|
||||||
|
Bises
|
|
@ -369,4 +369,42 @@ describe StaffActionLogger do
|
||||||
expect(user_history.action).to eq(UserHistory.actions[:create_category])
|
expect(user_history.action).to eq(UserHistory.actions[:create_category])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'log_lock_trust_level' do
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
it "raises an error when argument is missing" do
|
||||||
|
expect { logger.log_lock_trust_level(nil) }.to raise_error(Discourse::InvalidParameters)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a new UserHistory record" do
|
||||||
|
user.trust_level_locked = true
|
||||||
|
expect { logger.log_lock_trust_level(user) }.to change { UserHistory.count }.by(1)
|
||||||
|
user_history = UserHistory.last
|
||||||
|
expect(user_history.action).to eq(UserHistory.actions[:lock_trust_level])
|
||||||
|
|
||||||
|
user.trust_level_locked = false
|
||||||
|
expect { logger.log_lock_trust_level(user) }.to change { UserHistory.count }.by(1)
|
||||||
|
user_history = UserHistory.last
|
||||||
|
expect(user_history.action).to eq(UserHistory.actions[:unlock_trust_level])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'log_user_activate' do
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
it "raises an error when argument is missing" do
|
||||||
|
expect { logger.log_user_activate(nil, nil) }.to raise_error(Discourse::InvalidParameters)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a new UserHistory record" do
|
||||||
|
reason = "Staff activated from admin"
|
||||||
|
expect {
|
||||||
|
logger.log_user_activate(user, reason)
|
||||||
|
}.to change { UserHistory.count }.by(1)
|
||||||
|
user_history = UserHistory.last
|
||||||
|
expect(user_history.action).to eq(UserHistory.actions[:activate_user])
|
||||||
|
expect(user_history.details).to eq(reason)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -58,6 +58,14 @@ describe UserBlocker do
|
||||||
SystemMessage.expects(:create).never
|
SystemMessage.expects(:create).never
|
||||||
expect(block_user).to eq(false)
|
expect(block_user).to eq(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "logs it with context" do
|
||||||
|
SystemMessage.stubs(:create).returns(Fabricate.build(:post))
|
||||||
|
expect {
|
||||||
|
UserBlocker.block(user, Fabricate(:admin))
|
||||||
|
}.to change { UserHistory.count }.by(1)
|
||||||
|
expect(UserHistory.last.context).to be_present
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'unblock' do
|
describe 'unblock' do
|
||||||
|
@ -81,6 +89,12 @@ describe UserBlocker do
|
||||||
SystemMessage.expects(:create).never
|
SystemMessage.expects(:create).never
|
||||||
unblock_user
|
unblock_user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "logs it" do
|
||||||
|
expect {
|
||||||
|
unblock_user
|
||||||
|
}.to change { UserHistory.count }.by(1)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'hide_posts' do
|
describe 'hide_posts' do
|
||||||
|
|
Loading…
Reference in New Issue