Merge branch 'master' of github.com:discourse/discourse into bbpress-missing-display-name

This commit is contained in:
Jay Pfaffman 2017-01-11 11:28:38 -08:00
commit e307bbccf9
38 changed files with 545 additions and 129 deletions

View File

@ -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="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> &nbsp;
<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> &nbsp;
<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/).

View File

@ -6,6 +6,10 @@ export default Em.Component.extend(UploadMixin, {
tagName: "span",
uploadUrl: "/invites/upload_csv",
validateUploadedFilesOptions() {
return { csvOnly: true };
},
@computed("uploading")
uploadButtonText(uploading) {
return uploading ? I18n.t("uploading") : I18n.t("user.invited.bulk_invite.text");

View File

@ -192,6 +192,11 @@ export function validateUploadedFile(file, opts) {
bootbox.alert(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: authorizedImagesExtensions() }));
return false;
}
} else if (opts["csvOnly"]) {
if (!(/\.csv$/i).test(name)) {
bootbox.alert(I18n.t('user.invited.bulk_invite.error'));
return false;
}
} else {
if (!authorizesAllExtensions() && !isAuthorizedFile(name)) {
bootbox.alert(I18n.t('post.errors.upload_not_authorized', { authorized_extensions: authorizedExtensions() }));

View File

@ -1,9 +1,18 @@
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) {
register(helper, 'quote', {noWrap: true, singlePara: true}, (contents, bbParams, options) => {
const params = {'class': 'quote'};
let username = null;
const opts = helper.getOptions();
if (bbParams) {
const paramsSplit = bbParams.split(/\,\s*/);
@ -52,7 +61,16 @@ export function setup(helper) {
if (postNumber > 0) { href += "/" + postNumber; }
// get rid of username said stuff
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]);
}
}

View File

@ -85,6 +85,7 @@ fieldset {
pre code {
overflow: auto;
tab-size: 4;
}
// TODO figure out a clean place to put stuff like this

View File

@ -95,7 +95,14 @@ class Admin::BackupsController < Admin::AdminController
def readonly
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
end

View File

@ -46,6 +46,7 @@ class Admin::UsersController < Admin::AdminController
def delete_all_posts
@user = User.find_by(id: params[:user_id])
@user.delete_all_posts!(guardian)
# staff action logs will have an entry for each post
render nothing: true
end
@ -182,6 +183,8 @@ class Admin::UsersController < Admin::AdminController
@user.trust_level_locked = new_lock == "true"
@user.save
StaffActionLogger.new(current_user).log_lock_trust_level(@user)
unless @user.trust_level_locked
p = Promotion.new(@user)
2.times{ p.review }
@ -210,12 +213,14 @@ class Admin::UsersController < Admin::AdminController
def activate
guardian.ensure_can_activate!(@user)
@user.activate
StaffActionLogger.new(current_user).log_user_activate(@user, I18n.t('user.activated_by_staff'))
render json: success_json
end
def deactivate
guardian.ensure_can_deactivate!(@user)
@user.deactivate
StaffActionLogger.new(current_user).log_user_deactivate(@user, I18n.t('user.deactivated_by_staff'))
refresh_browser @user
render nothing: true
end

View File

@ -156,9 +156,9 @@ class InvitesController < ApplicationController
Scheduler::Defer.later("Upload CSV") do
begin
data = if extension == ".csv"
data = if extension.downcase == ".csv"
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}
else
failed_json.merge(errors: [I18n.t("bulk_invite.file_should_be_csv")])

View File

@ -17,7 +17,7 @@ class MetadataController < ApplicationController
name: SiteSetting.title,
short_name: SiteSetting.title,
display: 'standalone',
orientation: 'portrait',
orientation: 'any',
start_url: "#{Discourse.base_uri}/",
background_color: "##{ColorScheme.hex_for_name('secondary')}",
theme_color: "##{ColorScheme.hex_for_name('header_background')}",

View File

@ -3,6 +3,7 @@ require_dependency 'email/message_builder'
require_dependency 'age_words'
class UserNotifications < ActionMailer::Base
include UserNotificationsHelper
helper :application
default charset: 'UTF-8'
@ -106,6 +107,8 @@ class UserNotifications < ActionMailer::Base
end
@popular_topics = topics_for_digest[0,SiteSetting.digest_topics]
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
@ -119,7 +122,12 @@ class UserNotifications < ActionMailer::Base
[]
end
if @popular_topics.present?
@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
new_topics_count = Topic.new_since_last_seen(user, min_date).count

View File

@ -1,3 +1,5 @@
require_dependency 'slug'
class Badge < ActiveRecord::Base
# NOTE: These badge ids are not in order! They are grouped logically.
# When picking an id, *search* for it.
@ -119,6 +121,10 @@ class Badge < ActiveRecord::Base
}
end
def awarded_for_trust_level?
id <= 4
end
def reset_grant_count!
self.grant_count = UserBadge.where(badge_id: id).count
save!
@ -208,6 +214,7 @@ SQL
def i18n_name
self.name.downcase.tr(' ', '_')
end
end
# == Schema Information

View File

@ -145,7 +145,7 @@ class GlobalSetting
attr_accessor :provider
end
def self.configure!
if Rails.env == "test"
@provider = BlankProvider.new
else
@ -153,6 +153,6 @@ class GlobalSetting
FileProvider.from(File.expand_path('../../../config/discourse.conf', __FILE__)) ||
EnvProvider.new
end
end
load_defaults
end

View File

@ -55,7 +55,10 @@ class UserHistory < ActiveRecord::Base
rate_limited_like: 37, # not used anymore
revoke_email: 38,
deactivate_user: 39,
wizard_step: 40
wizard_step: 40,
lock_trust_level: 41,
unlock_trust_level: 42,
activate_user: 43
)
end
@ -91,7 +94,10 @@ class UserHistory < ActiveRecord::Base
:revoke_moderation,
:backup_operation,
:revoke_email,
:deactivate_user]
:deactivate_user,
:lock_trust_level,
:unlock_trust_level,
:activate_user]
end
def self.staff_action_ids

View File

@ -274,7 +274,7 @@ class BadgeGranter
/*where*/
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
"
@ -315,6 +315,8 @@ class BadgeGranter
# Make this variable in this scope
notification = nil
next if (row.staff && badge.awarded_for_trust_level?)
I18n.with_locale(notification_locale) do
notification = Notification.create!(
user_id: row.user_id,

View File

@ -14,7 +14,6 @@ class StaffActionLogger
raise Discourse::InvalidParameters.new(:deleted_user) unless deleted_user && deleted_user.is_a?(User)
UserHistory.create( params(opts).merge({
action: UserHistory.actions[:delete_user],
email: deleted_user.email,
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")
}))
@ -96,6 +95,14 @@ class StaffActionLogger
}))
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={})
raise Discourse::InvalidParameters.new(:setting_name) unless setting_name.present? && SiteSetting.respond_to?(setting_name)
UserHistory.create( params(opts).merge({
@ -353,6 +360,15 @@ class StaffActionLogger
}))
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={})
raise Discourse::InvalidParameters.new(:step) unless step
UserHistory.create(params(opts).merge({

View File

@ -17,8 +17,11 @@ class UserBlocker
unless @user.blocked?
@user.blocked = true
if @user.save
SystemMessage.create(@user, @opts[:message] || :blocked_by_staff)
StaffActionLogger.new(@by_user).log_block_user(@user) if @by_user
message_type = @opts[:message] || :blocked_by_staff
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
else
false

View File

@ -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>
<% end %>
</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">
<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>
@ -163,7 +163,7 @@ body, table, td, th, h1, h2, h3 {font-family: Helvetica, Arial, sans-serif !impo
<tbody>
<tr>
<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>
</tr>
</tbody>

View File

@ -6,8 +6,15 @@ require_relative '../lib/discourse_event'
require_relative '../lib/discourse_plugin'
require_relative '../lib/discourse_plugin_registry'
require_relative '../lib/plugin_gem'
# Global config
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?
@ -15,8 +22,10 @@ if defined?(Bundler)
Bundler.require(*Rails.groups(assets: %w(development test profile)))
end
module Discourse
class Application < Rails::Application
def config.database_configuration
if Rails.env.production?
GlobalSetting.database_config

View File

@ -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>."
text: "Bulk Invite from File"
success: "File uploaded successfully, you will be notified via message when the process is complete."
error: "Sorry, file should be of csv format."
password:
title: "Password"
@ -2409,8 +2410,8 @@ en:
backups: "backups"
traffic_short: "Traffic"
traffic: "Application web requests"
page_views: "API Requests"
page_views_short: "API Requests"
page_views: "Pageviews"
page_views_short: "Pageviews"
show_traffic_report: "Show Detailed Traffic Report"
reports:
@ -2914,6 +2915,10 @@ en:
deleted_tag: "deleted tag"
renamed_tag: "renamed tag"
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:
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."

View File

@ -53,6 +53,7 @@ en:
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."
anonymous: "Anonymous"
remove_posts_deleted_by_author: "Deleted by author"
emails:
incoming:
@ -1615,6 +1616,8 @@ en:
user:
no_accounts_associated: "No accounts associated"
deactivated: "Was deactivated due to too many bounced emails to '%{email}'."
deactivated_by_staff: "Deactivated by staff"
activated_by_staff: "Activated by staff"
username:
short: "must be at least %{min} characters"
long: "must be no more than %{max} characters"

View File

@ -701,7 +701,7 @@ files:
default: 3072
authorized_extensions:
client: true
default: 'jpg|jpeg|png|gif|csv'
default: 'jpg|jpeg|png|gif'
refresh: true
type: list
crawl_images:

View File

@ -52,7 +52,7 @@ class PostgreSQLFallbackHandler
logger.warn "#{log_prefix}: Master server is active. Reconnecting..."
self.master_up(key)
Discourse.disable_readonly_mode
Discourse.disable_readonly_mode(Discourse::PG_READONLY_MODE_KEY)
end
rescue => e
logger.warn "#{log_prefix}: Connection to master PostgreSQL server failed with '#{e.message}'"
@ -103,7 +103,7 @@ module ActiveRecord
}))
verify_replica(connection)
Discourse.enable_readonly_mode
Discourse.enable_readonly_mode(Discourse::PG_READONLY_MODE_KEY)
else
begin
connection = postgresql_connection(config)

View File

@ -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

View File

@ -113,24 +113,6 @@ module Discourse
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
plugins.select { |p| !p.enabled? }.map(&:name)
end
@ -212,41 +194,64 @@ module Discourse
READONLY_MODE_KEY_TTL ||= 60
READONLY_MODE_KEY ||= 'readonly_mode'.freeze
PG_READONLY_MODE_KEY ||= 'readonly_mode:postgres'.freeze
USER_READONLY_MODE_KEY ||= 'readonly_mode:user'.freeze
def self.enable_readonly_mode(user_enabled: false)
if user_enabled
$redis.set(USER_READONLY_MODE_KEY, 1)
READONLY_KEYS ||= [
READONLY_MODE_KEY,
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
$redis.setex(READONLY_MODE_KEY, READONLY_MODE_KEY_TTL, 1)
keep_readonly_mode
$redis.setex(key, READONLY_MODE_KEY_TTL, 1)
keep_readonly_mode(key)
end
MessageBus.publish(readonly_channel, true)
true
end
def self.keep_readonly_mode
def self.keep_readonly_mode(key)
# extend the expiry by 1 minute every 30 seconds
unless Rails.env.test?
Thread.new do
while readonly_mode?
$redis.expire(READONLY_MODE_KEY, READONLY_MODE_KEY_TTL)
$redis.expire(key, READONLY_MODE_KEY_TTL)
sleep 30.seconds
end
end
end
end
def self.disable_readonly_mode(user_enabled: false)
key = user_enabled ? USER_READONLY_MODE_KEY : READONLY_MODE_KEY
def self.disable_readonly_mode(key = READONLY_MODE_KEY)
$redis.del(key)
MessageBus.publish(readonly_channel, false)
true
end
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
def self.request_refresh!

View File

@ -156,7 +156,7 @@ module Email
elsif bounce_score >= SiteSetting.bounce_score_threshold
# NOTE: we check bounce_score before sending emails, nothing to do
# 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)
end
end
@ -239,6 +239,8 @@ module Email
end
def parse_from_field(mail)
return unless mail[:from]
if mail[:from].errors.blank?
mail[:from].address_list.addresses.each do |address_field|
address_field.decoded

View File

@ -363,27 +363,7 @@ JS
#
# This is a very rough initial implementation
def gem(name, version, 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
PluginGem.load(path, name, version, opts)
end
def enabled_site_setting(setting=nil)

27
lib/plugin_gem.rb Normal file
View File

@ -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

View File

@ -29,7 +29,7 @@ class PostDestroyer
pa.post_action_type_id IN (?)
)", PostActionType.notify_flag_type_ids)
.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

View File

@ -50,7 +50,7 @@ module PrettyText
topic = Topic.find_by(id: topic_id)
if topic && Guardian.new.can_see?(topic)
{
title: topic.title,
title: Rack::Utils.escape_html(topic.title),
href: topic.url
}
end

View File

@ -16,6 +16,8 @@ class ImportScripts::Bbpress < ImportScripts::Base
BATCH_SIZE ||= 1000
BB_PRESS_PW ||= ENV['BBPRESS_PW'] || ""
BB_PRESS_USER ||= ENV['BBPRESS_USER'] || "root"
BB_PRESS_PREFIX ||= ENV['BBPRESS_PREFIX'] || "wp_"
def initialize
super
@ -37,12 +39,12 @@ class ImportScripts::Bbpress < ImportScripts::Base
puts "", "importing users..."
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|
users = bbpress_query(<<-SQL
SELECT id, user_nicename, display_name, user_email, user_registered, user_url
FROM wp_users
FROM #{BB_PRESS_PREFIX}users
WHERE user_email LIKE '%@%'
AND id > #{last_user_id}
ORDER BY id
@ -62,7 +64,7 @@ class ImportScripts::Bbpress < ImportScripts::Base
users_description = {}
bbpress_query(<<-SQL
SELECT user_id, meta_value description
FROM wp_usermeta
FROM #{BB_PRESS_PREFIX}usermeta
WHERE user_id IN (#{user_ids_sql})
AND meta_key = 'description'
SQL
@ -71,7 +73,7 @@ class ImportScripts::Bbpress < ImportScripts::Base
users_last_activity = {}
bbpress_query(<<-SQL
SELECT user_id, meta_value last_activity
FROM wp_usermeta
FROM #{BB_PRESS_PREFIX}usermeta
WHERE user_id IN (#{user_ids_sql})
AND meta_key = 'last_activity'
SQL
@ -97,7 +99,7 @@ class ImportScripts::Bbpress < ImportScripts::Base
categories = bbpress_query(<<-SQL
SELECT id, post_name, post_parent
FROM wp_posts
FROM #{BB_PRESS_PREFIX}posts
WHERE post_type = 'forum'
AND LENGTH(COALESCE(post_name, '')) > 0
ORDER BY post_parent, id
@ -119,7 +121,7 @@ class ImportScripts::Bbpress < ImportScripts::Base
last_post_id = -1
total_posts = bbpress_query(<<-SQL
SELECT COUNT(*) count
FROM wp_posts
FROM #{BB_PRESS_PREFIX}posts
WHERE post_status <> 'spam'
AND post_type IN ('topic', 'reply')
SQL
@ -134,7 +136,7 @@ class ImportScripts::Bbpress < ImportScripts::Base
post_title,
post_type,
post_parent
FROM wp_posts
FROM #{BB_PRESS_PREFIX}posts
WHERE post_status <> 'spam'
AND post_type IN ('topic', 'reply')
AND id > #{last_post_id}
@ -155,7 +157,7 @@ class ImportScripts::Bbpress < ImportScripts::Base
posts_likes = {}
bbpress_query(<<-SQL
SELECT post_id, meta_value likes
FROM wp_postmeta
FROM #{BB_PRESS_PREFIX}postmeta
WHERE post_id IN (#{post_ids_sql})
AND meta_key = 'Likes'
SQL

View File

@ -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

View File

@ -42,8 +42,13 @@ describe ActiveRecord::ConnectionHandling do
end
after do
with_multisite_db(multisite_db) { Discourse.disable_readonly_mode }
Discourse.disable_readonly_mode
pg_readonly_mode_key = Discourse::PG_READONLY_MODE_KEY
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])
end

View File

@ -118,7 +118,7 @@ describe Discourse do
context 'user enabled readonly mode' 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)
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)
end
end
@ -160,10 +160,10 @@ describe Discourse do
end
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)
Discourse.disable_readonly_mode(user_enabled: true)
Discourse.disable_readonly_mode(user_readonly_mode_key)
expect(Discourse.readonly_mode?).to eq(false)
end
end

View File

@ -383,8 +383,10 @@ describe Email::Receiver do
expect(Post.last.raw).to match(/discourse\.rb/)
end
context "with forwarded emails enabled" do
before { SiteSetting.enable_forwarded_emails = true }
it "handles forwarded emails" do
SiteSetting.enable_forwarded_emails = true
expect { process(:forwarded_email_1) }.to change(Topic, :count)
forwarded_post, last_post = *Post.last(2)
@ -416,6 +418,13 @@ describe Email::Receiver do
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
end
end
context "new topic in a category" do

View File

@ -10,10 +10,10 @@ describe PrettyText do
describe "off topic quoting" 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
<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>
<blockquote><p>ddd</p></blockquote></aside>
HTML

View File

@ -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

View File

@ -369,4 +369,42 @@ describe StaffActionLogger do
expect(user_history.action).to eq(UserHistory.actions[:create_category])
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

View File

@ -58,6 +58,14 @@ describe UserBlocker do
SystemMessage.expects(:create).never
expect(block_user).to eq(false)
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
describe 'unblock' do
@ -81,6 +89,12 @@ describe UserBlocker do
SystemMessage.expects(:create).never
unblock_user
end
it "logs it" do
expect {
unblock_user
}.to change { UserHistory.count }.by(1)
end
end
describe 'hide_posts' do