Merge branch 'master' into pt_BR
This commit is contained in:
commit
e257cfc56c
69
Gemfile
69
Gemfile
|
@ -1,5 +1,60 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
# monkey patching to support dual booting
|
||||
module Bundler::SharedHelpers
|
||||
def default_lockfile=(path)
|
||||
@default_lockfile = path
|
||||
end
|
||||
def default_lockfile
|
||||
@default_lockfile ||= Pathname.new("#{default_gemfile}.lock")
|
||||
end
|
||||
end
|
||||
|
||||
module ::Kernel
|
||||
def rails4?
|
||||
!!ENV["RAILS4"]
|
||||
end
|
||||
end
|
||||
|
||||
if rails4?
|
||||
Bundler::SharedHelpers.default_lockfile = Pathname.new("#{Bundler::SharedHelpers.default_gemfile}_rails4.lock")
|
||||
|
||||
# Bundler::Dsl.evaluate already called with an incorrect lockfile ... fix it
|
||||
class Bundler::Dsl
|
||||
# A bit messy, this can be called multiple times by bundler, avoid blowing the stack
|
||||
unless self.method_defined? :to_definition_unpatched
|
||||
alias_method :to_definition_unpatched, :to_definition
|
||||
puts "Booting in Rails 4 mode"
|
||||
end
|
||||
def to_definition(bad_lockfile, unlock)
|
||||
to_definition_unpatched(Bundler::SharedHelpers.default_lockfile, unlock)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if rails4?
|
||||
gem 'rails', '4.0.0'
|
||||
gem 'redis-rails', :git => 'git://github.com/SamSaffron/redis-store.git'
|
||||
gem 'rails-observers'
|
||||
gem 'protected_attributes'
|
||||
gem 'actionpack-action_caching'
|
||||
gem 'seed-fu' , github: 'mbleigh/seed-fu'
|
||||
else
|
||||
# we had pain with the 3.2.13 upgrade so monkey patch the security fix
|
||||
# next time around we hope to upgrade
|
||||
gem 'rails', '3.2.12'
|
||||
gem 'strong_parameters' # remove when we upgrade to Rails 4
|
||||
# we are using a custom sprockets repo to work around: https://github.com/rails/rails/issues/8099#issuecomment-16137638
|
||||
# REVIEW EVERY RELEASE
|
||||
gem 'sprockets', git: 'https://github.com/SamSaffron/sprockets.git', branch: 'rails-compat'
|
||||
gem 'redis-rails'
|
||||
gem 'seed-fu'
|
||||
end
|
||||
|
||||
gem 'redis'
|
||||
gem 'hiredis'
|
||||
gem 'em-redis'
|
||||
|
||||
gem 'active_model_serializers', git: 'https://github.com/rails-api/active_model_serializers.git'
|
||||
|
||||
# we had issues with latest, stick to the rev till we figure this out
|
||||
|
@ -20,13 +75,11 @@ gem 'activerecord-postgres-hstore'
|
|||
gem 'active_attr' # until we get ActiveModel::Model with Rails 4
|
||||
gem 'airbrake', '3.1.2', require: false # errbit is broken with 3.1.3 for now
|
||||
gem 'clockwork', require: false
|
||||
gem 'em-redis'
|
||||
gem 'eventmachine'
|
||||
gem 'fast_xs'
|
||||
gem 'fast_xor', git: 'https://github.com/CodeMonkeySteve/fast_xor.git'
|
||||
gem 'fastimage'
|
||||
gem 'fog', require: false
|
||||
gem 'hiredis'
|
||||
|
||||
gem 'email_reply_parser', git: 'https://github.com/lawrencepit/email_reply_parser.git'
|
||||
|
||||
|
@ -49,22 +102,17 @@ gem 'omniauth-browserid', git: 'https://github.com/callahad/omniauth-browserid.g
|
|||
gem 'omniauth-cas'
|
||||
gem 'oj'
|
||||
gem 'pg'
|
||||
# we had pain with the 3.2.13 upgrade so monkey patch the security fix
|
||||
# next time around we hope to upgrade
|
||||
gem 'rails', '3.2.12'
|
||||
gem 'rake'
|
||||
gem 'redis'
|
||||
gem 'redis-rails'
|
||||
|
||||
|
||||
gem 'rest-client'
|
||||
gem 'rinku'
|
||||
gem 'sanitize'
|
||||
gem 'sass'
|
||||
gem 'seed-fu'
|
||||
gem 'sidekiq'
|
||||
gem 'sidekiq-failures'
|
||||
gem 'sinatra', require: nil
|
||||
gem 'slim' # required for sidekiq-web
|
||||
gem 'strong_parameters' # remove when we upgrade to Rails 4
|
||||
gem 'therubyracer', require: 'v8'
|
||||
gem 'thin', require: false
|
||||
gem 'diffy', require: false
|
||||
|
@ -123,9 +171,6 @@ group :development do
|
|||
gem 'annotate', :git => 'https://github.com/SamSaffron/annotate_models.git'
|
||||
end
|
||||
|
||||
# we are using a custom sprockets repo to work around: https://github.com/rails/rails/issues/8099#issuecomment-16137638
|
||||
# REVIEW EVERY RELEASE
|
||||
gem 'sprockets', git: 'https://github.com/SamSaffron/sprockets.git', branch: 'rails-compat'
|
||||
|
||||
|
||||
# this is an optional gem, it provides a high performance replacement
|
||||
|
|
|
@ -16,7 +16,7 @@ GIT
|
|||
|
||||
GIT
|
||||
remote: https://github.com/SamSaffron/message_bus
|
||||
revision: 9c16e7ebaafaf2a3933a84fa1c517c0eba44b052
|
||||
revision: c357cbfea34eec3cca8f00754eb9d5bd2be98594
|
||||
specs:
|
||||
message_bus (0.0.2)
|
||||
eventmachine
|
||||
|
@ -93,7 +93,7 @@ PATH
|
|||
remote: vendor/gems/simple_handlebars_rails
|
||||
specs:
|
||||
simple_handlebars_rails (0.0.1)
|
||||
rails (~> 3.1)
|
||||
rails (> 3.1)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
|
|
|
@ -0,0 +1,571 @@
|
|||
GIT
|
||||
remote: git://github.com/SamSaffron/redis-store.git
|
||||
revision: 1eafaa3d8bfbcb61ad89d1a2831adbba4ea8e1e1
|
||||
specs:
|
||||
redis-actionpack (3.2.3)
|
||||
actionpack (>= 3.2.3)
|
||||
redis-rack (~> 1.4.0)
|
||||
redis-store (~> 1.1.0)
|
||||
redis-activesupport (3.2.3)
|
||||
activesupport (>= 3.2.3)
|
||||
redis-store (~> 1.1.0)
|
||||
redis-rack (1.4.2)
|
||||
rack (> 1.4.1)
|
||||
redis-store (~> 1.1.0)
|
||||
redis-rails (3.2.3)
|
||||
redis-actionpack (>= 3.2.3)
|
||||
redis-activesupport (>= 3.2.3)
|
||||
redis-store (~> 1.1.0)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/mbleigh/seed-fu.git
|
||||
revision: f89ea306472c500ec7911c7be111a4aad9c1bc78
|
||||
specs:
|
||||
seed-fu (2.2.0)
|
||||
activerecord (>= 3.1, < 4.1)
|
||||
activesupport (>= 3.1, < 4.1)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/CodeMonkeySteve/fast_xor.git
|
||||
revision: 85b79ec6d116f9680f23bd2c5c8c2c2039d477d8
|
||||
specs:
|
||||
fast_xor (1.1.2)
|
||||
rake
|
||||
rake-compiler
|
||||
|
||||
GIT
|
||||
remote: https://github.com/SamSaffron/annotate_models.git
|
||||
revision: ebe4ba7e3f6ceeb43e4e40078da2b261a1bb71b2
|
||||
specs:
|
||||
annotate (2.6.0.beta1)
|
||||
activerecord (>= 2.3.0)
|
||||
rake (>= 0.8.7)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/SamSaffron/message_bus
|
||||
revision: 09392967940daf77943d1489ed3f1f71d6f8450a
|
||||
specs:
|
||||
message_bus (0.0.2)
|
||||
eventmachine
|
||||
rack (>= 1.1.3)
|
||||
redis
|
||||
thin
|
||||
|
||||
GIT
|
||||
remote: https://github.com/SamSaffron/redis-rack-cache.git
|
||||
revision: 379ef30e31d4e185cb1d7f8badca0cc06403eba2
|
||||
specs:
|
||||
redis-rack-cache (1.2.1)
|
||||
rack-cache (~> 1.2)
|
||||
redis-store (~> 1.1.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/callahad/omniauth-browserid.git
|
||||
revision: af62d667626c1622de6fe13b60849c3640765ab1
|
||||
branch: observer_api
|
||||
specs:
|
||||
omniauth-browserid (0.0.2)
|
||||
faraday
|
||||
multi_json
|
||||
omniauth (~> 1.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/lawrencepit/email_reply_parser.git
|
||||
revision: 67408dfb1b99fb8d5f145f782b9e22d1851a8e5a
|
||||
specs:
|
||||
email_reply_parser (0.6)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/rails-api/active_model_serializers.git
|
||||
revision: 8ac4bf90067eef442a6208848f86e55892d724f1
|
||||
specs:
|
||||
active_model_serializers (0.8.1)
|
||||
activemodel (>= 3.2)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/zhangyuan/vestal_versions
|
||||
revision: 0ea75ec4e269b5a9e609639919ade0f36381a446
|
||||
specs:
|
||||
vestal_versions (1.2.2)
|
||||
activerecord (>= 3.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
|
||||
PATH
|
||||
remote: vendor/gems/discourse_emoji
|
||||
specs:
|
||||
discourse_emoji (0.0.1)
|
||||
|
||||
PATH
|
||||
remote: vendor/gems/discourse_plugin
|
||||
specs:
|
||||
discourse_plugin (0.0.1)
|
||||
|
||||
PATH
|
||||
remote: vendor/gems/rails_multisite
|
||||
specs:
|
||||
rails_multisite (0.0.1)
|
||||
|
||||
PATH
|
||||
remote: vendor/gems/simple_handlebars_rails
|
||||
specs:
|
||||
simple_handlebars_rails (0.0.1)
|
||||
rails (> 3.1)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (4.0.0)
|
||||
actionpack (= 4.0.0)
|
||||
mail (~> 2.5.3)
|
||||
actionpack (4.0.0)
|
||||
activesupport (= 4.0.0)
|
||||
builder (~> 3.1.0)
|
||||
erubis (~> 2.7.0)
|
||||
rack (~> 1.5.2)
|
||||
rack-test (~> 0.6.2)
|
||||
actionpack-action_caching (1.0.0)
|
||||
actionpack (>= 4.0.0.beta, < 5.0)
|
||||
active_attr (0.8.2)
|
||||
activemodel (>= 3.0.2, < 4.1)
|
||||
activesupport (>= 3.0.2, < 4.1)
|
||||
activemodel (4.0.0)
|
||||
activesupport (= 4.0.0)
|
||||
builder (~> 3.1.0)
|
||||
activerecord (4.0.0)
|
||||
activemodel (= 4.0.0)
|
||||
activerecord-deprecated_finders (~> 1.0.2)
|
||||
activesupport (= 4.0.0)
|
||||
arel (~> 4.0.0)
|
||||
activerecord-deprecated_finders (1.0.3)
|
||||
activerecord-postgres-hstore (0.7.6)
|
||||
activerecord (>= 3.1)
|
||||
pg-hstore (>= 1.1.5)
|
||||
rake
|
||||
activesupport (4.0.0)
|
||||
i18n (~> 0.6, >= 0.6.4)
|
||||
minitest (~> 4.2)
|
||||
multi_json (~> 1.3)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo (~> 0.3.37)
|
||||
addressable (2.3.5)
|
||||
airbrake (3.1.2)
|
||||
activesupport
|
||||
builder
|
||||
arel (4.0.0)
|
||||
atomic (1.1.10)
|
||||
barber (0.4.2)
|
||||
ember-source
|
||||
execjs
|
||||
handlebars-source
|
||||
better_errors (0.9.0)
|
||||
coderay (>= 1.0.0)
|
||||
erubis (>= 2.6.6)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
builder (3.1.4)
|
||||
celluloid (0.14.1)
|
||||
timers (>= 1.0.0)
|
||||
certified (0.1.1)
|
||||
childprocess (0.3.9)
|
||||
ffi (~> 1.0, >= 1.0.11)
|
||||
clockwork (0.5.3)
|
||||
activesupport (~> 4.0.0)
|
||||
tzinfo (~> 0.3.35)
|
||||
coderay (1.0.9)
|
||||
connection_pool (1.1.0)
|
||||
daemons (1.1.9)
|
||||
debug_inspector (0.0.2)
|
||||
diff-lcs (1.2.4)
|
||||
diffy (3.0.1)
|
||||
em-redis (0.3.0)
|
||||
eventmachine
|
||||
ember-data-source (0.13)
|
||||
ember-source
|
||||
ember-rails (0.13.0)
|
||||
active_model_serializers
|
||||
barber (>= 0.4.1)
|
||||
ember-data-source
|
||||
ember-source
|
||||
execjs (>= 1.2)
|
||||
handlebars-source
|
||||
railties (>= 3.1)
|
||||
ember-source (1.0.0.rc6.2)
|
||||
handlebars-source (= 1.0.12)
|
||||
erubis (2.7.0)
|
||||
eventmachine (1.0.3)
|
||||
excon (0.25.3)
|
||||
execjs (1.4.0)
|
||||
multi_json (~> 1.0)
|
||||
fabrication (2.7.2)
|
||||
fakeweb (1.3.0)
|
||||
faraday (0.8.7)
|
||||
multipart-post (~> 1.1)
|
||||
fast_blank (0.0.1)
|
||||
rake
|
||||
rake-compiler
|
||||
fast_xs (0.8.0)
|
||||
fastimage (1.5.0)
|
||||
ffi (1.9.0)
|
||||
fog (1.14.0)
|
||||
builder
|
||||
excon (~> 0.25.0)
|
||||
formatador (~> 0.2.0)
|
||||
mime-types
|
||||
multi_json (~> 1.0)
|
||||
net-scp (~> 1.1)
|
||||
net-ssh (>= 2.1.3)
|
||||
nokogiri (~> 1.5)
|
||||
ruby-hmac
|
||||
formatador (0.2.4)
|
||||
fspath (2.0.4)
|
||||
given_core (3.0.0)
|
||||
sorcerer (>= 0.3.7)
|
||||
guard (1.8.1)
|
||||
formatador (>= 0.2.4)
|
||||
listen (>= 1.0.0)
|
||||
lumberjack (>= 1.0.2)
|
||||
pry (>= 0.9.10)
|
||||
thor (>= 0.14.6)
|
||||
guard-rspec (3.0.2)
|
||||
guard (>= 1.8)
|
||||
rspec (~> 2.13)
|
||||
guard-spork (1.5.1)
|
||||
childprocess (>= 0.2.3)
|
||||
guard (>= 1.1)
|
||||
spork (>= 0.8.4)
|
||||
handlebars-source (1.0.12)
|
||||
hashie (2.0.5)
|
||||
highline (1.6.19)
|
||||
hike (1.2.3)
|
||||
hiredis (0.4.5)
|
||||
httpauth (0.2.0)
|
||||
i18n (0.6.4)
|
||||
image_optim (0.8.1)
|
||||
fspath (~> 2.0.3)
|
||||
image_size (~> 1.1.2)
|
||||
in_threads (~> 1.1.1)
|
||||
progress (~> 2.4.0)
|
||||
image_size (1.1.2)
|
||||
image_sorcery (1.1.0)
|
||||
in_threads (1.1.1)
|
||||
json (1.8.0)
|
||||
jwt (0.1.8)
|
||||
multi_json (>= 1.5)
|
||||
kgio (2.8.0)
|
||||
librarian (0.1.0)
|
||||
highline
|
||||
thor (~> 0.15)
|
||||
libv8 (3.11.8.17)
|
||||
listen (1.2.2)
|
||||
rb-fsevent (>= 0.9.3)
|
||||
rb-inotify (>= 0.9)
|
||||
rb-kqueue (>= 0.2)
|
||||
lru_redux (0.0.6)
|
||||
lumberjack (1.0.4)
|
||||
mail (2.5.4)
|
||||
mime-types (~> 1.16)
|
||||
treetop (~> 1.4.8)
|
||||
metaclass (0.0.1)
|
||||
method_source (0.8.1)
|
||||
mime-types (1.23)
|
||||
mini_portile (0.5.1)
|
||||
minitest (4.7.5)
|
||||
mocha (0.14.0)
|
||||
metaclass (~> 0.0.1)
|
||||
multi_json (1.7.7)
|
||||
multipart-post (1.2.0)
|
||||
mustache (0.99.4)
|
||||
net-scp (1.1.2)
|
||||
net-ssh (>= 2.6.5)
|
||||
net-ssh (2.6.8)
|
||||
nokogiri (1.6.0)
|
||||
mini_portile (~> 0.5.0)
|
||||
oauth (0.4.7)
|
||||
oauth2 (0.8.1)
|
||||
faraday (~> 0.8)
|
||||
httpauth (~> 0.1)
|
||||
jwt (~> 0.1.4)
|
||||
multi_json (~> 1.0)
|
||||
rack (~> 1.2)
|
||||
oj (2.1.4)
|
||||
omniauth (1.1.4)
|
||||
hashie (>= 1.2, < 3)
|
||||
rack
|
||||
omniauth-cas (1.0.4)
|
||||
addressable (~> 2.3)
|
||||
nokogiri (~> 1.6)
|
||||
omniauth (~> 1.1.0)
|
||||
omniauth-facebook (1.4.1)
|
||||
omniauth-oauth2 (~> 1.1.0)
|
||||
omniauth-github (1.1.1)
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (~> 1.1)
|
||||
omniauth-oauth (1.0.1)
|
||||
oauth
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (1.1.1)
|
||||
oauth2 (~> 0.8.0)
|
||||
omniauth (~> 1.0)
|
||||
omniauth-openid (1.0.1)
|
||||
omniauth (~> 1.0)
|
||||
rack-openid (~> 1.3.1)
|
||||
omniauth-twitter (1.0.0)
|
||||
multi_json (~> 1.3)
|
||||
omniauth-oauth (~> 1.0)
|
||||
openid-redis-store (0.0.2)
|
||||
redis
|
||||
ruby-openid
|
||||
pg (0.15.1)
|
||||
pg-hstore (1.1.7)
|
||||
polyglot (0.3.3)
|
||||
progress (2.4.0)
|
||||
protected_attributes (1.0.3)
|
||||
activemodel (>= 4.0.0, < 5.0)
|
||||
pry (0.9.12.2)
|
||||
coderay (~> 1.0.5)
|
||||
method_source (~> 0.8)
|
||||
slop (~> 3.4)
|
||||
pry-nav (0.2.3)
|
||||
pry (~> 0.9.10)
|
||||
pry-rails (0.3.1)
|
||||
pry (>= 0.9.10)
|
||||
qunit-rails (0.0.3)
|
||||
railties (>= 3.2.3)
|
||||
rack (1.5.2)
|
||||
rack-cache (1.2)
|
||||
rack (>= 0.4)
|
||||
rack-cors (0.2.8)
|
||||
rack
|
||||
rack-mini-profiler (0.1.27)
|
||||
rack (>= 1.1.3)
|
||||
rack-openid (1.3.1)
|
||||
rack (>= 1.1.0)
|
||||
ruby-openid (>= 2.1.8)
|
||||
rack-protection (1.5.0)
|
||||
rack
|
||||
rack-test (0.6.2)
|
||||
rack (>= 1.0)
|
||||
rails (4.0.0)
|
||||
actionmailer (= 4.0.0)
|
||||
actionpack (= 4.0.0)
|
||||
activerecord (= 4.0.0)
|
||||
activesupport (= 4.0.0)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.0.0)
|
||||
sprockets-rails (~> 2.0.0)
|
||||
rails-observers (0.1.2)
|
||||
activemodel (~> 4.0)
|
||||
railties (4.0.0)
|
||||
actionpack (= 4.0.0)
|
||||
activesupport (= 4.0.0)
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
raindrops (0.11.0)
|
||||
rake (10.1.0)
|
||||
rake-compiler (0.8.3)
|
||||
rake
|
||||
rb-fsevent (0.9.3)
|
||||
rb-inotify (0.9.0)
|
||||
ffi (>= 0.5.0)
|
||||
rb-kqueue (0.2.0)
|
||||
ffi (>= 0.5.0)
|
||||
redcarpet (3.0.0)
|
||||
redis (3.0.4)
|
||||
redis-namespace (1.3.0)
|
||||
redis (~> 3.0.0)
|
||||
redis-store (1.1.2)
|
||||
redis (>= 2.2.0)
|
||||
ref (1.0.5)
|
||||
rest-client (1.6.7)
|
||||
mime-types (>= 1.16)
|
||||
rinku (1.7.3)
|
||||
rspec (2.14.1)
|
||||
rspec-core (~> 2.14.0)
|
||||
rspec-expectations (~> 2.14.0)
|
||||
rspec-mocks (~> 2.14.0)
|
||||
rspec-core (2.14.3)
|
||||
rspec-expectations (2.14.0)
|
||||
diff-lcs (>= 1.1.3, < 2.0)
|
||||
rspec-given (3.0.0)
|
||||
given_core (= 3.0.0)
|
||||
rspec (>= 2.12)
|
||||
rspec-mocks (2.14.1)
|
||||
rspec-rails (2.14.0)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 2.14.0)
|
||||
rspec-expectations (~> 2.14.0)
|
||||
rspec-mocks (~> 2.14.0)
|
||||
ruby-hmac (0.4.0)
|
||||
ruby-openid (2.2.3)
|
||||
sanitize (2.0.6)
|
||||
nokogiri (>= 1.4.4)
|
||||
sass (3.2.9)
|
||||
sass-rails (4.0.0)
|
||||
railties (>= 4.0.0.beta, < 5.0)
|
||||
sass (>= 3.1.10)
|
||||
sprockets-rails (~> 2.0.0)
|
||||
shoulda (3.5.0)
|
||||
shoulda-context (~> 1.0, >= 1.0.1)
|
||||
shoulda-matchers (>= 1.4.1, < 3.0)
|
||||
shoulda-context (1.1.4)
|
||||
shoulda-matchers (2.2.0)
|
||||
activesupport (>= 3.0.0)
|
||||
sidekiq (2.13.0)
|
||||
celluloid (>= 0.14.1)
|
||||
connection_pool (>= 1.0.0)
|
||||
json
|
||||
redis (>= 3.0)
|
||||
redis-namespace
|
||||
sidekiq-failures (0.2.1)
|
||||
sidekiq (>= 2.2.1)
|
||||
simplecov (0.7.1)
|
||||
multi_json (~> 1.0)
|
||||
simplecov-html (~> 0.7.1)
|
||||
simplecov-html (0.7.1)
|
||||
sinatra (1.4.3)
|
||||
rack (~> 1.4)
|
||||
rack-protection (~> 1.4)
|
||||
tilt (~> 1.3, >= 1.3.4)
|
||||
slim (2.0.0)
|
||||
temple (~> 0.6.5)
|
||||
tilt (~> 1.3, >= 1.3.3)
|
||||
slop (3.4.5)
|
||||
sorcerer (1.0.0)
|
||||
spork (0.9.2)
|
||||
sprockets (2.10.0)
|
||||
hike (~> 1.2)
|
||||
multi_json (~> 1.0)
|
||||
rack (~> 1.0)
|
||||
tilt (~> 1.1, != 1.3.0)
|
||||
sprockets-rails (2.0.0)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
sprockets (~> 2.8)
|
||||
temple (0.6.5)
|
||||
terminal-notifier-guard (1.5.3)
|
||||
therubyracer (0.11.4)
|
||||
libv8 (~> 3.11.8.12)
|
||||
ref
|
||||
thin (1.5.1)
|
||||
daemons (>= 1.0.9)
|
||||
eventmachine (>= 0.12.6)
|
||||
rack (>= 1.0.0)
|
||||
thor (0.18.1)
|
||||
thread_safe (0.1.0)
|
||||
atomic
|
||||
tilt (1.4.1)
|
||||
timecop (0.6.2.2)
|
||||
timers (1.1.0)
|
||||
treetop (1.4.14)
|
||||
polyglot
|
||||
polyglot (>= 0.3.1)
|
||||
turbo-sprockets-rails3 (0.2.9)
|
||||
railties (>= 3.1.0)
|
||||
sprockets (>= 2.0.0)
|
||||
tzinfo (0.3.37)
|
||||
uglifier (2.1.2)
|
||||
execjs (>= 0.3.0)
|
||||
multi_json (~> 1.0, >= 1.0.2)
|
||||
unicorn (4.6.3)
|
||||
kgio (~> 2.6)
|
||||
rack
|
||||
raindrops (~> 0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
actionpack-action_caching
|
||||
active_attr
|
||||
active_model_serializers!
|
||||
activerecord-postgres-hstore
|
||||
airbrake (= 3.1.2)
|
||||
annotate!
|
||||
barber
|
||||
better_errors
|
||||
binding_of_caller
|
||||
certified
|
||||
clockwork
|
||||
diffy
|
||||
discourse_emoji!
|
||||
discourse_plugin!
|
||||
em-redis
|
||||
email_reply_parser!
|
||||
ember-rails
|
||||
ember-source (= 1.0.0.rc6.2)
|
||||
eventmachine
|
||||
fabrication
|
||||
fakeweb (~> 1.3.0)
|
||||
fast_blank
|
||||
fast_xor!
|
||||
fast_xs
|
||||
fastimage
|
||||
fog
|
||||
guard-rspec
|
||||
guard-spork
|
||||
handlebars-source (= 1.0.12)
|
||||
highline
|
||||
hiredis
|
||||
image_optim
|
||||
image_sorcery
|
||||
librarian (>= 0.0.25)
|
||||
listen
|
||||
lru_redux
|
||||
message_bus!
|
||||
minitest
|
||||
mocha
|
||||
multi_json
|
||||
mustache
|
||||
nokogiri
|
||||
oj
|
||||
omniauth
|
||||
omniauth-browserid!
|
||||
omniauth-cas
|
||||
omniauth-facebook
|
||||
omniauth-github
|
||||
omniauth-openid
|
||||
omniauth-twitter
|
||||
openid-redis-store
|
||||
pg
|
||||
protected_attributes
|
||||
pry-nav
|
||||
pry-rails
|
||||
qunit-rails
|
||||
rack-cache
|
||||
rack-cors
|
||||
rack-mini-profiler (= 0.1.27)
|
||||
rails (= 4.0.0)
|
||||
rails-observers
|
||||
rails_multisite!
|
||||
rake
|
||||
rb-fsevent
|
||||
rb-inotify (~> 0.9)
|
||||
redcarpet
|
||||
redis
|
||||
redis-rack-cache!
|
||||
redis-rails!
|
||||
rest-client
|
||||
rinku
|
||||
rspec-given
|
||||
rspec-rails
|
||||
sanitize
|
||||
sass
|
||||
sass-rails
|
||||
seed-fu!
|
||||
shoulda
|
||||
sidekiq
|
||||
sidekiq-failures
|
||||
simple_handlebars_rails!
|
||||
simplecov
|
||||
sinatra
|
||||
slim
|
||||
terminal-notifier-guard
|
||||
therubyracer
|
||||
thin
|
||||
timecop
|
||||
turbo-sprockets-rails3
|
||||
uglifier
|
||||
unicorn
|
||||
vestal_versions!
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
Handles routes related to viewing flags.
|
||||
|
||||
@class AdminFlagsRoute
|
||||
@extends Discourse.Route
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
|
||||
Discourse.AdminFlagsRoute = Discourse.Route.extend({
|
||||
redirect: function() {
|
||||
this.transitionTo('adminFlags.active');
|
||||
}
|
||||
});
|
|
@ -9,6 +9,9 @@
|
|||
Discourse.AdminUsersListRoute = Discourse.Route.extend({
|
||||
renderTemplate: function() {
|
||||
this.render('admin/templates/users_list', {into: 'admin/templates/admin'});
|
||||
},
|
||||
redirect: function() {
|
||||
this.transitionTo('adminUsersList.active');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -108,4 +111,4 @@ Discourse.AdminUsersListBannedRoute = Discourse.Route.extend({
|
|||
setupController: function() {
|
||||
return this.controllerFor('adminUsersList').show('banned');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
<li>{{#linkTo 'admin.site_settings'}}{{i18n admin.site_settings.title}}{{/linkTo}}</li>
|
||||
<li>{{#linkTo 'adminSiteContents'}}{{i18n admin.site_content.title}}{{/linkTo}}</li>
|
||||
{{/if}}
|
||||
<li>{{#linkTo 'adminUsersList.active'}}{{i18n admin.users.title}}{{/linkTo}}</li>
|
||||
<li>{{#linkTo 'adminUsersList'}}{{i18n admin.users.title}}{{/linkTo}}</li>
|
||||
<li>{{#linkTo 'admin.groups'}}{{i18n admin.groups.title}}{{/linkTo}}</li>
|
||||
<li>{{#linkTo 'adminEmail'}}{{i18n admin.email.title}}{{/linkTo}}</li>
|
||||
<li>{{#linkTo 'adminFlags.active'}}{{i18n admin.flags.title}}{{/linkTo}}</li>
|
||||
<li>{{#linkTo 'adminFlags'}}{{i18n admin.flags.title}}{{/linkTo}}</li>
|
||||
{{#if currentUser.admin}}
|
||||
<li>{{#linkTo 'admin.customize'}}{{i18n admin.customize.title}}{{/linkTo}}</li>
|
||||
<li>{{#linkTo 'admin.api'}}{{i18n admin.api.title}}{{/linkTo}}</li>
|
||||
|
|
|
@ -8,12 +8,9 @@
|
|||
Discourse.Lightbox = {
|
||||
apply: function($elem) {
|
||||
var _this = this;
|
||||
$('a.lightbox', $elem).each(function(i, e) {
|
||||
$LAB.script("/javascripts/jquery.magnific-popup-min.js").wait(function() {
|
||||
$(e).magnificPopup({
|
||||
type: 'image',
|
||||
closeOnContentClick: true
|
||||
});
|
||||
$LAB.script("/javascripts/jquery.magnific-popup-min.js").wait(function() {
|
||||
$('a.lightbox', $elem).each(function(i, e) {
|
||||
$(e).magnificPopup({ type: 'image', closeOnContentClick: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -174,9 +174,13 @@ Discourse.Utilities = {
|
|||
return false;
|
||||
}
|
||||
var upload = files[0];
|
||||
// ensures that new users can upload image
|
||||
if (Discourse.User.current('trust_level') === 0 && Discourse.SiteSettings.newuser_max_images === 0) {
|
||||
bootbox.alert(I18n.t('post.errors.upload_not_allowed_for_new_user'));
|
||||
// ensures that new users can upload image/attachment
|
||||
if (Discourse.Utilities.isUploadForbidden(upload.name)) {
|
||||
if (Discourse.Utilities.isAnImage(upload.name)) {
|
||||
bootbox.alert(I18n.t('post.errors.image_upload_not_allowed_for_new_user'));
|
||||
} else {
|
||||
bootbox.alert(I18n.t('post.errors.attachment_upload_not_allowed_for_new_user'));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// if the image was pasted, sets its name to a default one
|
||||
|
@ -242,6 +246,17 @@ Discourse.Utilities = {
|
|||
**/
|
||||
maxUploadSizeInKB: function(path) {
|
||||
return Discourse.Utilities.isAnImage(path) ? Discourse.SiteSettings.max_image_size_kb : Discourse.SiteSettings.max_attachment_size_kb;
|
||||
},
|
||||
|
||||
/**
|
||||
Test whether an upload is forbidden or not
|
||||
|
||||
@method isUploadForbidden
|
||||
@param {String} path The path
|
||||
**/
|
||||
isUploadForbidden: function(path) {
|
||||
if (Discourse.User.current('trust_level') > 0) { return false; }
|
||||
return Discourse.Utilities.isAnImage(path) ? Discourse.SiteSettings.newuser_max_images === 0 : Discourse.SiteSettings.newuser_max_attachments === 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
Discourse.ComposerController = Discourse.Controller.extend({
|
||||
needs: ['modal', 'topic'],
|
||||
|
||||
replyAsNewTopicDraft: Em.computed.equal('model.draftKey', Discourse.Composer.REPLY_AS_NEW_TOPIC_KEY),
|
||||
|
||||
togglePreview: function() {
|
||||
this.get('model').togglePreview();
|
||||
},
|
||||
|
@ -32,13 +34,8 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
|||
}.property(),
|
||||
|
||||
save: function(force) {
|
||||
var composer,
|
||||
_this = this,
|
||||
topic,
|
||||
message,
|
||||
buttons;
|
||||
|
||||
composer = this.get('model');
|
||||
var composer = this.get('model'),
|
||||
composerController = this;
|
||||
|
||||
if( composer.get('cantSubmitPost') ) {
|
||||
this.set('view.showTitleTip', Date.now());
|
||||
|
@ -52,12 +49,12 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
|||
// for now handle a very narrow use case
|
||||
// if we are replying to a topic AND not on the topic pop the window up
|
||||
if(!force && composer.get('replyingToTopic')) {
|
||||
topic = this.get('topic');
|
||||
var topic = this.get('topic');
|
||||
if (!topic || topic.get('id') !== composer.get('topic.id'))
|
||||
{
|
||||
message = I18n.t("composer.posting_not_on_topic", {title: this.get('model.topic.title')});
|
||||
var message = I18n.t("composer.posting_not_on_topic", {title: this.get('model.topic.title')});
|
||||
|
||||
buttons = [{
|
||||
var buttons = [{
|
||||
"label": I18n.t("composer.cancel"),
|
||||
"class": "cancel",
|
||||
"link": true
|
||||
|
@ -70,7 +67,7 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
|||
"callback": function(){
|
||||
composer.set('topic', topic);
|
||||
composer.set('post', null);
|
||||
_this.save(true);
|
||||
composerController.save(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -79,7 +76,7 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
|||
"label": I18n.t("composer.reply_original") + "<br/><div class='topic-title'>" + this.get('model.topic.title') + "</div>",
|
||||
"class": "btn-primary btn-reply-on-original",
|
||||
"callback": function(){
|
||||
_this.save(true);
|
||||
composerController.save(true);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -91,8 +88,15 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
|||
return composer.save({
|
||||
imageSizes: this.get('view').imageSizes()
|
||||
}).then(function(opts) {
|
||||
|
||||
// If we replied as a new topic successfully, remove the draft.
|
||||
if (composerController.get('replyAsNewTopicDraft')) {
|
||||
composerController.destroyDraft();
|
||||
}
|
||||
|
||||
|
||||
opts = opts || {};
|
||||
_this.close();
|
||||
composerController.close();
|
||||
|
||||
var currentUser = Discourse.User.current();
|
||||
if (composer.get('creatingTopic')) {
|
||||
|
@ -101,6 +105,7 @@ Discourse.ComposerController = Discourse.Controller.extend({
|
|||
currentUser.set('reply_count', currentUser.get('reply_count') + 1);
|
||||
}
|
||||
Discourse.URL.routeTo(opts.post.get('url'));
|
||||
|
||||
}, function(error) {
|
||||
composer.set('disableDrafts', false);
|
||||
bootbox.alert(error);
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
**/
|
||||
Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
|
||||
|
||||
onShow: function() {
|
||||
this.set('selected', null);
|
||||
},
|
||||
|
||||
changePostActionType: function(action) {
|
||||
this.set('selected', action);
|
||||
},
|
||||
|
|
|
@ -332,7 +332,6 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
|
|||
},
|
||||
|
||||
replyAsNewTopic: function(post) {
|
||||
// TODO shut down topic draft cleanly if it exists ...
|
||||
var composerController = this.get('controllers.composer');
|
||||
var promise = composerController.open({
|
||||
action: Discourse.Composer.CREATE_TOPIC,
|
||||
|
@ -343,9 +342,9 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
|
|||
|
||||
promise.then(function() {
|
||||
Discourse.Post.loadQuote(post.get('id')).then(function(q) {
|
||||
composerController.appendText("" + (I18n.t("post.continue_discussion", {
|
||||
composerController.appendText(I18n.t("post.continue_discussion", {
|
||||
postLink: postLink
|
||||
})) + "\n\n" + q);
|
||||
}) + "\n\n" + q);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -27,12 +27,12 @@ Discourse.Post = Discourse.Model.extend({
|
|||
deleted: Em.computed.or('deleted_at', 'deletedViaTopic'),
|
||||
|
||||
postDeletedBy: function() {
|
||||
if (this.get('firstPost')) { return this.get('topic.deleted_by') }
|
||||
if (this.get('firstPost')) { return this.get('topic.deleted_by'); }
|
||||
return this.get('deleted_by');
|
||||
}.property('firstPost', 'deleted_by', 'topic.deleted_by'),
|
||||
|
||||
postDeletedAt: function() {
|
||||
if (this.get('firstPost')) { return this.get('topic.deleted_at') }
|
||||
if (this.get('firstPost')) { return this.get('topic.deleted_at'); }
|
||||
return this.get('deleted_at');
|
||||
}.property('firstPost', 'deleted_at', 'topic.deleted_at'),
|
||||
|
||||
|
@ -199,13 +199,23 @@ Discourse.Post = Discourse.Model.extend({
|
|||
@method recover
|
||||
**/
|
||||
recover: function() {
|
||||
this.setProperties({
|
||||
var post = this;
|
||||
post.setProperties({
|
||||
deleted_at: null,
|
||||
deleted_by: null,
|
||||
can_delete: true
|
||||
user_deleted: false,
|
||||
can_delete: false
|
||||
});
|
||||
|
||||
return Discourse.ajax("/posts/" + (this.get('id')) + "/recover", { type: 'PUT', cache: false });
|
||||
return Discourse.ajax("/posts/" + (this.get('id')) + "/recover", { type: 'PUT', cache: false }).then(function(data){
|
||||
post.setProperties({
|
||||
cooked: data.cooked,
|
||||
raw: data.raw,
|
||||
user_deleted: false,
|
||||
can_delete: true,
|
||||
version: data.version
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -226,7 +236,10 @@ Discourse.Post = Discourse.Model.extend({
|
|||
this.setProperties({
|
||||
cooked: Discourse.Markdown.cook(I18n.t("post.deleted_by_author")),
|
||||
can_delete: false,
|
||||
version: this.get('version') + 1
|
||||
version: this.get('version') + 1,
|
||||
can_recover: true,
|
||||
can_edit: false,
|
||||
user_deleted: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ Discourse.Route = Em.Route.extend({
|
|||
$('.d-dropdown').hide();
|
||||
$('header ul.icons li').removeClass('active');
|
||||
$('[data-toggle="dropdown"]').parent().removeClass('open');
|
||||
// close the lightbox
|
||||
if ($.magnificPopup && $.magnificPopup.instance) { $.magnificPopup.instance.close(); }
|
||||
|
||||
Discourse.set('notifyCount',0);
|
||||
|
||||
|
|
|
@ -11,18 +11,15 @@ Discourse.FlagView = Discourse.ModalBodyView.extend({
|
|||
title: I18n.t('flagging.title'),
|
||||
|
||||
selectedChanged: function() {
|
||||
var nameKey = this.get('controller.selected.name_key');
|
||||
if (!nameKey) return;
|
||||
var flagView = this;
|
||||
Em.run.next(function() {
|
||||
$('#radio_' + nameKey).prop('checked', 'true');
|
||||
flagView.$("input[type='radio']").prop('checked', false);
|
||||
|
||||
var nameKey = flagView.get('controller.selected.name_key');
|
||||
if (!nameKey) return;
|
||||
|
||||
flagView.$('#radio_' + nameKey).prop('checked', 'true');
|
||||
});
|
||||
}.observes('controller.selected.name_key'),
|
||||
}.observes('controller.selected.name_key')
|
||||
|
||||
didInsertElement: function() {
|
||||
this._super();
|
||||
|
||||
// Would be nice if there were an EmberJs radio button to do this for us. Oh well, one should be coming
|
||||
// in an upcoming release.
|
||||
this.$("input[type='radio']").prop('checked', false);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -88,7 +88,7 @@ Discourse.PostMenuView = Discourse.View.extend({
|
|||
} else {
|
||||
|
||||
// The delete actions target the post iteself
|
||||
if (post.get('deleted_at')) {
|
||||
if (post.get('deleted_at') || post.get('user_deleted')) {
|
||||
if (!post.get('can_recover')) { return; }
|
||||
label = "post.controls.undelete";
|
||||
action = "recover";
|
||||
|
|
|
@ -12,10 +12,15 @@ Discourse.PostView = Discourse.View.extend({
|
|||
classNameBindings: ['postTypeClass',
|
||||
'selected',
|
||||
'post.hidden:hidden',
|
||||
'post.deleted',
|
||||
'addDeletedClass:deleted',
|
||||
'parentPost:replies-above'],
|
||||
postBinding: 'content',
|
||||
|
||||
addDeletedClass: function() {
|
||||
var post = this.get('post');
|
||||
return post.get('deleted') || post.get('user_deleted');
|
||||
}.property('post.deleted','post.user_deleted'),
|
||||
|
||||
postTypeClass: function() {
|
||||
return this.get('post.post_type') === Discourse.Site.instance().get('post_types.moderator_action') ? 'moderator' : 'regular';
|
||||
}.property('post.post_type'),
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
// Pagedown customizations
|
||||
//= require ./pagedown_custom.js
|
||||
|
||||
// The rest of the externals
|
||||
//= require_tree ./external
|
||||
|
||||
|
@ -9,6 +6,9 @@
|
|||
|
||||
//= require ./locales/date_locales.js
|
||||
|
||||
// Pagedown customizations
|
||||
//= require ./pagedown_custom.js
|
||||
|
||||
// Stuff we need to load first
|
||||
//= require_tree ./discourse/mixins
|
||||
//= require ./discourse/components/computed
|
||||
|
@ -16,6 +16,7 @@
|
|||
//= require ./discourse/components/debounce
|
||||
//= require ./discourse/models/model
|
||||
//= require ./discourse/models/user_action
|
||||
//= require ./discourse/models/composer
|
||||
//= require ./discourse/controllers/controller
|
||||
//= require ./discourse/controllers/object_controller
|
||||
//= require ./discourse/views/modal/modal_body_view
|
||||
|
|
|
@ -238,5 +238,6 @@
|
|||
.category {
|
||||
float: left;
|
||||
background-color: transparent;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,7 +77,6 @@
|
|||
color: $topic-list-th-color;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
text-shadow: 0 1px 0 $white;
|
||||
@include box-shadow(inset 0 1px 0 $white);
|
||||
&:first-of-type {
|
||||
@include border-radius-all(4px 0 0 0);
|
||||
|
|
|
@ -374,8 +374,8 @@
|
|||
}
|
||||
.user-title {
|
||||
font-size: 11px;
|
||||
background-color: none;
|
||||
margin-top: 2px;
|
||||
line-height: 15px;
|
||||
color: #555;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,12 +21,10 @@
|
|||
// --------------------------------------------------
|
||||
|
||||
.badge-category {
|
||||
@extend %badge;
|
||||
padding: 3px 8px;
|
||||
padding: 5px 8px;
|
||||
color: $white;
|
||||
font-size: 12px;
|
||||
text-shadow: 0 1px 0 rgba($black, 0.3);
|
||||
@include box-shadow(inset 0 1px 0 rgba($white, 0.22));
|
||||
font-weight: bold;
|
||||
&[href] {
|
||||
color: $white;
|
||||
}
|
||||
|
|
|
@ -94,17 +94,13 @@ class PostsController < ApplicationController
|
|||
@post = Post.where(topic_id: params[:topic_id], post_number: params[:post_number]).first
|
||||
guardian.ensure_can_see!(@post)
|
||||
@post.revert_to(params[:version].to_i) if params[:version].present?
|
||||
post_serializer = PostSerializer.new(@post, scope: guardian, root: false)
|
||||
post_serializer.add_raw = true
|
||||
render_json_dump(post_serializer)
|
||||
render_post_json(@post)
|
||||
end
|
||||
|
||||
def show
|
||||
@post = find_post_from_params
|
||||
@post.revert_to(params[:version].to_i) if params[:version].present?
|
||||
post_serializer = PostSerializer.new(@post, scope: guardian, root: false)
|
||||
post_serializer.add_raw = true
|
||||
render_json_dump(post_serializer)
|
||||
render_post_json(@post)
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
@ -120,10 +116,11 @@ class PostsController < ApplicationController
|
|||
def recover
|
||||
post = find_post_from_params
|
||||
guardian.ensure_can_recover_post!(post)
|
||||
post.recover!
|
||||
post.topic.update_statistics
|
||||
destroyer = PostDestroyer.new(current_user, post)
|
||||
destroyer.recover
|
||||
post.reload
|
||||
|
||||
render nothing: true
|
||||
render_post_json(post)
|
||||
end
|
||||
|
||||
def destroy_many
|
||||
|
@ -188,6 +185,12 @@ class PostsController < ApplicationController
|
|||
post
|
||||
end
|
||||
|
||||
def render_post_json(post)
|
||||
post_serializer = PostSerializer.new(post, scope: guardian, root: false)
|
||||
post_serializer.add_raw = true
|
||||
render_json_dump(post_serializer)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_params
|
||||
|
@ -200,9 +203,13 @@ class PostsController < ApplicationController
|
|||
:target_usernames,
|
||||
:reply_to_post_number,
|
||||
:image_sizes,
|
||||
:auto_close_days
|
||||
:auto_close_days,
|
||||
:auto_track
|
||||
]
|
||||
|
||||
# param munging for WordPress
|
||||
params[:auto_track] = !(params[:auto_track].to_s == "false") if params[:auto_track]
|
||||
|
||||
if api_key_valid?
|
||||
# php seems to be sending this incorrectly, don't fight with it
|
||||
params[:skip_validations] = params[:skip_validations].to_s == "true"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
require_dependency 'jobs'
|
||||
require_dependency 'pretty_text'
|
||||
require_dependency 'local_store'
|
||||
require_dependency 's3_store'
|
||||
require_dependency 'rate_limiter'
|
||||
require_dependency 'post_revisor'
|
||||
require_dependency 'enum'
|
||||
|
@ -89,7 +91,7 @@ class Post < ActiveRecord::Base
|
|||
@post_analyzer = PostAnalyzer.new(raw, topic_id)
|
||||
end
|
||||
|
||||
%w{raw_mentions linked_hosts image_count link_count raw_links}.each do |attr|
|
||||
%w{raw_mentions linked_hosts image_count attachment_count link_count raw_links}.each do |attr|
|
||||
define_method(attr) do
|
||||
PostAnalyzer.new(raw, topic_id).send(attr)
|
||||
end
|
||||
|
@ -263,12 +265,6 @@ class Post < ActiveRecord::Base
|
|||
PostCreator.before_create_tasks(self)
|
||||
end
|
||||
|
||||
# TODO: Move some of this into an asynchronous job?
|
||||
# TODO: Move into PostCreator
|
||||
after_create do
|
||||
PostCreator.after_create_tasks(self)
|
||||
end
|
||||
|
||||
# This calculates the geometric mean of the post timings and stores it along with
|
||||
# each post.
|
||||
def self.calculate_avg_time
|
||||
|
|
|
@ -39,6 +39,18 @@ class PostAnalyzer
|
|||
end.count
|
||||
end
|
||||
|
||||
# How many attachments are present in the post
|
||||
def attachment_count
|
||||
return 0 unless @raw.present?
|
||||
|
||||
if SiteSetting.enable_s3_uploads?
|
||||
cooked_document.css("a.attachment[href^=\"#{S3Store.base_url}\"]")
|
||||
else
|
||||
cooked_document.css("a.attachment[href^=\"#{LocalStore.directory}\"]") +
|
||||
cooked_document.css("a.attachment[href^=\"#{LocalStore.base_url}\"]")
|
||||
end.count
|
||||
end
|
||||
|
||||
def raw_mentions
|
||||
return [] if @raw.blank?
|
||||
|
||||
|
|
|
@ -212,6 +212,7 @@ class SiteSetting < ActiveRecord::Base
|
|||
|
||||
setting(:newuser_max_links, 2)
|
||||
client_setting(:newuser_max_images, 0)
|
||||
client_setting(:newuser_max_attachments, 0)
|
||||
|
||||
setting(:newuser_spam_host_threshold, 3)
|
||||
|
||||
|
|
|
@ -51,7 +51,11 @@ class Topic < ActiveRecord::Base
|
|||
self.title = TextCleaner.clean_title(TextSentinel.title_sentinel(title).text) if errors[:title].empty?
|
||||
end
|
||||
|
||||
serialize :meta_data, ActiveRecord::Coders::Hstore
|
||||
if rails4?
|
||||
store_accessor :meta_data
|
||||
else
|
||||
serialize :meta_data, ActiveRecord::Coders::Hstore
|
||||
end
|
||||
|
||||
belongs_to :category
|
||||
has_many :posts
|
||||
|
@ -130,7 +134,6 @@ class Topic < ActiveRecord::Base
|
|||
|
||||
after_create do
|
||||
changed_to_category(category)
|
||||
notifier.created_topic! user_id
|
||||
if archetype == Archetype.private_message
|
||||
DraftSequence.next!(user, Draft::NEW_PRIVATE_MESSAGE)
|
||||
else
|
||||
|
|
|
@ -14,7 +14,7 @@ class TopicNotifier
|
|||
|
||||
end
|
||||
|
||||
def created_topic!(user_id)
|
||||
def watch_topic!(user_id)
|
||||
change_level user_id, :watching, :created_topic
|
||||
end
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ class Upload < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def self.is_relative?(url)
|
||||
url.start_with?("/uploads/")
|
||||
url.start_with?(LocalStore.directory)
|
||||
end
|
||||
|
||||
def self.is_local?(url)
|
||||
|
@ -99,10 +99,12 @@ class Upload < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def self.is_on_s3?(url)
|
||||
SiteSetting.enable_s3_uploads? && (url.start_with?(S3Store.base_url) || url.start_with?(S3Store.base_url_old))
|
||||
SiteSetting.enable_s3_uploads? && url.start_with?(S3Store.base_url)
|
||||
end
|
||||
|
||||
def self.get_from_url(url)
|
||||
# we store relative urls, so we need to remove any host/cdn
|
||||
url = url.gsub(/^#{LocalStore.asset_host}/i, "") if LocalStore.asset_host.present?
|
||||
Upload.where(url: url).first if has_been_uploaded?(url)
|
||||
end
|
||||
|
||||
|
|
|
@ -208,6 +208,7 @@ ORDER BY p.created_at desc
|
|||
end
|
||||
|
||||
def self.synchronize_target_topic_ids(post_ids = nil)
|
||||
|
||||
builder = SqlBuilder.new("UPDATE user_actions
|
||||
SET target_topic_id = (select topic_id from posts where posts.id = target_post_id)
|
||||
/*where*/")
|
||||
|
|
|
@ -41,7 +41,8 @@ class PostSerializer < BasicPostSerializer
|
|||
:hidden_reason_id,
|
||||
:trust_level,
|
||||
:deleted_at,
|
||||
:deleted_by
|
||||
:deleted_by,
|
||||
:user_deleted
|
||||
|
||||
|
||||
def moderator?
|
||||
|
|
|
@ -112,7 +112,7 @@
|
|||
</p>
|
||||
<div class="more">
|
||||
<p>
|
||||
Rather than posting “+1” or “Agreed,” use the Like button. Rather than taking an existing topic in a radically different direction, use Reply as a New Topic.
|
||||
Rather than posting “+1” or “Agreed”, use the Like button. Rather than taking an existing topic in a radically different direction, use Reply as a New Topic.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -132,4 +132,4 @@
|
|||
<div class="more">
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
|
||||
<div id="7"></div>
|
||||
<h2><a href="#7">7. Content Posted on Other Websites</a></h2>
|
||||
<p>We have not reviewed, and cannot review, all of the material, including computer software, made available through the websites and webpages to which <%= SiteSetting.company_domain %> links, and that link to <%= SiteSetting.company_domain %>. <%= SiteSetting.company_short_name %> does not have any control over those non-<%= SiteSetting.company_domain %> websites and webpages, and is not responsible for their contents or their use. By linking to a non-<%= SiteSetting.company_domain %> website or webpage, <%= SiteSetting.company_short_name %> does not represent or imply that it endorses such website or webpage. You are responsible for taking precautions as necessary to protect yourself and your computer systems from viruses, worms, Trojan horses, and other harmful or destructive content. <%= SiteSetting.company_short_name %> disclaims any responsibility for any harm resulting from your use of non-WordPress websites and webpages.
|
||||
<p>We have not reviewed, and cannot review, all of the material, including computer software, made available through the websites and webpages to which <%= SiteSetting.company_domain %> links, and that link to <%= SiteSetting.company_domain %>. <%= SiteSetting.company_short_name %> does not have any control over those non-<%= SiteSetting.company_domain %> websites and webpages, and is not responsible for their contents or their use. By linking to a non-<%= SiteSetting.company_domain %> website or webpage, <%= SiteSetting.company_short_name %> does not represent or imply that it endorses such website or webpage. You are responsible for taking precautions as necessary to protect yourself and your computer systems from viruses, worms, Trojan horses, and other harmful or destructive content. <%= SiteSetting.company_short_name %> disclaims any responsibility for any harm resulting from your use of non-<%= SiteSetting.company_domain %> websites and webpages.
|
||||
</p>
|
||||
|
||||
<div id="8"></div>
|
||||
|
|
|
@ -93,7 +93,7 @@ module Discourse
|
|||
# dumping rack lock cause the message bus does not work with it (throw :async, it catches Exception)
|
||||
# see: https://github.com/sporkrb/spork/issues/66
|
||||
# rake assets:precompile also fails
|
||||
config.threadsafe! unless $PROGRAM_NAME =~ /spork|rake/
|
||||
config.threadsafe! unless rails4? || $PROGRAM_NAME =~ /spork|rake/
|
||||
|
||||
# route all exceptions via our router
|
||||
config.exceptions_app = self.routes
|
||||
|
|
|
@ -34,5 +34,6 @@ module Clockwork
|
|||
every(1.day, 'version_check')
|
||||
every(1.minute, 'clockwork_heartbeat')
|
||||
every(1.minute, 'poll_mailbox')
|
||||
every(2.hours, 'destroy_old_deletion_stubs')
|
||||
|
||||
end
|
||||
|
|
|
@ -7,7 +7,8 @@ Discourse::Application.configure do
|
|||
config.cache_classes = false
|
||||
|
||||
# Log error messages when you accidentally call methods on nil.
|
||||
config.whiny_nils = true
|
||||
config.whiny_nils = true unless rails4?
|
||||
config.eager_load = false if rails4?
|
||||
|
||||
# Show full error reports and disable caching
|
||||
config.consider_all_requests_local = true
|
||||
|
|
|
@ -757,7 +757,8 @@ en:
|
|||
upload_too_large: "Sorry, the file you are trying to upload is too big (maximum size is {{max_size_kb}}kb), please resize it and try again."
|
||||
too_many_uploads: "Sorry, you can only upload one file at a time."
|
||||
upload_not_authorized: "Sorry, the file you are trying to upload is not authorized (authorized extension: {{authorized_extensions}})."
|
||||
upload_not_allowed_for_new_user: "Sorry, new users can not upload images."
|
||||
image_upload_not_allowed_for_new_user: "Sorry, new users can not upload images."
|
||||
attachment_upload_not_allowed_for_new_user: "Sorry, new users can not upload attachments."
|
||||
|
||||
abandon: "Are you sure you want to abandon your post?"
|
||||
|
||||
|
|
|
@ -737,7 +737,8 @@ fr:
|
|||
upload_too_large: "Désolé, le fichier que vous êtes en train d'envoyer est trop grand (maximum {{max_size_kb}}Kb). Merci de le redimensionner et de réessayer."
|
||||
too_many_uploads: "Désolé, vous ne pouvez envoyer qu'un seul fichier à la fois."
|
||||
upload_not_authorized: "Désole, le fichier que vous êtes en train d'uploader n'est pas autorisé (extensions autorisées : {{authorized_extensions}})."
|
||||
upload_not_allowed_for_new_user: "Désolé, les nouveaux utilisateurs ne peuvent pas uploader d'images."
|
||||
image_upload_not_allowed_for_new_user: "Désolé, les nouveaux utilisateurs ne peuvent pas uploader d'image."
|
||||
attachment_upload_not_allowed_for_new_user: "Désolé, les nouveaux utilisateurs ne peuvent pas uploader de fichier."
|
||||
|
||||
abandon: "Voulez-vous vraiment abandonner ce message ?"
|
||||
|
||||
|
|
|
@ -13,13 +13,13 @@ ru:
|
|||
number:
|
||||
human:
|
||||
storage_units:
|
||||
format: ! '%n %u'
|
||||
format: 0
|
||||
units:
|
||||
byte:
|
||||
few: байта
|
||||
many: байт
|
||||
one: байт
|
||||
other: байта
|
||||
one: Байт
|
||||
other: Байт
|
||||
few: Байта
|
||||
many: Байт
|
||||
gb: ГБ
|
||||
kb: КБ
|
||||
mb: МБ
|
||||
|
@ -199,6 +199,7 @@ ru:
|
|||
"12": Отправленные
|
||||
"13": Входящие
|
||||
user:
|
||||
said: '{{username}} писал(а):'
|
||||
profile: Профайл
|
||||
mute: Отключить
|
||||
edit: Настройки
|
||||
|
@ -455,8 +456,8 @@ ru:
|
|||
quote_text: Цитата
|
||||
code_title: Фрагмент кода
|
||||
code_text: вводите код здесь
|
||||
upload_title: Изображение
|
||||
upload_description: введите здесь описание изображения
|
||||
upload_title: Загрузить
|
||||
upload_description: введите здесь описание загружаемого объекта
|
||||
olist_title: Нумерованный список
|
||||
ulist_title: Маркированный список
|
||||
list_item: Элемент списка
|
||||
|
@ -486,16 +487,16 @@ ru:
|
|||
moved_post: "<i title='перенесенное сообщение' class='icon icon-arrow-right'></i> {{username}} перенес сообщение в {{link}}"
|
||||
total_flagged: всего сообщений с жалобами
|
||||
upload_selector:
|
||||
title: Вставка изображения
|
||||
title: Загрузить файл
|
||||
from_my_computer: С устройства
|
||||
from_the_web: Из интернета
|
||||
add_image: Добавить изображение
|
||||
remote_title: изображение из интернета
|
||||
remote_tip: введите адрес изображения в формате http://example.com/image.jpg
|
||||
local_title: локальное изображение
|
||||
local_tip: выбрать изображение с устройства.
|
||||
add_image: Добавить файл
|
||||
remote_title: удаленный файл
|
||||
remote_tip: введите адрес файла в формате http://example.com/image.jpg
|
||||
local_title: локальный файл
|
||||
local_tip: кликните для выбора файла на вашем устройстве.
|
||||
upload: Загрузить
|
||||
upload_file: Загрузка изображения
|
||||
upload_file: Загрузка
|
||||
search:
|
||||
title: поиск по темам, сообщениям, пользователям или категориям
|
||||
placeholder: условия поиска...
|
||||
|
@ -887,6 +888,7 @@ ru:
|
|||
few: Вы уверены, что хотите удалить сообщения?
|
||||
many: Вы уверены, что хотите удалить сообщения?
|
||||
category:
|
||||
can: может…
|
||||
none: (без категории)
|
||||
edit: изменить
|
||||
edit_long: Изменить категорию
|
||||
|
@ -915,11 +917,10 @@ ru:
|
|||
change_in_category_topic: Изменить описание
|
||||
hotness: Популярность
|
||||
already_used: Цвет уже используется другой категорией
|
||||
is_secure: Обезопасить категорию?
|
||||
add_group: Добавить группу
|
||||
security: Безопасность
|
||||
allowed_groups: 'Доступные группы:'
|
||||
auto_close_label: 'Закрыть тему через:'
|
||||
edit_permissions: Изменить права доступа
|
||||
add_permission: Добавить права
|
||||
flagging:
|
||||
title: Выберите действие над сообщением
|
||||
action: Пожаловаться
|
||||
|
@ -1005,6 +1006,10 @@ ru:
|
|||
many: '{{categoryName}} ({{count}})'
|
||||
help: 'последние темы в категории {{categoryName}}'
|
||||
browser_update: 'К сожалению, <a href="http://www.discourse.org/faq/#browser">ваш браузер слишком устарел</a> для комфортного просмотра нашего форума. Пожалуйста, <a href="http://browsehappy.com">обновите ваш браузер</a>.'
|
||||
permission_types:
|
||||
full: Создавать / Отвечать / Просматривать
|
||||
create_post: Отвечать / Просматривать
|
||||
readonly: Просматривать
|
||||
admin_js:
|
||||
type_to_filter: Введите текст для фильтрации...
|
||||
admin:
|
||||
|
|
|
@ -39,6 +39,10 @@ en:
|
|||
zero: "Sorry, new users can't put images in posts."
|
||||
one: "Sorry, new users can only put one image in a post."
|
||||
other: "Sorry, new users can only put %{count} images in a post."
|
||||
too_many_attachments:
|
||||
zero: "Sorry, new users can't put attachments in posts."
|
||||
one: "Sorry, new users can only put one attachment in a post."
|
||||
other: "Sorry, new users can only put %{count} attachments in a post."
|
||||
too_many_links:
|
||||
zero: "Sorry, new users can't put links in posts."
|
||||
one: "Sorry, new users can only put one link in a post."
|
||||
|
@ -582,7 +586,7 @@ en:
|
|||
suggested_topics: "The number of suggested topics shown at the bottom of a topic"
|
||||
|
||||
enable_s3_uploads: "Place uploads on Amazon S3"
|
||||
s3_upload_bucket: "The Amazon S3 bucket name that files will be uploaded into"
|
||||
s3_upload_bucket: "The Amazon S3 bucket name that files will be uploaded into. WARNING: must be lowercase (cf. http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html)"
|
||||
s3_access_key_id: "The Amazon S3 access key id that will be used to upload images"
|
||||
s3_secret_access_key: "The Amazon S3 secret access key that will be used to upload images"
|
||||
s3_region: "The Amazon S3 region name that will be used to upload images"
|
||||
|
@ -606,6 +610,7 @@ en:
|
|||
|
||||
newuser_max_links: "How many links a new user can add to a post"
|
||||
newuser_max_images: "How many images a new user can add to a post"
|
||||
newuser_max_attachments: "How many attachments a new user can add to a post"
|
||||
newuser_max_mentions_per_post: "Maximum number of @name notifications a new user can use in a post"
|
||||
max_mentions_per_post: "Maximum number of @name notifications you can use in a post"
|
||||
|
||||
|
|
|
@ -41,6 +41,10 @@ fr:
|
|||
zero: "Désolé, les visiteurs ne peuvent pas ajouter d'image."
|
||||
one: "Désolé, les visiteurs ne peuvent ajouter qu'une seule image."
|
||||
other: "Désolé, les visiteurs ne peuvent ajouter que %{count} images."
|
||||
too_many_attachments:
|
||||
zero: "Désolé, les visiteurs ne peuvent pas ajouter de fichier."
|
||||
one: "Désolé, les visiteurs ne peuvent ajouter qu'un seul fichier."
|
||||
other: "Désolé, les visiteurs ne peuvent ajouter que %{count} fichiers."
|
||||
too_many_links:
|
||||
zero: "Désolé, les visiteurs ne peuvent pas insérer de liens."
|
||||
one: "Désolé, les visiteurs ne peuvent insérer qu'un seul lien."
|
||||
|
@ -534,6 +538,7 @@ fr:
|
|||
|
||||
newuser_max_links: "Nombre maximum de liens qu'un visiteur peut ajouter à un message"
|
||||
newuser_max_images: "Nombre maximum d'images qu'un visiteur peut ajouter à un message"
|
||||
newuser_max_attachments: "Nombre maximum de fichiers qu'un visiteur peut ajouter à un message"
|
||||
newuser_max_mentions_per_post: "Nombre maximum de référence à un @utilisateur qu'un visiteur peut ajouter à un message"
|
||||
max_mentions_per_post: "Le nombre maximal de @mentions que vous pouvez ajouter à un message"
|
||||
|
||||
|
@ -551,7 +556,7 @@ fr:
|
|||
suggested_topics: "Nombre de discussions suggerées en dessous d'une discussion"
|
||||
|
||||
enable_s3_uploads: "S'il faut ou non uploader sur s3"
|
||||
s3_upload_bucket: "Le bucket dans lequel uploader sur s3"
|
||||
s3_upload_bucket: "Le nom du bucket s3 où seront uploader les fichiers. ATTENTION : le nom doit être en minuscule (cf. http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html)"
|
||||
s3_secret_access_key: "La clé secrète Amazon S3 qui va être utilisée pour uploader des images"
|
||||
s3_region: "Le nom de la région Amazon S3 qui va être utilisée pour uploader des images"
|
||||
|
||||
|
|
|
@ -498,6 +498,9 @@ ru:
|
|||
tos_miscellaneous:
|
||||
title: 'Условия предоставления услуг: Miscellaneous'
|
||||
description: Текст раздела Miscellaneous «Условий предоставления услуг»
|
||||
login_required:
|
||||
title: 'Требуется вход: Начальная страница'
|
||||
description: Текст, показываемый неавторизованным пользователям, когда требуется вход на сайт.
|
||||
site_settings:
|
||||
default_locale: Язык по умолчанию для данного экземпляра Discourse (ISO 639-1 Code)
|
||||
min_post_length: Минимальная длина сообщения в символах
|
||||
|
@ -519,7 +522,7 @@ ru:
|
|||
company_short_name: Короткое название компании, которой принадлежит сайт, используется в правовой документации как /tos
|
||||
company_domain: Имя домена, принадлежащего компании, заведующей сайтом, используется в правовой документации как /tos
|
||||
api_key: Секретный API ключ, используемый для создания и обновления тем. Зайдите в секцию /admin/api , чтобы его задать
|
||||
queue_jobs: ВНИМАНИЕ! ТОЛЬКО ДЛЯ РАЗРАБОТЧИКОВ! Обрабатывать задачи асинхронно в очереди sidekiq, если выключено, задачи обрабатываются синхронно без использования очереди
|
||||
queue_jobs: ТОЛЬКО ДЛЯ РАЗРАБОТЧИКОВ! ВНИМАНИЕ! По умолчанию задачи обрабатываются асинхронно в очереди sidekiq. Если настройка выключена, ваш сайт может не работать.
|
||||
crawl_images: Разрешить извлечение изображений из сторонних источников, ширина и высота
|
||||
ninja_edit_window: Количество секунд после размещения сообщения, в течение которых внесение правок в сообщение не повлечет его изменение
|
||||
max_image_width: Максимальная ширина изображений, добавляемых в сообщение
|
||||
|
@ -643,7 +646,8 @@ ru:
|
|||
min_title_similar_length: Минимальная длина названия темы, при которой тема будет проверена на наличие похожих
|
||||
min_body_similar_length: Минимальная длина тела сообщения, при которой оно будет проверено на наличие похожих тем
|
||||
category_colors: Разделенный чертой (|) список дозволенных hexadecimal цветов для категорий
|
||||
max_image_size_kb: Максимальный размер файлов для загрузки пользователем в кб – убедитесь, что вы настроили лимит также в nginx (client_max_body_size) / apache или proxy.
|
||||
max_image_size_kb: Максимальный размер изображений для загрузки пользователем в КБ – убедитесь, что вы так же настроили лимит в nginx (client_max_body_size) / apache или прокси.
|
||||
max_attachment_size_kb: Максимальный размер файлов для загрузки пользователем в КБ – убедитесь, что вы так же настроили лимит в nginx (client_max_body_size) / apache или прокси.
|
||||
authorized_extensions: Список расширений файлов, разрешенных к загрузке, разделенный вертикальной чертой (|)
|
||||
max_similar_results: Количество похожих тем, показываемых пользователю во время создания новой темы
|
||||
title_prettify: Предотвращать распространенные опечатки и ошибки, включая КАПС, первый строчный символ, множественные ! и ?, лишние . в конце предложения и т.д.
|
||||
|
@ -1067,6 +1071,7 @@ ru:
|
|||
miscellaneous: 'This Agreement constitutes the entire agreement between %{company_short_name} and you concerning the subject matter hereof, and they may only be modified by a written amendment signed by an authorized executive of %{company_short_name}, or by the posting by %{company_short_name} of a revised version. Except to the extent applicable law, if any, provides otherwise, this Agreement, any access to or use of the Website will be governed by the laws of the state of California, U.S.A., excluding its conflict of law provisions, and the proper venue for any disputes arising out of or relating to any of the same will be the state and federal courts located in San Francisco County, California. Except for claims for injunctive or equitable relief or claims regarding intellectual property rights (which may be brought in any competent court without the posting of a bond), any dispute arising under this Agreement shall be finally settled in accordance with the Comprehensive Arbitration Rules of the Judicial Arbitration and Mediation Service, Inc. (“JAMS”) by three arbitrators appointed in accordance with such Rules. The arbitration shall take place in San Francisco, California, in the English language and the arbitral decision may be enforced in any court. The prevailing party in any action or proceeding to enforce this Agreement shall be entitled to costs and attorneys’ fees. If any part of this Agreement is held invalid or unenforceable, that part will be construed to reflect the parties’ original intent, and the remaining portions will remain in full force and effect. A waiver by either party of any term or condition of this Agreement or any breach thereof, in any one instance, will not waive such term or condition or any subsequent breach thereof. You may assign your rights under this Agreement to any party that consents to, and agrees to be bound by, its terms and conditions; %{company_short_name} may assign its rights under this Agreement without condition. This Agreement will be binding upon and will inure to the benefit of the parties, their successors and permitted assigns.'
|
||||
deleted: удалено
|
||||
upload:
|
||||
unauthorized: 'К сожалению, вы не можете загрузить файл данного типа (список разрешенных типов файлов: %{authorized_extensions}).'
|
||||
pasted_image_filename: Имя файла изображения
|
||||
image:
|
||||
fetch_failure: Извините, во время извлечения изображения произошла ошибка.
|
||||
|
|
|
@ -17,6 +17,7 @@ server {
|
|||
sendfile on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
client_max_body_size 2m;
|
||||
|
||||
location / {
|
||||
root /home/discourse/discourse/public;
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
# Alternative Install Options
|
||||
|
||||
Here lie some alternative installation options for Discourse. They're not the
|
||||
recommended way of doing things, hence they're a bit out of the way.
|
||||
|
||||
Oh, and dragons. Lots of dragons.
|
||||
|
||||
## Web Server Alternative: apache2
|
||||
|
||||
If you instead want to use apache2 to serve the static pages:
|
||||
|
||||
# Run these commands as your normal login (e.g. "michael")
|
||||
# If you don't have apache2 yet
|
||||
sudo apt-get install apache2
|
||||
|
||||
# Edit your site details in a new apache2 config file
|
||||
sudo vim /etc/apache2/sites-available/your-domain.com
|
||||
|
||||
# Put these info inside and change accordingly
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName your-domain.com
|
||||
ServerAlias www.your-domain.com
|
||||
|
||||
DocumentRoot /srv/www/apps/discourse/public
|
||||
|
||||
<Directory /srv/www/apps/discourse/public>
|
||||
AllowOverride all
|
||||
Options -MultiViews
|
||||
</Directory>
|
||||
|
||||
# Custom log file locations
|
||||
ErrorLog /srv/www/apps/discourse/log/error.log
|
||||
CustomLog /srv/www/apps/discourse/access.log combined
|
||||
</VirtualHost>
|
||||
|
||||
# Install the Passenger Phusion gem and run the install
|
||||
gem install passenger
|
||||
passenger-install-apache2-module
|
||||
|
||||
# Next, we "create" a new apache2 module, passenger
|
||||
sudo vim /etc/apache2/mods-available/passenger.load
|
||||
|
||||
# Inside paste (change the user accodingly)
|
||||
LoadModule passenger_module /home/YOUR-USER/.rvm/gems/ruby-2.0.0-p0/gems/passenger-4.0.2/libout/apache2/mod_passenger.so
|
||||
|
||||
# Now the passenger module configuration
|
||||
sudo vim /etc/apache2/mods-available/passenger.conf
|
||||
|
||||
# Inside, paste (change the user accodingly)
|
||||
PassengerRoot /home/YOUR-USER/.rvm/gems/ruby-2.0.0-p0/gems/passenger-4.0.2
|
||||
PassengerDefaultRuby /home/YOUR-USER/.rvm/wrappers/ruby-2.0.0-p0/ruby
|
||||
|
||||
# Now activate them all
|
||||
|
||||
sudo a2ensite your-domain.com
|
||||
sudo a2enmod passenger
|
||||
sudo service apache2 reload
|
||||
sudo service apache2 restart
|
||||
|
||||
If you get any errors starting or reloading apache, please check the paths above - Ruby 2.0 should be there if you are using RVM, but it could get tricky.
|
||||
|
||||
## RVM Alternative: Systemwide installation
|
||||
|
||||
Taken from http://rvm.io/, the commands below installs RVM and users in the 'rvm' group have access to modify state:
|
||||
|
||||
# Run these commands as your normal login (e.g. "michael") \curl -s -S -L https://get.rvm.io | sudo bash -s stable
|
||||
sudo adduser $USER rvm
|
||||
newgrp rvm
|
||||
. /etc/profile.d/rvm.sh
|
||||
rvm requirements
|
||||
|
||||
# Build and install ruby
|
||||
rvm install 2.0.0
|
||||
gem install bundler
|
||||
|
||||
When creating the `discourse` user, add him/her/it to the RVM group:
|
||||
|
||||
# Run these commands as your normal login (e.g. "michael")
|
||||
sudo adduser discourse rvm
|
||||
|
||||
RVM will be located in `/usr/local/rvm` directory instead of `/home/discourse/.rvm`, so update the crontab line respectively.
|
|
@ -2,10 +2,16 @@
|
|||
|
||||
## What kind of hardware do you have?
|
||||
|
||||
- We *strongly* recommend 2GB of memory minimum if you don't want to deal with swap partitions during the install.
|
||||
- We recommend at least a dual core CPU.
|
||||
- Recommended minimum configuration is:
|
||||
- 2GiB of RAM
|
||||
- 2GiB of swap
|
||||
- 2 processor cores
|
||||
- With 2GB of memory and dual cores, you can run two instances of the thin
|
||||
server (`NUM_WEBS=2`)
|
||||
|
||||
1 GB of memory and a single core CPU are the minimums for a steady state, running Discourse forum -- but it's simpler to just throw a bit more hardware at the problem if you can, particularly during the install.
|
||||
1 GiB of memory, 3GiB of swap and a single core CPU are the minimums for a
|
||||
steady state, running Discourse forum -- but it's simpler to just throw a bit
|
||||
more hardware at the problem if you can, particularly during the install.
|
||||
|
||||
## Install Ubuntu Server 12.04 LTS with the package groups:
|
||||
|
||||
|
@ -51,7 +57,13 @@ Install necessary packages:
|
|||
sudo apt-get update
|
||||
sudo apt-get install redis-server
|
||||
|
||||
## Web Server Option: nginx
|
||||
## Web Server: nginx
|
||||
|
||||
nginx is used for:
|
||||
|
||||
* reverse proxy (i.e. load balancer)
|
||||
* static asset serving (since you don't want to do that from ruby)
|
||||
* anonymous user cache
|
||||
|
||||
At Discourse, we recommend the latest version of nginx (we like the new and
|
||||
shiny). To install on Ubuntu:
|
||||
|
@ -73,83 +85,11 @@ shiny). To install on Ubuntu:
|
|||
# install nginx
|
||||
sudo apt-get update && sudo apt-get -y install nginx
|
||||
|
||||
## Web Server Option: apache2
|
||||
|
||||
If you instead want to use apache2 to serve the static pages:
|
||||
|
||||
# Run these commands as your normal login (e.g. "michael")
|
||||
# If you don't have apache2 yet
|
||||
sudo apt-get install apache2
|
||||
|
||||
# Edit your site details in a new apache2 config file
|
||||
sudo vim /etc/apache2/sites-available/your-domain.com
|
||||
|
||||
# Put these info inside and change accordingly
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName your-domain.com
|
||||
ServerAlias www.your-domain.com
|
||||
|
||||
DocumentRoot /srv/www/apps/discourse/public
|
||||
|
||||
<Directory /srv/www/apps/discourse/public>
|
||||
AllowOverride all
|
||||
Options -MultiViews
|
||||
</Directory>
|
||||
|
||||
# Custom log file locations
|
||||
ErrorLog /srv/www/apps/discourse/log/error.log
|
||||
CustomLog /srv/www/apps/discourse/access.log combined
|
||||
</VirtualHost>
|
||||
|
||||
# Install the Passenger Phusion gem and run the install
|
||||
gem install passenger
|
||||
passenger-install-apache2-module
|
||||
|
||||
# Next, we "create" a new apache2 module, passenger
|
||||
sudo vim /etc/apache2/mods-available/passenger.load
|
||||
|
||||
# Inside paste (change the user accodingly)
|
||||
LoadModule passenger_module /home/YOUR-USER/.rvm/gems/ruby-2.0.0-p0/gems/passenger-4.0.2/libout/apache2/mod_passenger.so
|
||||
|
||||
# Now the passenger module configuration
|
||||
sudo vim /etc/apache2/mods-available/passenger.conf
|
||||
|
||||
# Inside, paste (change the user accodingly)
|
||||
PassengerRoot /home/YOUR-USER/.rvm/gems/ruby-2.0.0-p0/gems/passenger-4.0.2
|
||||
PassengerDefaultRuby /home/YOUR-USER/.rvm/wrappers/ruby-2.0.0-p0/ruby
|
||||
|
||||
# Now activate them all
|
||||
|
||||
sudo a2ensite your-domain.com
|
||||
sudo a2enmod passenger
|
||||
sudo service apache2 reload
|
||||
sudo service apache2 restart
|
||||
|
||||
If you get any errors starting or reloading apache, please check the paths above - Ruby 2.0 should be there if you are using RVM, but it could get tricky.
|
||||
|
||||
## Install Ruby with RVM
|
||||
|
||||
### RVM Option: Systemwide installation
|
||||
### RVM : Single-user installation
|
||||
|
||||
Taken from http://rvm.io/, the commands below installs RVM and users in the 'rvm' group have access to modify state:
|
||||
|
||||
# Run these commands as your normal login (e.g. "michael")
|
||||
\curl -s -S -L https://get.rvm.io | sudo bash -s stable
|
||||
sudo adduser $USER rvm
|
||||
newgrp rvm
|
||||
. /etc/profile.d/rvm.sh
|
||||
rvm requirements
|
||||
|
||||
# Build and install ruby
|
||||
rvm install 2.0.0
|
||||
gem install bundler
|
||||
|
||||
### RVM Option: Single-user installation
|
||||
|
||||
Another sensible option (especially if only one Ruby app is on the machine) is
|
||||
to install RVM isolated to a user's environment. Further instructions are
|
||||
below.
|
||||
We recommend installing RVM isolated to a single user's environment.
|
||||
|
||||
## Discourse setup
|
||||
|
||||
|
@ -157,9 +97,6 @@ Create Discourse user:
|
|||
|
||||
# Run these commands as your normal login (e.g. "michael")
|
||||
sudo adduser --shell /bin/bash discourse
|
||||
# If this fails, it's because you're doing the RVM single-user install.
|
||||
# In that case, you could just not run it if errors make you squirrely
|
||||
sudo adduser discourse rvm
|
||||
|
||||
Give Postgres database rights to the `discourse` user:
|
||||
|
||||
|
@ -172,7 +109,7 @@ Change to the 'discourse' user:
|
|||
# Run this command as your normal login (e.g. "michael"), further commands should be run as 'discourse'
|
||||
sudo su - discourse
|
||||
|
||||
Install RVM if doing a single-user RVM installation:
|
||||
Install RVM
|
||||
|
||||
# As 'discourse'
|
||||
# Install RVM
|
||||
|
@ -304,7 +241,7 @@ Configure Bluepill:
|
|||
Start Discourse:
|
||||
|
||||
# Run these commands as the discourse user
|
||||
RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ROOT=~/discourse RAILS_ENV=production NUM_WEBS=4 bluepill --no-privileged -c ~/.bluepill load ~/discourse/config/discourse.pill
|
||||
RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ROOT=~/discourse RAILS_ENV=production NUM_WEBS=2 bluepill --no-privileged -c ~/.bluepill load ~/discourse/config/discourse.pill
|
||||
|
||||
Add the Bluepill startup to crontab.
|
||||
|
||||
|
@ -313,10 +250,7 @@ Add the Bluepill startup to crontab.
|
|||
|
||||
Add the following lines:
|
||||
|
||||
@reboot RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ROOT=~/discourse RAILS_ENV=production NUM_WEBS=4 /home/discourse/.rvm/bin/bootup_bluepill --no-privileged -c ~/.bluepill load ~/discourse/config/discourse.pill
|
||||
|
||||
|
||||
Note: in case of RVM system-wide installation RVM will be located in `/usr/local/rvm` directory instead of `/home/discourse/.rvm`, so update the line above respectively.
|
||||
@reboot RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ROOT=~/discourse RAILS_ENV=production NUM_WEBS=2 /home/discourse/.rvm/bin/bootup_bluepill --no-privileged -c ~/.bluepill load ~/discourse/config/discourse.pill
|
||||
|
||||
## Log rotation setup
|
||||
|
||||
|
@ -360,6 +294,11 @@ The corresponding site setting is:
|
|||
|
||||
# Run these commands as the discourse user
|
||||
bluepill stop
|
||||
bluepill quit
|
||||
# Back up your install
|
||||
DATESTAMP=$(TZ=UTC date +%F-%T)
|
||||
pg_dump --no-owner --clean discourse_prod | gzip -c > ~/discourse-db-$DATESTAMP.sql.gz
|
||||
tar cfz ~/discourse-dir-$DATESTAMP.tar.gz -C ~ discourse
|
||||
# Pull down the latest release
|
||||
cd ~/discourse
|
||||
git checkout master
|
||||
|
@ -367,9 +306,65 @@ The corresponding site setting is:
|
|||
git fetch --tags
|
||||
# To run on the latest version instead of bleeding-edge:
|
||||
#git checkout latest-release
|
||||
#
|
||||
# Follow the section below titled:
|
||||
# "Check sample configuration files for new settings"
|
||||
#
|
||||
bundle install --without test --deployment
|
||||
RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ENV=production rake db:migrate
|
||||
RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ENV=production rake assets:precompile
|
||||
bluepill start
|
||||
# restart bluepill
|
||||
crontab -l
|
||||
# Here, run the command to start bluepill.
|
||||
# Get it from the crontab output above.
|
||||
|
||||
Note that if bluepill *itself* needs to be restarted, it must be killed with `bluepill quit` and restarted with the same command that's in crontab
|
||||
|
||||
### Check sample configuration files for new settings
|
||||
|
||||
Check the sample configuration files provided in the repo with the ones being used for additional recommended settings and merge those in:
|
||||
|
||||
# Run these commands as the discourse user
|
||||
cd ~/discourse
|
||||
diff -u config/discourse.pill.sample config/discourse.pill
|
||||
diff -u config/nginx.sample.conf /etc/nginx/conf.d/discourse.conf
|
||||
diff -u config/environments/production.rb.sample config/environments/production.rb
|
||||
|
||||
#### Example 1
|
||||
|
||||
$ diff -u config/discourse.pill.sample config/discourse.pill
|
||||
--- config/discourse.pill.sample 2013-07-15 17:38:06.501507001 +0000
|
||||
+++ config/discourse.pill 2013-07-05 06:38:27.133506896 +0000
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
app.working_dir = rails_root
|
||||
sockdir = "#{rails_root}/tmp/sockets"
|
||||
- File.directory? sockdir or FileUtils.mkdir_p sockdir
|
||||
+ File.directory? sockdir or Dir.mkdir sockdir
|
||||
num_webs.times do |i|
|
||||
app.process("thin-#{i}") do |process|
|
||||
|
||||
This change reflects us switching to using `FileUtils.mkdir_p` instead of `Dir.mkdir`.
|
||||
|
||||
#### Example 2
|
||||
|
||||
$ diff -u config/nginx.sample.conf /etc/nginx/conf.d/discourse.conf
|
||||
--- config/nginx.sample.conf 2013-07-15 17:38:06.521507000 +0000
|
||||
+++ /etc/nginx/conf.d/discourse.conf 2013-07-15 17:52:46.649507024 +0000
|
||||
@@ -12,17 +12,18 @@
|
||||
gzip_min_length 1000;
|
||||
gzip_types application/json text/css application/x-javascript;
|
||||
|
||||
- server_name enter.your.web.hostname.here;
|
||||
+ server_name webtier.discourse.org;
|
||||
|
||||
sendfile on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
- client_max_body_size 2m;
|
||||
location / {
|
||||
root /home/discourse/discourse/public;
|
||||
|
||||
This change reflects a change in placeholder information plus (importantly)
|
||||
adding the `client_max_body_size 2m;` directive to the nginx.conf. This change
|
||||
should also be made to your production file.
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# Discourse Migration Guide
|
||||
|
||||
## Install new server
|
||||
|
||||
Complete a fresh install of Discourse on the new server, following the official guide, except for the initial database population (rake db:migrate).
|
||||
|
||||
## Review old server
|
||||
|
||||
On old server, run `git status` and review changes to the tree. For example:
|
||||
|
||||
# On branch master
|
||||
# Changes not staged for commit:
|
||||
# (use "git add <file>..." to update what will be committed)
|
||||
# (use "git checkout -- <file>..." to discard changes in working directory)
|
||||
#
|
||||
# modified: app/assets/javascripts/external/Markdown.Editor.js
|
||||
# modified: app/views/layouts/application.html.erb
|
||||
# modified: config/application.rb
|
||||
#
|
||||
# Untracked files:
|
||||
# (use "git add <file>..." to include in what will be committed)
|
||||
#
|
||||
# app/views/layouts/application.html.erb.bitnami
|
||||
# config/environments/production.rb
|
||||
# log/clockworkd.clock.output
|
||||
# log/clockworkd.clock.pid
|
||||
# log/sidekiq.pid
|
||||
# vendor/gems/active_model_serializers/
|
||||
# vendor/gems/fast_blank/
|
||||
# vendor/gems/message_bus/
|
||||
# vendor/gems/redis-rack-cache/
|
||||
# vendor/gems/sprockets/
|
||||
# vendor/gems/vestal_versions/
|
||||
|
||||
### Review for changes
|
||||
|
||||
Review each of the changed files for changes that need to be manually moved over
|
||||
|
||||
* Ignore all files under vendor/gems
|
||||
* Ignore files under log/
|
||||
|
||||
Check your config/environments/production.rb, config/discourse.pill,
|
||||
config/database.yml (as per the upgrade instructions)
|
||||
|
||||
## Move DB
|
||||
|
||||
Take DB dump with:
|
||||
|
||||
pg_dump --no-owner -U user_name -W database_name
|
||||
|
||||
Copy it over to the new server
|
||||
|
||||
Run as discourse user:
|
||||
|
||||
* createdb discourse_prod
|
||||
* psql discourse_prod
|
||||
* \i discourse_dump_from_old_server.sql
|
||||
|
||||
On oldserver:
|
||||
|
||||
* rsync -avz -e ssh public newserver:public
|
||||
|
||||
bundle install --without test --deployment
|
||||
RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ENV=production rake db:migrate
|
||||
RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ENV=production rake assets:precompile
|
||||
RUBY_GC_MALLOC_LIMIT=90000000 RAILS_ENV=production rake posts:rebake
|
||||
|
||||
Are you just testing your migration? Disable outgoing email by changing
|
||||
`config/environments/production.rb` and adding the following below the mail
|
||||
configuration:
|
||||
|
||||
config.action_mailer.perform_deliveries = false
|
|
@ -3,56 +3,55 @@
|
|||
Are you having trouble setting up Discourse? Here are some basic things to
|
||||
check before reaching out to the community for help:
|
||||
|
||||
|
||||
1. Are you running Ruby 1.9.3 or later?
|
||||
|
||||
Discourse is designed for Ruby 1.9.3 or later. You can check your version by
|
||||
typing `ruby -v` and checking the response.
|
||||
typing `ruby -v` (as the discourse user) and checking the response for
|
||||
something like:
|
||||
|
||||
`ruby 2.0.0p195 (2013-05-14 revision 40734) [x86_64-linux]`
|
||||
|
||||
|
||||
2. Are you on Postgres 9.1 or later with HSTORE enabled?
|
||||
1. Are you on Postgres 9.1 or later with HSTORE enabled?
|
||||
|
||||
You can check your postgres version by typing `psql --version`. To see if hstore is
|
||||
installed, open a session to postgres and type `\dx` and see if hstore is listed.
|
||||
You can check your postgres version by typing `psql --version`. To see if
|
||||
hstore is installed, open a session to postgres and type `\dx` and see if
|
||||
hstore is listed.
|
||||
|
||||
|
||||
3. Have you run `bundle install`?
|
||||
1. Have you run `bundle install`?
|
||||
|
||||
We frequently update our dependencies to newer versions. It is a good idea to run
|
||||
`bundle install` every time you check out Discourse, especially if it's been a while.
|
||||
We frequently update our dependencies to newer versions. It is a good idea
|
||||
to run `bundle install` every time you check out Discourse, especially if it's
|
||||
been a while.
|
||||
|
||||
4. Did you run `bundle update`?
|
||||
1. Did you run `bundle update`?
|
||||
|
||||
Don't. Running `bundle update` will download gem versions that we haven't tested with.
|
||||
The Gemfile.lock has the gem versions that Discourse currently uses, so `bundle install`
|
||||
will work. If you ran update, then you should uninstall the gems, run
|
||||
`git checkout -- Gemfile.lock` and then run `bundle install`.
|
||||
Don't. Running `bundle update` will download gem versions that we haven't
|
||||
tested with. The Gemfile.lock has the gem versions that Discourse currently
|
||||
uses, so `bundle install` will work. If you ran update, then you should
|
||||
uninstall the gems, run `git checkout -- Gemfile.lock` and then run `bundle
|
||||
install`.
|
||||
|
||||
5. Have you migrated your database?
|
||||
1. Have you migrated your database?
|
||||
|
||||
Our schema changes fairly frequently. After checking out the source code, you should
|
||||
run `rake db:migrate`
|
||||
Our schema changes fairly frequently. After checking out the source code,
|
||||
you should run `rake db:migrate`.
|
||||
|
||||
1. Do the tests pass?
|
||||
|
||||
6. Have you added the seed data?
|
||||
If you are having other problems, it's useful to know if the test suite
|
||||
passes. You can run it by first using `rake db:test:prepare` and then `rake
|
||||
spec`. If you experience any failures, that's a bad sign! Our master branch
|
||||
should *always* pass every test.
|
||||
|
||||
We depend on some basic seed data being present in the database. You should run
|
||||
`rake db:seed_fu` to keep your database in sync.
|
||||
1. Have you updated host_names in your database.yml?
|
||||
|
||||
|
||||
7. Do the tests pass?
|
||||
|
||||
If you are having other problems, it's useful to know if the test suite passes. You
|
||||
can run it by first using `rake db:test:prepare` and then `rake spec`. If you
|
||||
experience any failures, that's a bad sign! Our master branch should *always* pass
|
||||
every test.
|
||||
|
||||
8. Have you updated host_names in your database.yml?
|
||||
|
||||
If links in emails have localhost in them, then you are still using the default host_names
|
||||
value in database.yml. Update it to use your site's host name(s).
|
||||
If links in emails have localhost in them, then you are still using the
|
||||
default `host_names` value in database.yml. Update it to use your site's host
|
||||
name(s).
|
||||
|
||||
9. Are you having problems bundling:
|
||||
1. Are you having problems bundling:
|
||||
|
||||
```
|
||||
ArgumentError: invalid byte sequence in US-ASCII
|
||||
|
@ -75,3 +74,13 @@ Encoding.default_external = Encoding::UTF_8
|
|||
Encoding.default_internal = Encoding::UTF_8
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Check your ~/discourse/log/production.log file if you are getting HTTP 500
|
||||
errors.
|
||||
|
||||
Some common situations:
|
||||
|
||||
**Problem:** `ActiveRecord::StatementInvalid (PG::Error: ERROR: column X does not exist`
|
||||
**Solution**: run `db:migrate` task to apply migrations to the database
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class AvatarLookup
|
||||
|
||||
def initialize(user_ids=[])
|
||||
@user_ids = user_ids.tap(&:compact!).tap(&:uniq!)
|
||||
@user_ids = user_ids.tap(&:compact!).tap(&:uniq!).tap(&:flatten!)
|
||||
end
|
||||
|
||||
# Lookup a user by id
|
||||
|
|
|
@ -223,10 +223,12 @@ class CookedPostProcessor
|
|||
|
||||
def attachments
|
||||
if SiteSetting.enable_s3_uploads?
|
||||
@doc.css("a[href^=\"#{S3Store.base_url}\"]")
|
||||
@doc.css("a.attachment[href^=\"#{S3Store.base_url}\"]")
|
||||
else
|
||||
# local uploads are identified using a relative uri
|
||||
@doc.css("a[href^=\"#{LocalStore.directory}\"]")
|
||||
@doc.css("a.attachment[href^=\"#{LocalStore.directory}\"]") +
|
||||
# when cdn is enabled, we have the whole url
|
||||
@doc.css("a.attachment[href^=\"#{LocalStore.base_url}\"]")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,124 +1,125 @@
|
|||
module HTML
|
||||
class WhiteListSanitizer
|
||||
# Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute
|
||||
def sanitize_css(style)
|
||||
# disallow urls
|
||||
style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ')
|
||||
unless Rails.version =~ /^4/
|
||||
module HTML
|
||||
class WhiteListSanitizer
|
||||
# Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute
|
||||
def sanitize_css(style)
|
||||
# disallow urls
|
||||
style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ')
|
||||
|
||||
# gauntlet
|
||||
if style !~ /\A([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*\z/ ||
|
||||
style !~ /\A(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*\z/
|
||||
return ''
|
||||
end
|
||||
# gauntlet
|
||||
if style !~ /\A([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*\z/ ||
|
||||
style !~ /\A(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*\z/
|
||||
return ''
|
||||
end
|
||||
|
||||
clean = []
|
||||
style.scan(/([-\w]+)\s*:\s*([^:;]*)/) do |prop,val|
|
||||
if allowed_css_properties.include?(prop.downcase)
|
||||
clean << prop + ': ' + val + ';'
|
||||
elsif shorthand_css_properties.include?(prop.split('-')[0].downcase)
|
||||
unless val.split().any? do |keyword|
|
||||
!allowed_css_keywords.include?(keyword) &&
|
||||
keyword !~ /\A(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)\z/
|
||||
end
|
||||
clean << prop + ': ' + val + ';'
|
||||
clean = []
|
||||
style.scan(/([-\w]+)\s*:\s*([^:;]*)/) do |prop,val|
|
||||
if allowed_css_properties.include?(prop.downcase)
|
||||
clean << prop + ': ' + val + ';'
|
||||
elsif shorthand_css_properties.include?(prop.split('-')[0].downcase)
|
||||
unless val.split().any? do |keyword|
|
||||
!allowed_css_keywords.include?(keyword) &&
|
||||
keyword !~ /\A(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)\z/
|
||||
end
|
||||
clean << prop + ': ' + val + ';'
|
||||
end
|
||||
end
|
||||
end
|
||||
clean.join(' ')
|
||||
end
|
||||
clean.join(' ')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module HTML
|
||||
class WhiteListSanitizer
|
||||
self.protocol_separator = /:|(�*58)|(p)|(�*3a)|(%|%)3A/i
|
||||
module HTML
|
||||
class WhiteListSanitizer
|
||||
self.protocol_separator = /:|(�*58)|(p)|(�*3a)|(%|%)3A/i
|
||||
|
||||
def contains_bad_protocols?(attr_name, value)
|
||||
uri_attributes.include?(attr_name) &&
|
||||
(value =~ /(^[^\/:]*):|(�*58)|(p)|(�*3a)|(%|%)3A/i && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
|
||||
def contains_bad_protocols?(attr_name, value)
|
||||
uri_attributes.include?(attr_name) &&
|
||||
(value =~ /(^[^\/:]*):|(�*58)|(p)|(�*3a)|(%|%)3A/i && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ActiveRecord
|
||||
class Relation
|
||||
module ActiveRecord
|
||||
class Relation
|
||||
|
||||
def where_values_hash
|
||||
equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
|
||||
node.left.relation.name == table_name
|
||||
}
|
||||
def where_values_hash
|
||||
equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
|
||||
node.left.relation.name == table_name
|
||||
}
|
||||
|
||||
Hash[equalities.map { |where| [where.left.name, where.right] }].with_indifferent_access
|
||||
end
|
||||
|
||||
Hash[equalities.map { |where| [where.left.name, where.right] }].with_indifferent_access
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
module ActiveRecord
|
||||
class PredicateBuilder # :nodoc:
|
||||
def self.build_from_hash(engine, attributes, default_table, allow_table_name = true)
|
||||
predicates = attributes.map do |column, value|
|
||||
table = default_table
|
||||
module ActiveRecord
|
||||
class PredicateBuilder # :nodoc:
|
||||
def self.build_from_hash(engine, attributes, default_table, allow_table_name = true)
|
||||
predicates = attributes.map do |column, value|
|
||||
table = default_table
|
||||
|
||||
if allow_table_name && value.is_a?(Hash)
|
||||
table = Arel::Table.new(column, engine)
|
||||
if allow_table_name && value.is_a?(Hash)
|
||||
table = Arel::Table.new(column, engine)
|
||||
|
||||
if value.empty?
|
||||
'1 = 2'
|
||||
else
|
||||
build_from_hash(engine, value, table, false)
|
||||
end
|
||||
else
|
||||
column = column.to_s
|
||||
|
||||
if allow_table_name && column.include?('.')
|
||||
table_name, column = column.split('.', 2)
|
||||
table = Arel::Table.new(table_name, engine)
|
||||
end
|
||||
|
||||
attribute = table[column]
|
||||
|
||||
case value
|
||||
when ActiveRecord::Relation
|
||||
value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
|
||||
attribute.in(value.arel.ast)
|
||||
when Array, ActiveRecord::Associations::CollectionProxy
|
||||
values = value.to_a.map {|x| x.is_a?(ActiveRecord::Base) ? x.id : x}
|
||||
ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)}
|
||||
|
||||
array_predicates = ranges.map {|range| attribute.in(range)}
|
||||
|
||||
if values.include?(nil)
|
||||
values = values.compact
|
||||
if values.empty?
|
||||
array_predicates << attribute.eq(nil)
|
||||
else
|
||||
array_predicates << attribute.in(values.compact).or(attribute.eq(nil))
|
||||
end
|
||||
if value.empty?
|
||||
'1 = 2'
|
||||
else
|
||||
array_predicates << attribute.in(values)
|
||||
build_from_hash(engine, value, table, false)
|
||||
end
|
||||
else
|
||||
column = column.to_s
|
||||
|
||||
if allow_table_name && column.include?('.')
|
||||
table_name, column = column.split('.', 2)
|
||||
table = Arel::Table.new(table_name, engine)
|
||||
end
|
||||
|
||||
array_predicates.inject {|composite, predicate| composite.or(predicate)}
|
||||
when Range, Arel::Relation
|
||||
attribute.in(value)
|
||||
when ActiveRecord::Base
|
||||
attribute.eq(value.id)
|
||||
when Class
|
||||
# FIXME: I think we need to deprecate this behavior
|
||||
attribute.eq(value.name)
|
||||
when Integer, ActiveSupport::Duration
|
||||
# Arel treats integers as literals, but they should be quoted when compared with strings
|
||||
column = engine.connection.schema_cache.columns_hash[table.name][attribute.name.to_s]
|
||||
attribute.eq(Arel::Nodes::SqlLiteral.new(engine.connection.quote(value, column)))
|
||||
else
|
||||
attribute.eq(value)
|
||||
attribute = table[column]
|
||||
|
||||
case value
|
||||
when ActiveRecord::Relation
|
||||
value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
|
||||
attribute.in(value.arel.ast)
|
||||
when Array, ActiveRecord::Associations::CollectionProxy
|
||||
values = value.to_a.map {|x| x.is_a?(ActiveRecord::Base) ? x.id : x}
|
||||
ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)}
|
||||
|
||||
array_predicates = ranges.map {|range| attribute.in(range)}
|
||||
|
||||
if values.include?(nil)
|
||||
values = values.compact
|
||||
if values.empty?
|
||||
array_predicates << attribute.eq(nil)
|
||||
else
|
||||
array_predicates << attribute.in(values.compact).or(attribute.eq(nil))
|
||||
end
|
||||
else
|
||||
array_predicates << attribute.in(values)
|
||||
end
|
||||
|
||||
array_predicates.inject {|composite, predicate| composite.or(predicate)}
|
||||
when Range, Arel::Relation
|
||||
attribute.in(value)
|
||||
when ActiveRecord::Base
|
||||
attribute.eq(value.id)
|
||||
when Class
|
||||
# FIXME: I think we need to deprecate this behavior
|
||||
attribute.eq(value.name)
|
||||
when Integer, ActiveSupport::Duration
|
||||
# Arel treats integers as literals, but they should be quoted when compared with strings
|
||||
column = engine.connection.schema_cache.columns_hash[table.name][attribute.name.to_s]
|
||||
attribute.eq(Arel::Nodes::SqlLiteral.new(engine.connection.quote(value, column)))
|
||||
else
|
||||
attribute.eq(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
predicates.flatten
|
||||
predicates.flatten
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -267,7 +267,7 @@ class Guardian
|
|||
end
|
||||
|
||||
def can_edit_post?(post)
|
||||
is_staff? || (not(post.topic.archived?) && is_my_own?(post))
|
||||
is_staff? || (!post.topic.archived? && is_my_own?(post) && !post.user_deleted &&!post.deleted_at)
|
||||
end
|
||||
|
||||
def can_edit_user?(user)
|
||||
|
@ -291,7 +291,7 @@ class Guardian
|
|||
|
||||
# Recovery Method
|
||||
def can_recover_post?(post)
|
||||
is_staff?
|
||||
is_staff? || (is_my_own?(post) && post.user_deleted && !post.deleted_at)
|
||||
end
|
||||
|
||||
def can_recover_topic?(topic)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
module Jobs
|
||||
# various consistency checks
|
||||
class DestroyOldDeletionStubs < Jobs::Base
|
||||
def execute(args)
|
||||
PostDestroyer.destroy_stubs
|
||||
end
|
||||
end
|
||||
end
|
|
@ -48,10 +48,14 @@ module Jobs
|
|||
def order_columns_for(model)
|
||||
@order_columns_for_hash ||= {
|
||||
'CategoryFeaturedTopic' => 'category_id, topic_id',
|
||||
'CategorySearchData' => 'category_id',
|
||||
'PostOneboxRender' => 'post_id, onebox_render_id',
|
||||
'PostReply' => 'post_id, reply_id',
|
||||
'PostSearchData' => 'post_id',
|
||||
'PostTiming' => 'topic_id, post_number, user_id',
|
||||
'SiteContent' => 'content_type',
|
||||
'TopicUser' => 'topic_id, user_id',
|
||||
'UserSearchData' => 'user_id',
|
||||
'View' => 'parent_id, parent_type, ip_address, viewed_at'
|
||||
}
|
||||
@order_columns_for_hash[model.name]
|
||||
|
|
|
@ -4,7 +4,7 @@ module LocalStore
|
|||
unique_sha1 = Digest::SHA1.hexdigest("#{Time.now.to_s}#{file.original_filename}")[0,16]
|
||||
extension = File.extname(file.original_filename)
|
||||
clean_name = "#{unique_sha1}#{extension}"
|
||||
url_root = "/uploads/#{RailsMultisite::ConnectionManagement.current_db}/#{upload_id}"
|
||||
url_root = "#{directory}/#{upload_id}"
|
||||
path = "#{Rails.root}/public#{url_root}"
|
||||
|
||||
FileUtils.mkdir_p path
|
||||
|
@ -41,7 +41,7 @@ module LocalStore
|
|||
end
|
||||
|
||||
def self.asset_host
|
||||
ActionController::Base.asset_host
|
||||
Rails.configuration.action_controller.asset_host
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -20,6 +20,7 @@ class PostCreator
|
|||
# who is the post "author." For example when copying posts to a new
|
||||
# topic.
|
||||
# created_at - Post creation time (optional)
|
||||
# auto_track - Automatically track this topic if needed (default true)
|
||||
#
|
||||
# When replying to a topic:
|
||||
# topic_id - topic we're replying to
|
||||
|
@ -65,6 +66,7 @@ class PostCreator
|
|||
store_unique_post_key
|
||||
send_notifications_for_private_message
|
||||
track_topic
|
||||
update_topic_stats
|
||||
update_user_counts
|
||||
publish
|
||||
@post.advance_draft_sequence
|
||||
|
@ -99,19 +101,6 @@ class PostCreator
|
|||
post.last_version_at ||= Time.now
|
||||
end
|
||||
|
||||
def self.after_create_tasks(post)
|
||||
# Update attributes on the topic - featured users and last posted.
|
||||
attrs = {last_posted_at: post.created_at, last_post_user_id: post.user_id}
|
||||
attrs[:bumped_at] = post.created_at unless post.no_bump
|
||||
post.topic.update_attributes(attrs)
|
||||
|
||||
# Update topic user data
|
||||
TopicUser.change(post.user.id,
|
||||
post.topic.id,
|
||||
posted: true,
|
||||
last_read_post_number: post.post_number,
|
||||
seen_post_count: post.post_number)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
|
@ -183,6 +172,13 @@ class PostCreator
|
|||
@topic = topic
|
||||
end
|
||||
|
||||
def update_topic_stats
|
||||
# Update attributes on the topic - featured users and last posted.
|
||||
attrs = {last_posted_at: @post.created_at, last_post_user_id: @post.user_id}
|
||||
attrs[:bumped_at] = @post.created_at unless @post.no_bump
|
||||
@topic.update_attributes(attrs)
|
||||
end
|
||||
|
||||
def setup_post
|
||||
post = @topic.posts.new(raw: @opts[:raw],
|
||||
user: @user,
|
||||
|
@ -264,7 +260,15 @@ class PostCreator
|
|||
end
|
||||
|
||||
def track_topic
|
||||
TopicUser.auto_track(@user.id, @topic.id, TopicUser.notification_reasons[:created_post])
|
||||
unless @opts[:auto_track] == false
|
||||
TopicUser.auto_track(@user.id, @topic.id, TopicUser.notification_reasons[:created_post])
|
||||
# Update topic user data
|
||||
TopicUser.change(@post.user.id,
|
||||
@post.topic.id,
|
||||
posted: true,
|
||||
last_read_post_number: @post.post_number,
|
||||
seen_post_count: @post.post_number)
|
||||
end
|
||||
end
|
||||
|
||||
def enqueue_jobs
|
||||
|
|
|
@ -4,6 +4,13 @@
|
|||
#
|
||||
class PostDestroyer
|
||||
|
||||
def self.destroy_stubs
|
||||
Post.where(deleted_at: nil, user_deleted: true)
|
||||
.where('updated_at < ? AND post_number > 1', 1.day.ago).each do |post|
|
||||
PostDestroyer.new(Discourse.system_user, post).destroy
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(user, post)
|
||||
@user, @post = user, post
|
||||
end
|
||||
|
@ -16,6 +23,19 @@ class PostDestroyer
|
|||
end
|
||||
end
|
||||
|
||||
def recover
|
||||
if @user.staff? && @post.deleted_at
|
||||
staff_recovered
|
||||
elsif @user.staff? || @user.id == @post.user_id
|
||||
user_recovered
|
||||
end
|
||||
@post.topic.update_statistics
|
||||
end
|
||||
|
||||
def staff_recovered
|
||||
@post.recover!
|
||||
end
|
||||
|
||||
# When a post is properly deleted. Well, it's still soft deleted, but it will no longer
|
||||
# show up in the topic
|
||||
def staff_destroyed
|
||||
|
@ -75,4 +95,12 @@ class PostDestroyer
|
|||
end
|
||||
end
|
||||
|
||||
def user_recovered
|
||||
Post.transaction do
|
||||
@post.update_column(:user_deleted, false)
|
||||
@post.revise(@user, @post.versions.last.modifications["raw"][0], force_new_version: true)
|
||||
@post.update_flagged_posts_count
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -13,10 +13,6 @@ module S3Store
|
|||
end
|
||||
|
||||
def self.base_url
|
||||
"//s3.amazonaws.com/#{SiteSetting.s3_upload_bucket}"
|
||||
end
|
||||
|
||||
def self.base_url_old
|
||||
"//#{SiteSetting.s3_upload_bucket.downcase}.s3.amazonaws.com"
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,10 @@ class TopicCreator
|
|||
|
||||
attr_accessor :errors
|
||||
|
||||
def self.create(user, guardian, opts)
|
||||
self.new(user, guardian, opts).create
|
||||
end
|
||||
|
||||
def initialize(user, guardian, opts)
|
||||
@user = user
|
||||
@guardian = guardian
|
||||
|
@ -17,11 +21,19 @@ class TopicCreator
|
|||
process_private_message if @opts[:archetype] == Archetype.private_message
|
||||
save_topic
|
||||
|
||||
watch_topic
|
||||
|
||||
@topic
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def watch_topic
|
||||
unless @opts[:auto_track] == false
|
||||
@topic.notifier.watch_topic!(@topic.user_id)
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
topic_params = {title: @opts[:title], user_id: @user.id, last_post_user_id: @user.id}
|
||||
topic_params[:archetype] = @opts[:archetype] if @opts[:archetype].present?
|
||||
|
|
|
@ -7,6 +7,7 @@ class Validators::PostValidator < ActiveModel::Validator
|
|||
raw_quality(record)
|
||||
max_mention_validator(record)
|
||||
max_images_validator(record)
|
||||
max_attachments_validator(record)
|
||||
max_links_validator(record)
|
||||
unique_post_validator(record)
|
||||
end
|
||||
|
@ -41,6 +42,11 @@ class Validators::PostValidator < ActiveModel::Validator
|
|||
add_error_if_count_exceeded(post, :too_many_images, post.image_count, SiteSetting.newuser_max_images) unless acting_user_is_trusted?(post)
|
||||
end
|
||||
|
||||
# Ensure new users can not put too many attachments in a post
|
||||
def max_attachments_validator(post)
|
||||
add_error_if_count_exceeded(post, :too_many_attachments, post.attachment_count, SiteSetting.newuser_max_attachments) unless acting_user_is_trusted?(post)
|
||||
end
|
||||
|
||||
# Ensure new users can not put too many links in a post
|
||||
def max_links_validator(post)
|
||||
add_error_if_count_exceeded(post, :too_many_links, post.link_count, SiteSetting.newuser_max_links) unless acting_user_is_trusted?(post)
|
||||
|
|
|
@ -65,13 +65,13 @@ describe CookedPostProcessor do
|
|||
context "with locally uploaded images" do
|
||||
|
||||
let(:upload) { Fabricate(:upload) }
|
||||
let(:post) { Fabricate(:post_with_uploaded_images) }
|
||||
let(:post) { Fabricate(:post_with_uploaded_image) }
|
||||
let(:cpp) { CookedPostProcessor.new(post) }
|
||||
before { FastImage.stubs(:size) }
|
||||
before { FastImage.stubs(:size).returns([200, 400]) }
|
||||
|
||||
# all in one test to speed things up
|
||||
it "works" do
|
||||
Upload.expects(:get_from_url).returns(upload).twice
|
||||
Upload.expects(:get_from_url).returns(upload)
|
||||
cpp.post_process_images
|
||||
# ensures absolute urls on uploaded images
|
||||
cpp.html.should =~ /#{LocalStore.base_url}/
|
||||
|
@ -148,7 +148,7 @@ describe CookedPostProcessor do
|
|||
context "topic image" do
|
||||
|
||||
let(:topic) { build(:topic, id: 1) }
|
||||
let(:post) { Fabricate(:post_with_uploaded_images, topic: topic) }
|
||||
let(:post) { Fabricate(:post_with_uploaded_image, topic: topic) }
|
||||
let(:cpp) { CookedPostProcessor.new(post) }
|
||||
|
||||
it "adds a topic image if there's one in the post" do
|
||||
|
|
|
@ -485,6 +485,16 @@ describe Guardian do
|
|||
Guardian.new(post.user).can_edit?(post).should be_true
|
||||
end
|
||||
|
||||
it 'returns false if you are trying to edit a post you soft deleted' do
|
||||
post.user_deleted = true
|
||||
Guardian.new(post.user).can_edit?(post).should be_false
|
||||
end
|
||||
|
||||
it 'returns false if you are trying to edit a deleted post' do
|
||||
post.deleted_at = 1.day.ago
|
||||
Guardian.new(post.user).can_edit?(post).should be_false
|
||||
end
|
||||
|
||||
it 'returns false if another regular user tries to edit your post' do
|
||||
Guardian.new(coding_horror).can_edit?(post).should be_false
|
||||
end
|
||||
|
|
|
@ -12,12 +12,12 @@ describe Jobs::FeatureTopicUsers do
|
|||
end
|
||||
|
||||
context 'with a topic' do
|
||||
let!(:post) { Fabricate(:post) }
|
||||
let!(:post) { create_post }
|
||||
let(:topic) { post.topic }
|
||||
let!(:coding_horror) { Fabricate(:coding_horror) }
|
||||
let!(:evil_trout) { Fabricate(:evil_trout) }
|
||||
let!(:second_post) { Fabricate(:post, topic: topic, user: coding_horror)}
|
||||
let!(:third_post) { Fabricate(:post, topic: topic, user: evil_trout)}
|
||||
let!(:second_post) { create_post(topic: topic, user: coding_horror)}
|
||||
let!(:third_post) { create_post(topic: topic, user: evil_trout)}
|
||||
|
||||
it "won't feature the OP" do
|
||||
Jobs::FeatureTopicUsers.new.execute(topic_id: topic.id)
|
||||
|
|
|
@ -21,6 +21,12 @@ describe PostCreator do
|
|||
let(:creator_with_meta_data) { PostCreator.new(user, basic_topic_params.merge(meta_data: {hello: "world"} )) }
|
||||
let(:creator_with_image_sizes) { PostCreator.new(user, basic_topic_params.merge(image_sizes: image_sizes)) }
|
||||
|
||||
it "can be created with auto tracking disabled" do
|
||||
p = PostCreator.create(user, basic_topic_params.merge(auto_track: false))
|
||||
# must be 0 otherwise it will think we read the topic which is clearly untrue
|
||||
TopicUser.where(user_id: p.user_id, topic_id: p.topic_id).count.should == 0
|
||||
end
|
||||
|
||||
it "ensures the user can create the topic" do
|
||||
Guardian.any_instance.expects(:can_create?).with(Topic,nil).returns(false)
|
||||
lambda { creator.create }.should raise_error(Discourse::InvalidAccess)
|
||||
|
@ -148,8 +154,15 @@ describe PostCreator do
|
|||
|
||||
it 'increases topic response counts' do
|
||||
first_post = creator.create
|
||||
user2 = Fabricate(:coding_horror)
|
||||
|
||||
# ensure topic user is correct
|
||||
topic_user = first_post.user.topic_users.where(topic_id: first_post.topic_id).first
|
||||
topic_user.should be_present
|
||||
topic_user.should be_posted
|
||||
topic_user.last_read_post_number.should == first_post.post_number
|
||||
topic_user.seen_post_count.should == first_post.post_number
|
||||
|
||||
user2 = Fabricate(:coding_horror)
|
||||
user2.topic_reply_count.should == 0
|
||||
first_post.user.reload.topic_reply_count.should == 0
|
||||
|
||||
|
|
|
@ -8,7 +8,32 @@ describe PostDestroyer do
|
|||
end
|
||||
|
||||
let(:moderator) { Fabricate(:moderator) }
|
||||
let(:post) { Fabricate(:post) }
|
||||
let(:post) { create_post }
|
||||
|
||||
describe 'destroy_old_stubs' do
|
||||
it 'destroys stubs for deleted by user posts' do
|
||||
Fabricate(:admin)
|
||||
reply1 = create_post(topic: post.topic)
|
||||
reply2 = create_post(topic: post.topic)
|
||||
reply3 = create_post(topic: post.topic)
|
||||
|
||||
PostDestroyer.new(reply1.user, reply1).destroy
|
||||
PostDestroyer.new(reply2.user, reply2).destroy
|
||||
|
||||
reply2.update_column(:updated_at, 2.days.ago)
|
||||
|
||||
PostDestroyer.destroy_stubs
|
||||
|
||||
reply1.reload
|
||||
reply2.reload
|
||||
reply3.reload
|
||||
|
||||
reply1.deleted_at.should == nil
|
||||
reply2.deleted_at.should_not == nil
|
||||
reply3.deleted_at.should == nil
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe 'basic destroying' do
|
||||
|
||||
|
@ -17,6 +42,7 @@ describe PostDestroyer do
|
|||
|
||||
context "as the creator of the post" do
|
||||
before do
|
||||
@orig = post.cooked
|
||||
PostDestroyer.new(post.user, post).destroy
|
||||
post.reload
|
||||
end
|
||||
|
@ -24,8 +50,16 @@ describe PostDestroyer do
|
|||
it "doesn't delete the post" do
|
||||
post.deleted_at.should be_blank
|
||||
post.deleted_by.should be_blank
|
||||
post.user_deleted.should be_true
|
||||
post.raw.should == I18n.t('js.post.deleted_by_author')
|
||||
post.version.should == 2
|
||||
|
||||
# lets try to recover
|
||||
PostDestroyer.new(post.user, post).recover
|
||||
post.reload
|
||||
post.version.should == 3
|
||||
post.user_deleted.should be_false
|
||||
post.cooked.should == @orig
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -56,10 +90,10 @@ describe PostDestroyer do
|
|||
context 'deleting the second post in a topic' do
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let!(:post) { Fabricate(:post, user: user) }
|
||||
let(:topic) { post.topic }
|
||||
let!(:post) { create_post(user: user) }
|
||||
let(:topic) { post.topic.reload }
|
||||
let(:second_user) { Fabricate(:coding_horror) }
|
||||
let!(:second_post) { Fabricate(:post, topic: topic, user: second_user) }
|
||||
let!(:second_post) { create_post(topic: topic, user: second_user) }
|
||||
|
||||
before do
|
||||
PostDestroyer.new(moderator, second_post).destroy
|
||||
|
|
|
@ -24,8 +24,7 @@ describe S3Store do
|
|||
end
|
||||
|
||||
it 'returns the url of the S3 upload if successful' do
|
||||
# NOTE: s3 bucket's name are case sensitive so we can't use it as a subdomain...
|
||||
S3Store.store_file(file, "SHA", 1).should == '//s3.amazonaws.com/S3_Upload_Bucket/1SHA.png'
|
||||
S3Store.store_file(file, "SHA", 1).should == '//s3_upload_bucket.s3.amazonaws.com/1SHA.png'
|
||||
end
|
||||
|
||||
after(:each) do
|
||||
|
|
|
@ -230,7 +230,7 @@ describe TopicQuery do
|
|||
end
|
||||
|
||||
context 'created topics' do
|
||||
let!(:created_topic) { Fabricate(:post, user: user).topic }
|
||||
let!(:created_topic) { create_post(user: user).topic }
|
||||
|
||||
it "includes the created topic" do
|
||||
topics.include?(created_topic).should be_true
|
||||
|
@ -238,8 +238,8 @@ describe TopicQuery do
|
|||
end
|
||||
|
||||
context "topic you've posted in" do
|
||||
let(:other_users_topic) { Fabricate(:post, user: creator).topic }
|
||||
let!(:your_post) { Fabricate(:post, user: user, topic: other_users_topic )}
|
||||
let(:other_users_topic) { create_post(user: creator).topic }
|
||||
let!(:your_post) { create_post(user: user, topic: other_users_topic )}
|
||||
|
||||
it "includes the posted topic" do
|
||||
topics.include?(other_users_topic).should be_true
|
||||
|
|
|
@ -3,7 +3,7 @@ require 'topic_view'
|
|||
|
||||
describe TopicView do
|
||||
|
||||
let(:topic) { Fabricate(:topic) }
|
||||
let(:topic) { create_topic }
|
||||
let(:coding_horror) { Fabricate(:coding_horror) }
|
||||
let(:first_poster) { topic.user }
|
||||
|
||||
|
@ -109,53 +109,39 @@ describe TopicView do
|
|||
let(:path) { "/1234" }
|
||||
|
||||
before do
|
||||
topic.expects(:relative_url).returns(path)
|
||||
described_class.any_instance.expects(:find_topic).with(1234).returns(topic)
|
||||
topic.stubs(:relative_url).returns(path)
|
||||
TopicView.any_instance.stubs(:find_topic).with(1234).returns(topic)
|
||||
end
|
||||
|
||||
context "without a post number" do
|
||||
context "without a page" do
|
||||
it "generates a canonical path for a topic" do
|
||||
described_class.new(1234, user).canonical_path.should eql(path)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a page" do
|
||||
let(:path_with_page) { "/1234?page=5" }
|
||||
|
||||
it "generates a canonical path for a topic" do
|
||||
described_class.new(1234, user, page: 5).canonical_path.should eql(path_with_page)
|
||||
end
|
||||
end
|
||||
it "generates canonical path correctly" do
|
||||
TopicView.new(1234, user).canonical_path.should eql(path)
|
||||
TopicView.new(1234, user, page: 5).canonical_path.should eql("/1234?page=5")
|
||||
end
|
||||
context "with a post number" do
|
||||
let(:path_with_page) { "/1234?page=10" }
|
||||
before { SiteSetting.stubs(:posts_per_page).returns(5) }
|
||||
|
||||
it "generates a canonical path for a topic" do
|
||||
described_class.new(1234, user, post_number: 50).canonical_path.should eql(path_with_page)
|
||||
end
|
||||
it "generates a canonical correctly for paged results" do
|
||||
SiteSetting.stubs(:posts_per_page).returns(5)
|
||||
TopicView.new(1234, user, post_number: 50).canonical_path.should eql("/1234?page=10")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#next_page" do
|
||||
let(:p2) { stub(post_number: 2) }
|
||||
let(:topic) do
|
||||
topic = Fabricate(:topic)
|
||||
topic = create_topic
|
||||
topic.stubs(:highest_post_number).returns(5)
|
||||
topic
|
||||
end
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
described_class.any_instance.expects(:find_topic).with(1234).returns(topic)
|
||||
described_class.any_instance.stubs(:filter_posts)
|
||||
described_class.any_instance.stubs(:last_post).returns(p2)
|
||||
TopicView.any_instance.expects(:find_topic).with(1234).returns(topic)
|
||||
TopicView.any_instance.stubs(:filter_posts)
|
||||
TopicView.any_instance.stubs(:last_post).returns(p2)
|
||||
SiteSetting.stubs(:posts_per_page).returns(2)
|
||||
end
|
||||
|
||||
it "should return the next page" do
|
||||
described_class.new(1234, user).next_page.should eql(2)
|
||||
TopicView.new(1234, user).next_page.should eql(2)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -183,17 +169,17 @@ describe TopicView do
|
|||
end
|
||||
|
||||
context '.read?' do
|
||||
it 'is unread with no logged in user' do
|
||||
it 'tracks correctly' do
|
||||
# anon has nothing
|
||||
TopicView.new(topic.id).read?(1).should be_false
|
||||
end
|
||||
|
||||
it 'makes posts as unread by default' do
|
||||
# random user has nothing
|
||||
topic_view.read?(1).should be_false
|
||||
end
|
||||
|
||||
it 'knows a post is read when it has a PostTiming' do
|
||||
PostTiming.create(topic: topic, user: coding_horror, post_number: 1, msecs: 1000)
|
||||
topic_view.read?(1).should be_true
|
||||
# a real user that just read it should have it marked
|
||||
PostTiming.process_timings(coding_horror, topic.id, 1, [[1,1000]])
|
||||
TopicView.new(topic.id, coding_horror).read?(1).should be_true
|
||||
TopicView.new(topic.id, coding_horror).topic_user.should be_present
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -201,10 +187,6 @@ describe TopicView do
|
|||
it 'returns nil when there is no user' do
|
||||
TopicView.new(topic.id, nil).topic_user.should be_blank
|
||||
end
|
||||
|
||||
it 'returns a record once the user has some data' do
|
||||
TopicView.new(topic.id, coding_horror).topic_user.should be_present
|
||||
end
|
||||
end
|
||||
|
||||
context '#recent_posts' do
|
||||
|
|
|
@ -6,13 +6,13 @@ describe Unread do
|
|||
|
||||
before do
|
||||
@topic = Fabricate(:topic, posts_count: 13, highest_post_number: 13)
|
||||
@topic.notifier.watch_topic!(@topic.user_id)
|
||||
@topic_user = TopicUser.get(@topic, @topic.user)
|
||||
@topic_user.stubs(:notification_level).returns(TopicUser.notification_levels[:tracking])
|
||||
@topic_user.notification_level = TopicUser.notification_levels[:tracking]
|
||||
@unread = Unread.new(@topic, @topic_user)
|
||||
end
|
||||
|
||||
|
||||
describe 'unread_posts' do
|
||||
it 'should have 0 unread posts if the user has seen all posts' do
|
||||
@topic_user.stubs(:last_read_post_number).returns(13)
|
||||
|
|
|
@ -124,10 +124,14 @@ describe PostsController do
|
|||
response.should be_forbidden
|
||||
end
|
||||
|
||||
it "calls recover and updates the topic's statistics" do
|
||||
Post.any_instance.expects(:recover!)
|
||||
Topic.any_instance.expects(:update_statistics)
|
||||
it "recovers a post correctly" do
|
||||
topic_id = create_post.topic_id
|
||||
post = create_post(topic_id: topic_id)
|
||||
|
||||
PostDestroyer.new(user, post).destroy
|
||||
xhr :put, :recover, post_id: post.id
|
||||
post.reload
|
||||
post.deleted_at.should == nil
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -43,11 +43,8 @@ Fabricator(:post_with_images_in_quote_and_onebox, from: :post) do
|
|||
'
|
||||
end
|
||||
|
||||
Fabricator(:post_with_uploaded_images, from: :post) do
|
||||
cooked '
|
||||
<img src="/uploads/default/2/3456789012345678.png" width="1500" height="2000">
|
||||
<img src="/uploads/default/1/1234567890123456.jpg">
|
||||
'
|
||||
Fabricator(:post_with_uploaded_image, from: :post) do
|
||||
cooked '<img src="/uploads/default/2/3456789012345678.png" width="1500" height="2000">'
|
||||
end
|
||||
|
||||
Fabricator(:post_with_an_attachment, from: :post) do
|
||||
|
|
|
@ -19,8 +19,8 @@ describe PostAction do
|
|||
Given(:spammer) { Fabricate(:user, trust_level: TrustLevel.levels[:newuser]) }
|
||||
|
||||
context 'spammer post is not flagged enough times' do
|
||||
Given!(:spam_post) { Fabricate(:post, user: spammer) }
|
||||
Given!(:spam_post2) { Fabricate(:post, user: spammer) }
|
||||
Given!(:spam_post) { create_post(user: spammer) }
|
||||
Given!(:spam_post2) { create_post(user: spammer) }
|
||||
When { PostAction.act(user1, spam_post, PostActionType.types[:spam]) }
|
||||
Then { expect(spam_post.reload).to_not be_hidden }
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ describe PostAction do
|
|||
|
||||
describe "flagged_posts_report" do
|
||||
it "operates correctly" do
|
||||
post = create_post
|
||||
PostAction.act(codinghorror, post, PostActionType.types[:spam])
|
||||
mod_message = PostAction.act(Fabricate(:user), post, PostActionType.types[:notify_moderators], message: "this is a 10")
|
||||
|
||||
|
@ -30,6 +31,7 @@ describe PostAction do
|
|||
describe "messaging" do
|
||||
|
||||
it "notify moderators integration test" do
|
||||
post = create_post
|
||||
mod = moderator
|
||||
action = PostAction.act(codinghorror, post, PostActionType.types[:notify_moderators], message: "this is my special long message");
|
||||
|
||||
|
@ -100,6 +102,7 @@ describe PostAction do
|
|||
end
|
||||
|
||||
it "should ignore validated flags" do
|
||||
post = create_post
|
||||
admin = Fabricate(:admin)
|
||||
PostAction.act(codinghorror, post, PostActionType.types[:off_topic])
|
||||
post.hidden.should be_false
|
||||
|
@ -193,7 +196,7 @@ describe PostAction do
|
|||
|
||||
context "flag_counts_for" do
|
||||
it "returns the correct flag counts" do
|
||||
post = Fabricate(:post)
|
||||
post = create_post
|
||||
|
||||
SiteSetting.stubs(:flags_required_to_hide_post).returns(7)
|
||||
|
||||
|
@ -244,7 +247,7 @@ describe PostAction do
|
|||
end
|
||||
|
||||
it 'should follow the rules for automatic hiding workflow' do
|
||||
post = Fabricate(:post)
|
||||
post = create_post
|
||||
u1 = Fabricate(:evil_trout)
|
||||
u2 = Fabricate(:walter_white)
|
||||
admin = Fabricate(:admin) # we need an admin for the messages
|
||||
|
|
|
@ -184,6 +184,54 @@ describe Post do
|
|||
|
||||
end
|
||||
|
||||
describe "maximum attachments" do
|
||||
let(:newuser) { Fabricate(:user, trust_level: TrustLevel.levels[:newuser]) }
|
||||
let(:post_no_attachments) { Fabricate.build(:post, post_args.merge(user: newuser)) }
|
||||
let(:post_one_attachment) { post_with_body('<a class="attachment" href="/uploads/default/1/2082985.txt">file.txt</a>', newuser) }
|
||||
let(:post_two_attachments) { post_with_body('<a class="attachment" href="/uploads/default/2/20947092.log">errors.log</a> <a class="attachment" href="/uploads/default/3/283572385.3ds">model.3ds</a>', newuser) }
|
||||
|
||||
it "returns 0 attachments for an empty post" do
|
||||
Fabricate.build(:post).attachment_count.should == 0
|
||||
end
|
||||
|
||||
it "finds attachments from HTML" do
|
||||
post_two_attachments.attachment_count.should == 2
|
||||
end
|
||||
|
||||
context "validation" do
|
||||
|
||||
before do
|
||||
SiteSetting.stubs(:newuser_max_attachments).returns(1)
|
||||
end
|
||||
|
||||
context 'newuser' do
|
||||
it "allows a new user to post below the limit" do
|
||||
post_one_attachment.should be_valid
|
||||
end
|
||||
|
||||
it "doesn't allow more than the maximum" do
|
||||
post_two_attachments.should_not be_valid
|
||||
end
|
||||
|
||||
it "doesn't allow a new user to edit their post to insert an attachment" do
|
||||
post_no_attachments.user.trust_level = TrustLevel.levels[:new]
|
||||
post_no_attachments.save
|
||||
-> {
|
||||
post_no_attachments.revise(post_no_attachments.user, post_two_attachments.raw)
|
||||
post_no_attachments.reload
|
||||
}.should_not change(post_no_attachments, :raw)
|
||||
end
|
||||
end
|
||||
|
||||
it "allows more attachments from a not-new account" do
|
||||
post_two_attachments.user.trust_level = TrustLevel.levels[:basic]
|
||||
post_two_attachments.should be_valid
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "links" do
|
||||
let(:newuser) { Fabricate(:user, trust_level: TrustLevel.levels[:newuser]) }
|
||||
let(:no_links) { post_with_body("hello world my name is evil trout", newuser) }
|
||||
|
@ -570,19 +618,6 @@ describe Post do
|
|||
post.replies.should be_blank
|
||||
end
|
||||
|
||||
describe 'a forum topic user record for the topic' do
|
||||
|
||||
let(:topic_user) { post.user.topic_users.where(topic_id: topic.id).first }
|
||||
|
||||
it 'is set correctly' do
|
||||
topic_user.should be_present
|
||||
topic_user.should be_posted
|
||||
topic_user.last_read_post_number.should == post.post_number
|
||||
topic_user.seen_post_count.should == post.post_number
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'extract_quoted_post_numbers' do
|
||||
|
||||
let!(:post) { Fabricate(:post, post_args) }
|
||||
|
|
|
@ -339,7 +339,7 @@ describe Topic do
|
|||
it 'updates the bumped_at field when a new post is made' do
|
||||
@topic.bumped_at.should be_present
|
||||
lambda {
|
||||
Fabricate(:post, topic: @topic, user: @topic.user)
|
||||
create_post(topic: @topic, user: @topic.user)
|
||||
@topic.reload
|
||||
}.should change(@topic, :bumped_at)
|
||||
end
|
||||
|
@ -621,8 +621,8 @@ describe Topic do
|
|||
context 'last_poster info' do
|
||||
|
||||
before do
|
||||
@user = Fabricate(:user)
|
||||
@post = Fabricate(:post, user: @user)
|
||||
@post = create_post
|
||||
@user = @post.user
|
||||
@topic = @post.topic
|
||||
end
|
||||
|
||||
|
@ -633,7 +633,7 @@ describe Topic do
|
|||
context 'after a second post' do
|
||||
before do
|
||||
@second_user = Fabricate(:coding_horror)
|
||||
@new_post = Fabricate(:post, topic: @topic, user: @second_user)
|
||||
@new_post = create_post(topic: @topic, user: @second_user)
|
||||
@topic.reload
|
||||
end
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ describe TopicTrackingState do
|
|||
end
|
||||
|
||||
let(:post) do
|
||||
Fabricate(:post)
|
||||
create_post
|
||||
end
|
||||
|
||||
it "can correctly publish unread" do
|
||||
|
@ -20,6 +20,7 @@ describe TopicTrackingState do
|
|||
report.length.should == 0
|
||||
|
||||
new_post = post
|
||||
post.topic.notifier.watch_topic!(post.topic.user_id)
|
||||
|
||||
report = TopicTrackingState.report([user.id])
|
||||
|
||||
|
@ -38,7 +39,7 @@ describe TopicTrackingState do
|
|||
TopicTrackingState.report([user.id], post.topic_id + 1).should be_empty
|
||||
|
||||
# when we reply the poster should have an unread row
|
||||
Fabricate(:post, user: user, topic: post.topic)
|
||||
create_post(user: user, topic: post.topic)
|
||||
|
||||
report = TopicTrackingState.report([post.user_id, user.id])
|
||||
report.length.should == 1
|
||||
|
|
|
@ -11,8 +11,12 @@ describe TopicUser do
|
|||
DateTime.expects(:now).at_least_once.returns(yesterday)
|
||||
end
|
||||
|
||||
let!(:topic) { Fabricate(:topic) }
|
||||
let!(:user) { Fabricate(:coding_horror) }
|
||||
let!(:topic) {
|
||||
user = Fabricate(:user)
|
||||
guardian = Guardian.new(user)
|
||||
TopicCreator.create(user, guardian, title: "this is my topic title")
|
||||
}
|
||||
let(:topic_user) { TopicUser.get(topic,user) }
|
||||
let(:topic_creator_user) { TopicUser.get(topic, topic.user) }
|
||||
|
||||
|
@ -228,6 +232,7 @@ describe TopicUser do
|
|||
it "is able to self heal" do
|
||||
p1 = Fabricate(:post)
|
||||
p2 = Fabricate(:post, user: p1.user, topic: p1.topic, post_number: 2)
|
||||
p1.topic.notifier.watch_topic!(p1.user_id)
|
||||
|
||||
TopicUser.exec_sql("UPDATE topic_users set seen_post_count=100, last_read_post_number=0
|
||||
WHERE topic_id = :topic_id AND user_id = :user_id", topic_id: p1.topic_id, user_id: p1.user_id)
|
||||
|
|
|
@ -153,18 +153,18 @@ describe Upload do
|
|||
it "identifies internal or relatives urls" do
|
||||
Discourse.expects(:base_url_no_prefix).returns("http://discuss.site.com")
|
||||
Upload.has_been_uploaded?("http://discuss.site.com/uploads/default/42/0123456789ABCDEF.jpg").should == true
|
||||
Upload.has_been_uploaded?("/uploads/42/0123456789ABCDEF.jpg").should == true
|
||||
Upload.has_been_uploaded?("/uploads/default/42/0123456789ABCDEF.jpg").should == true
|
||||
end
|
||||
|
||||
it "identifies internal urls when using a CDN" do
|
||||
ActionController::Base.expects(:asset_host).returns("http://my.cdn.com").twice
|
||||
Rails.configuration.action_controller.expects(:asset_host).returns("http://my.cdn.com").twice
|
||||
Upload.has_been_uploaded?("http://my.cdn.com/uploads/default/42/0123456789ABCDEF.jpg").should == true
|
||||
end
|
||||
|
||||
it "identifies S3 uploads" do
|
||||
SiteSetting.stubs(:enable_s3_uploads).returns(true)
|
||||
SiteSetting.stubs(:s3_upload_bucket).returns("Bucket")
|
||||
Upload.has_been_uploaded?("//s3.amazonaws.com/Bucket/1337.png").should == true
|
||||
Upload.has_been_uploaded?("//bucket.s3.amazonaws.com/1337.png").should == true
|
||||
end
|
||||
|
||||
it "identifies external urls" do
|
||||
|
@ -174,30 +174,22 @@ describe Upload do
|
|||
|
||||
end
|
||||
|
||||
context ".is_on_s3?" do
|
||||
|
||||
before do
|
||||
SiteSetting.stubs(:enable_s3_uploads).returns(true)
|
||||
SiteSetting.stubs(:s3_upload_bucket).returns("BuCkEt")
|
||||
end
|
||||
|
||||
it "case-insensitively matches the old subdomain format" do
|
||||
Upload.is_on_s3?("//bucket.s3.amazonaws.com/1337.png").should == true
|
||||
end
|
||||
|
||||
it "case-sensitively matches the new folder format" do
|
||||
Upload.is_on_s3?("//s3.amazonaws.com/BuCkEt/1337.png").should == true
|
||||
Upload.is_on_s3?("//s3.amazonaws.com/bucket/1337.png").should == false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context ".get_from_url" do
|
||||
|
||||
it "works when the file has been uploaded" do
|
||||
Upload.expects(:where).returns([]).once
|
||||
Upload.get_from_url("/uploads/default/1/10387531.jpg")
|
||||
end
|
||||
|
||||
it "works when using a cdn" do
|
||||
Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com")
|
||||
Upload.expects(:where).with(url: "/uploads/default/1/02395732905.jpg").returns([]).once
|
||||
Upload.get_from_url("http://my.cdn.com/uploads/default/1/02395732905.jpg")
|
||||
end
|
||||
|
||||
it "works only when the file has been uploaded" do
|
||||
Upload.expects(:has_been_uploaded?).returns(false)
|
||||
Upload.expects(:where).never
|
||||
Upload.get_from_url("discourse.org")
|
||||
Upload.get_from_url("http://domain.com/my/file.txt")
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -12,6 +12,11 @@ require 'fakeweb'
|
|||
FakeWeb.allow_net_connect = false
|
||||
|
||||
module Helpers
|
||||
|
||||
def self.next_seq
|
||||
@next_seq = (@next_seq || 0) + 1
|
||||
end
|
||||
|
||||
def log_in(fabricator=nil)
|
||||
user = Fabricate(fabricator || :user)
|
||||
log_in_user(user)
|
||||
|
@ -49,9 +54,8 @@ Spork.prefork do
|
|||
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
|
||||
|
||||
|
||||
|
||||
# let's not run seed_fu every test
|
||||
SeedFu.quiet = true
|
||||
SeedFu.quiet = true if SeedFu.respond_to? :quiet
|
||||
SeedFu.seed
|
||||
|
||||
RSpec.configure do |config|
|
||||
|
@ -127,6 +131,21 @@ def build(*args)
|
|||
Fabricate.build(*args)
|
||||
end
|
||||
|
||||
def create_topic(args={})
|
||||
args[:title] ||= "This is my title #{Helpers.next_seq}"
|
||||
user = args.delete(:user) || Fabricate(:user)
|
||||
guardian = Guardian.new(user)
|
||||
TopicCreator.create(user, guardian, args)
|
||||
end
|
||||
|
||||
def create_post(args={})
|
||||
args[:title] ||= "This is my title #{Helpers.next_seq}"
|
||||
args[:raw] ||= "This is the raw body of my post, it is cool #{Helpers.next_seq}"
|
||||
args[:topic_id] = args[:topic].id if args[:topic]
|
||||
user = args.delete(:user) || Fabricate(:user)
|
||||
PostCreator.create(user, args)
|
||||
end
|
||||
|
||||
module MessageBus::DiagnosticsHelper
|
||||
def publish(channel, data, opts = nil)
|
||||
id = super(channel, data, opts)
|
||||
|
|
|
@ -22,13 +22,22 @@ test("uploading one file", function() {
|
|||
ok(bootbox.alert.calledWith(I18n.t('post.errors.too_many_uploads')));
|
||||
});
|
||||
|
||||
test("new user", function() {
|
||||
test("new user cannot upload images", function() {
|
||||
Discourse.SiteSettings.newuser_max_images = 0;
|
||||
this.stub(Discourse.User, 'current').withArgs("trust_level").returns(0);
|
||||
this.stub(bootbox, "alert");
|
||||
|
||||
ok(!validUpload([1]));
|
||||
ok(bootbox.alert.calledWith(I18n.t('post.errors.upload_not_allowed_for_new_user')));
|
||||
ok(!validUpload([{name: "image.png"}]));
|
||||
ok(bootbox.alert.calledWith(I18n.t('post.errors.image_upload_not_allowed_for_new_user')));
|
||||
});
|
||||
|
||||
test("new user cannot upload attachments", function() {
|
||||
Discourse.SiteSettings.newuser_max_attachments = 0;
|
||||
this.stub(Discourse.User, 'current').withArgs("trust_level").returns(0);
|
||||
this.stub(bootbox, "alert");
|
||||
|
||||
ok(!validUpload([{name: "roman.txt"}]));
|
||||
ok(bootbox.alert.calledWith(I18n.t('post.errors.attachment_upload_not_allowed_for_new_user')));
|
||||
});
|
||||
|
||||
test("ensures an authorized upload", function() {
|
||||
|
@ -141,4 +150,4 @@ test("avatarImg", function() {
|
|||
|
||||
blank(Discourse.Utilities.avatarImg({username: 'weird*username', size: 'tiny'}),
|
||||
"it doesn't render avatars for invalid usernames");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
/*jshint maxlen:10000000 */
|
||||
Discourse.SiteSettingsOriginal = {"title":"Discourse Meta","logo_url":"/assets/logo.png","logo_small_url":"/assets/logo-single.png","traditional_markdown_linebreaks":false,"top_menu":"latest|new|unread|read|favorited|categories","post_menu":"like|edit|flag|delete|share|bookmark|reply","share_links":"twitter|facebook|google+|email","track_external_right_clicks":false,"must_approve_users":false,"ga_tracking_code":"UA-33736483-2","ga_domain_name":"","enable_long_polling":true,"polling_interval":3000,"anon_polling_interval":30000,"min_post_length":20,"max_post_length":16000,"min_topic_title_length":15,"max_topic_title_length":255,"min_private_message_title_length":2,"allow_uncategorized_topics":true,"min_search_term_length":3,"flush_timings_secs":5,"suppress_reply_directly_below":true,"email_domains_blacklist":"mailinator.com","email_domains_whitelist":null,"version_checks":true,"min_title_similar_length":10,"min_body_similar_length":15,"category_colors":"BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|283890","max_upload_size_kb":1024,"category_featured_topics":6,"favicon_url":"/assets/favicon.ico","dynamic_favicon":false,"uncategorized_name":"uncategorized","uncategorized_color":"AB9364","uncategorized_text_color":"FFFFFF","invite_only":false,"login_required":false,"enable_local_logins":true,"enable_local_account_create":true,"enable_google_logins":true,"enable_yahoo_logins":true,"enable_twitter_logins":true,"enable_facebook_logins":true,"enable_cas_logins":false,"enable_github_logins":true,"enable_persona_logins":true,"educate_until_posts":2,"topic_views_heat_low":1000,"topic_views_heat_medium":2000,"topic_views_heat_high":5000,"min_private_message_post_length":5,"faq_url":"","tos_url":"","privacy_policy_url":"","authorized_extensions":".jpg|.jpeg|.png|.gif","relative_date_duration":14};
|
||||
Discourse.SiteSettings = jQuery.extend(true, {}, Discourse.SiteSettingsOriginal);
|
||||
Discourse.SiteSettings = jQuery.extend(true, {}, Discourse.SiteSettingsOriginal);
|
||||
|
|
|
@ -58,7 +58,7 @@ test('destroy by staff', function() {
|
|||
var user = Discourse.User.create({username: 'staff', staff: true});
|
||||
var post = buildPost({user: user});
|
||||
|
||||
this.stub(Discourse, 'ajax');
|
||||
this.stub(Discourse, 'ajax').returns(new Em.Deferred());
|
||||
post.destroy(user);
|
||||
|
||||
present(post.get('deleted_at'), "it has a `deleted_at` field.");
|
||||
|
|
|
@ -10,9 +10,9 @@ Gem::Specification.new do |s|
|
|||
s.summary = %q{Basic Mustache Support for Rails}
|
||||
s.description = %q{Adds the Mustache plugin and a corresponding Sprockets engine to the asset pipeline in Rails applications.}
|
||||
|
||||
s.add_development_dependency "rails", ["~> 3.1"]
|
||||
s.add_dependency 'rails', ['~> 3.1']
|
||||
s.add_development_dependency "rails", ["> 3.1"]
|
||||
s.add_dependency 'rails', ['> 3.1']
|
||||
|
||||
s.files = Dir["lib/**/*"]
|
||||
s.require_paths = ["lib"]
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue