thredded 0.15.4 → 0.15.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/assets/stylesheets/thredded/base/_alerts.scss +1 -0
  4. data/app/controllers/thredded/application_controller.rb +14 -7
  5. data/app/controllers/thredded/messageboards_controller.rb +5 -6
  6. data/app/controllers/thredded/moderation_controller.rb +28 -18
  7. data/app/controllers/thredded/topics_controller.rb +1 -1
  8. data/app/forms/thredded/post_form.rb +1 -1
  9. data/app/forms/thredded/private_post_form.rb +1 -1
  10. data/app/forms/thredded/private_topic_form.rb +2 -3
  11. data/app/forms/thredded/topic_form.rb +1 -1
  12. data/app/helpers/thredded/application_helper.rb +10 -9
  13. data/app/mailer_previews/thredded/base_mailer_preview.rb +4 -4
  14. data/app/models/concerns/thredded/content_moderation_state.rb +24 -24
  15. data/app/models/concerns/thredded/topic_common.rb +1 -1
  16. data/app/models/concerns/thredded/user_topic_read_state_common.rb +1 -1
  17. data/app/models/thredded/category.rb +1 -1
  18. data/app/models/thredded/messageboard.rb +6 -6
  19. data/app/models/thredded/messageboard_user.rb +4 -1
  20. data/app/models/thredded/post.rb +4 -2
  21. data/app/models/thredded/post_moderation_record.rb +3 -9
  22. data/app/models/thredded/private_topic.rb +6 -5
  23. data/app/models/thredded/topic.rb +6 -4
  24. data/app/models/thredded/topic_category.rb +2 -2
  25. data/app/models/thredded/user_detail.rb +12 -6
  26. data/app/models/thredded/user_extender.rb +22 -22
  27. data/app/models/thredded/user_permissions/moderate/if_moderator_column_true.rb +7 -0
  28. data/app/models/thredded/user_permissions/moderate/none.rb +6 -0
  29. data/app/models/thredded/user_permissions/read/all.rb +7 -0
  30. data/app/models/thredded/user_preference.rb +9 -9
  31. data/app/policies/thredded/messageboard_policy.rb +8 -3
  32. data/app/views/thredded/messageboards/_form.html.erb +3 -2
  33. data/app/views/thredded/messageboards/new.html.erb +1 -1
  34. data/app/views/thredded/private_topics/_form.html.erb +1 -1
  35. data/app/views/thredded/private_topics/edit.html.erb +1 -1
  36. data/app/views/thredded/shared/_field_errors.html.erb +5 -3
  37. data/app/views/thredded/shared/nav/_moderation.html.erb +1 -1
  38. data/app/views/thredded/topics/_form.html.erb +1 -1
  39. data/app/views/thredded/topics/edit.html.erb +1 -1
  40. data/bin/rails +2 -2
  41. data/config/locales/ru.yml +1 -1
  42. data/lib/generators/thredded/install/install_generator.rb +2 -2
  43. data/lib/generators/thredded/install/templates/initializer.rb +3 -0
  44. data/lib/thredded.rb +15 -2
  45. data/lib/thredded/base_notifier.rb +1 -3
  46. data/lib/thredded/database_seeder.rb +2 -5
  47. data/lib/thredded/engine.rb +1 -3
  48. data/lib/thredded/rails_lt_5_2_arel_case_node.rb +4 -4
  49. data/lib/thredded/version.rb +1 -1
  50. metadata +86 -72
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4fb30f1cf973b0e6d8e26feb9006cbf20b1405cddfcaa2deb4e06ccd1d3a6524
4
- data.tar.gz: cd3fa99b4797609c4e840ed02a3117ccf7288d1c706ef879ac6ee3d32546404a
3
+ metadata.gz: 5bd8be4009af787b59ded83d2baa329f83fe8af37162b24d44191bbdae5623a7
4
+ data.tar.gz: 66601634a6691c8b0488011034dc5a03d872d203452651bf5e792ed87edfa801
5
5
  SHA512:
6
- metadata.gz: ef7c3bd8c922fc954a55e00625ffe267b0a164be7e4f36eb434de72a70f974571ae72cde41d799643ce893ef3f55aff7b5daebc70119cfe7462027d150ddf897
7
- data.tar.gz: e005eb547cbbb181d9f118ff54e9d48427a28c6b3b66dd2e19289e8b5468ceeb059b35e80d9d62b896af964ea416184ac3ea4c4fc8d189675a9b2d5dd9d23616
6
+ metadata.gz: 416d1ccbef43a2d7b2127151cba2727013a348cf3b36bb5db91cae85e179f7eb579359e945e70296fe965685b73d394125db5bb66dac691c37a756ac4ca49ac4
7
+ data.tar.gz: 37e9420a65e8278e3dd85c6159817c0336ca544d09119720234a87bad97ba4189238e159eb9a739ce5a90748eb8b6f230cfb95486b05e0c156e41b019c7ff8eb
data/README.md CHANGED
@@ -95,7 +95,7 @@ Then, see the rest of this Readme for more information about using and customizi
95
95
  Add the gem to your Gemfile:
96
96
 
97
97
  ```ruby
98
- gem 'thredded', '~> 0.15.4'
98
+ gem 'thredded', '~> 0.15.5'
99
99
  ```
100
100
 
101
101
  Add the Thredded [initializer] to your parent app by running the install generator.
@@ -17,6 +17,7 @@
17
17
  %thredded--alert {
18
18
  border: solid 1px;
19
19
  border-radius: 3px;
20
+ box-sizing: border-box;
20
21
  margin-bottom: $thredded-small-spacing;
21
22
  padding: $thredded-small-spacing;
22
23
  text-decoration: none;
@@ -16,7 +16,8 @@ module Thredded
16
16
  :unread_followed_topics_count,
17
17
  :unread_topics_count,
18
18
  :preferences,
19
- :thredded_signed_in?
19
+ :thredded_signed_in?,
20
+ :thredded_moderator?
20
21
 
21
22
  rescue_from Thredded::Errors::MessageboardNotFound,
22
23
  Thredded::Errors::PrivateTopicNotFound,
@@ -57,6 +58,11 @@ module Thredded
57
58
  !thredded_current_user.thredded_anonymous?
58
59
  end
59
60
 
61
+ def thredded_moderator?
62
+ return @is_thredded_moderator unless @is_thredded_moderator.nil?
63
+ @is_thredded_moderator = !thredded_current_user.thredded_can_moderate_messageboards.empty?
64
+ end
65
+
60
66
  if Rails::VERSION::MAJOR < 5
61
67
  # redirect_back polyfill
62
68
  def redirect_back(fallback_location:, **args)
@@ -154,9 +160,7 @@ module Thredded
154
160
  def unread_followed_topics_count
155
161
  @unread_followed_topics_count ||=
156
162
  if thredded_signed_in?
157
- scope = topics_scope
158
- scope = topics_scope.where(messageboard_id: messageboard.id) if messageboard_or_nil
159
- scope.unread_followed_by(thredded_current_user).count
163
+ topics_scope.unread_followed_by(thredded_current_user).count
160
164
  else
161
165
  0
162
166
  end
@@ -165,9 +169,7 @@ module Thredded
165
169
  def unread_topics_count
166
170
  @unread_topics_count ||=
167
171
  if thredded_signed_in?
168
- scope = topics_scope
169
- scope = topics_scope.where(messageboard_id: messageboard.id) if messageboard_or_nil
170
- scope.unread(thredded_current_user).count
172
+ topics_scope.unread(thredded_current_user).count
171
173
  else
172
174
  0
173
175
  end
@@ -190,5 +192,10 @@ module Thredded
190
192
  def thredded_require_login!
191
193
  fail Thredded::Errors::LoginRequired if thredded_current_user.thredded_anonymous?
192
194
  end
195
+
196
+ def thredded_require_moderator!
197
+ return if thredded_moderator?
198
+ fail Pundit::NotAuthorizedError, 'You are not authorized to perform this action.'
199
+ end
193
200
  end
194
201
  end
@@ -12,15 +12,14 @@ module Thredded
12
12
  end
13
13
 
14
14
  def new
15
- @messageboard = Thredded::Messageboard.new
16
- @messageboard_group = Thredded::MessageboardGroup.all
17
- authorize_creating @messageboard
15
+ @new_messageboard = Thredded::Messageboard.new
16
+ authorize_creating @new_messageboard
18
17
  end
19
18
 
20
19
  def create
21
- @messageboard = Thredded::Messageboard.new(messageboard_params)
22
- authorize_creating @messageboard
23
- if Thredded::CreateMessageboard.new(@messageboard, thredded_current_user).run
20
+ @new_messageboard = Thredded::Messageboard.new(messageboard_params)
21
+ authorize_creating @new_messageboard
22
+ if Thredded::CreateMessageboard.new(@new_messageboard, thredded_current_user).run
24
23
  redirect_to root_path
25
24
  else
26
25
  render :new
@@ -3,7 +3,7 @@
3
3
  module Thredded
4
4
  class ModerationController < Thredded::ApplicationController
5
5
  before_action :thredded_require_login!
6
- before_action :load_moderatable_messageboards
6
+ before_action :thredded_require_moderator!
7
7
 
8
8
  def pending
9
9
  @posts = Thredded::PostsPageView.new(
@@ -35,12 +35,20 @@ module Thredded
35
35
  end
36
36
 
37
37
  def moderate_post
38
- return head(:bad_request) unless Thredded::Post.moderation_states.include?(params[:moderation_state])
39
- flash[:last_moderated_record_id] = Thredded::ModeratePost.run!(
40
- post: moderatable_posts.find(params[:id]),
41
- moderation_state: params[:moderation_state],
42
- moderator: thredded_current_user,
43
- ).id
38
+ moderation_state = params[:moderation_state].to_s
39
+ return head(:bad_request) unless Thredded::Post.moderation_states.include?(moderation_state)
40
+ post = moderatable_posts.find(params[:id].to_s)
41
+ if post.moderation_state != moderation_state
42
+ flash[:last_moderated_record_id] = Thredded::ModeratePost.run!(
43
+ post: post,
44
+ moderation_state: moderation_state,
45
+ moderator: thredded_current_user,
46
+ ).id
47
+ else
48
+ flash[:alert] = "Post was already #{moderation_state}:"
49
+ flash[:last_moderated_record_id] =
50
+ Thredded::PostModerationRecord.order_newest_first.find_by(post_id: post.id)&.id
51
+ end
44
52
  redirect_back fallback_location: pending_moderation_path
45
53
  end
46
54
 
@@ -49,9 +57,7 @@ module Thredded
49
57
  .left_join_thredded_user_details
50
58
  .merge(Thredded::UserDetail.order(moderation_state_changed_at: :desc))
51
59
  @query = params[:q].to_s
52
- if @query.present?
53
- @users = DbTextSearch::CaseInsensitive.new(@users, Thredded.user_name_column).prefix(@query)
54
- end
60
+ @users = DbTextSearch::CaseInsensitive.new(@users, Thredded.user_name_column).prefix(@query) if @query.present?
55
61
  @users = @users.page(current_page)
56
62
  end
57
63
 
@@ -81,19 +87,23 @@ module Thredded
81
87
  end
82
88
 
83
89
  def moderatable_posts
84
- Thredded::Post.where(messageboard_id: @moderatable_messageboards)
90
+ if moderatable_messageboards == Thredded::Messageboard.all
91
+ Thredded::Post.all
92
+ else
93
+ Thredded::Post.where(messageboard_id: moderatable_messageboards)
94
+ end
85
95
  end
86
96
 
87
97
  def accessible_post_moderation_records
88
- Thredded::PostModerationRecord
89
- .where(messageboard_id: @moderatable_messageboards)
98
+ if moderatable_messageboards == Thredded::Messageboard.all
99
+ Thredded::PostModerationRecord.all
100
+ else
101
+ Thredded::PostModerationRecord.where(messageboard_id: moderatable_messageboards)
102
+ end
90
103
  end
91
104
 
92
- def load_moderatable_messageboards
93
- @moderatable_messageboards = thredded_current_user.thredded_can_moderate_messageboards.to_a
94
- if @moderatable_messageboards.empty? # rubocop:disable Style/GuardClause
95
- fail Pundit::NotAuthorizedError, 'You are not authorized to perform this action.'
96
- end
105
+ def moderatable_messageboards
106
+ @moderatable_messageboards ||= thredded_current_user.thredded_can_moderate_messageboards
97
107
  end
98
108
 
99
109
  def current_page
@@ -56,7 +56,7 @@ module Thredded
56
56
  return redirect_to(canonical_topic_params) unless params_match?(canonical_topic_params)
57
57
  page_scope = policy_scope(topic.posts)
58
58
  .order_oldest_first
59
- .includes(:user, :messageboard, :postable)
59
+ .includes(:user, :messageboard)
60
60
  .page(current_page)
61
61
  return redirect_to(last_page_params(page_scope)) if page_beyond_last?(page_scope)
62
62
  @posts = Thredded::TopicPostsPageView.new(thredded_current_user, topic, page_scope)
@@ -16,7 +16,7 @@ module Thredded
16
16
  def initialize(user:, topic:, post: nil, post_params: {})
17
17
  @messageboard = topic.messageboard
18
18
  @topic = topic
19
- @post = post ? post : topic.posts.build
19
+ @post = post || topic.posts.build
20
20
  user ||= Thredded::NullUser.new
21
21
 
22
22
  if post_params.include?(:quote_post)
@@ -15,7 +15,7 @@ module Thredded
15
15
  # @param post_params [Hash]
16
16
  def initialize(user:, topic:, post: nil, post_params: {})
17
17
  @topic = topic
18
- @post = post ? post : topic.posts.build
18
+ @post = post || topic.posts.build
19
19
  user ||= Thredded::NullUser.new
20
20
 
21
21
  if post_params.include?(:quote_post)
@@ -14,8 +14,7 @@ module Thredded
14
14
  :user_ids,
15
15
  :locked,
16
16
  :sticky,
17
- :content,
18
- :private_topic
17
+ :content
19
18
 
20
19
  attr_reader :user, :params
21
20
  attr_writer :user_names
@@ -131,7 +130,7 @@ module Thredded
131
130
 
132
131
  def parse_names(text) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
133
132
  result = []
134
- current = String.new
133
+ current = +''
135
134
  in_name = in_quoted = false
136
135
  text.each_char do |char|
137
136
  case char
@@ -4,7 +4,7 @@ module Thredded
4
4
  class TopicForm
5
5
  include ActiveModel::Model
6
6
 
7
- attr_accessor :title, :category_ids, :locked, :sticky, :content, :topic
7
+ attr_accessor :title, :category_ids, :locked, :sticky, :content
8
8
  attr_reader :user, :messageboard
9
9
 
10
10
  validate :validate_children
@@ -24,7 +24,7 @@ module Thredded
24
24
  'thredded--main-container',
25
25
  content_for(:thredded_page_id),
26
26
  "thredded--global-nav-icons-#{global_nav_icons_count}",
27
- ('thredded--is-moderator' unless moderatable_messageboards_ids.empty?),
27
+ ('thredded--is-moderator' if thredded_moderator?),
28
28
  ('thredded--private-messaging-enabled' if Thredded.private_messaging_enabled),
29
29
  ].compact
30
30
  end
@@ -32,7 +32,7 @@ module Thredded
32
32
  def global_nav_icons_count
33
33
  result = 1 # Notification Settings
34
34
  result += 1 if Thredded.private_messaging_enabled
35
- result += 1 if moderatable_messageboards_ids.present?
35
+ result += 1 if thredded_moderator?
36
36
  result
37
37
  end
38
38
 
@@ -115,14 +115,15 @@ module Thredded
115
115
  end
116
116
  end
117
117
 
118
- def moderatable_messageboards_ids
119
- @moderatable_messageboards_ids ||=
120
- thredded_current_user.thredded_can_moderate_messageboards.pluck(:id)
121
- end
122
-
123
118
  def posts_pending_moderation_count
124
- @posts_pending_moderation_count ||=
125
- Thredded::Post.where(messageboard_id: moderatable_messageboards_ids).pending_moderation.count
119
+ @posts_pending_moderation_count ||= begin
120
+ scope = Thredded::Post.pending_moderation
121
+ moderatable_messageboards = thredded_current_user.thredded_can_moderate_messageboards
122
+ unless moderatable_messageboards == Thredded::Messageboard.all
123
+ scope = scope.where(messageboard_id: moderatable_messageboards.pluck(:id))
124
+ end
125
+ scope.count
126
+ end
126
127
  end
127
128
  end
128
129
  end
@@ -31,7 +31,7 @@ Additionally, Markdown is extended to support the following:
31
31
  last_user: mock_user,
32
32
  locked: [false, true].sample,
33
33
  messageboard: mock_messageboard,
34
- posts_count: 1 + rand(42),
34
+ posts_count: rand(1..42),
35
35
  sticky: [false, true].sample,
36
36
  updated_at: Time.zone.now,
37
37
  user: mock_user,
@@ -45,7 +45,7 @@ Additionally, Markdown is extended to support the following:
45
45
  attr.reverse_merge(
46
46
  content: 'A test post',
47
47
  created_at: Time.zone.now,
48
- id: 1 + rand(1334),
48
+ id: rand(1..1334),
49
49
  messageboard: topic.messageboard,
50
50
  postable: topic,
51
51
  updated_at: Time.zone.now,
@@ -62,7 +62,7 @@ Additionally, Markdown is extended to support the following:
62
62
  slug: 'a-test-private-topic',
63
63
  created_at: 3.days.ago,
64
64
  last_user: mock_user,
65
- posts_count: 1 + rand(42),
65
+ posts_count: rand(1..42),
66
66
  updated_at: Time.zone.now,
67
67
  user: mock_user,
68
68
  )
@@ -75,7 +75,7 @@ Additionally, Markdown is extended to support the following:
75
75
  attr.reverse_merge(
76
76
  content: 'A test private post',
77
77
  created_at: Time.zone.now,
78
- id: 1 + rand(1334),
78
+ id: rand(1..1334),
79
79
  postable: private_topic,
80
80
  updated_at: Time.zone.now,
81
81
  user: private_topic.last_user,
@@ -11,35 +11,35 @@ module Thredded
11
11
  included do
12
12
  before_validation :set_default_moderation_state, on: :create
13
13
 
14
- scope :moderation_state_visible_to_all, -> { where(visible_to_all_arel_node) }
15
-
16
- scope :moderation_state_visible_to_user, ->(user) {
17
- visible = visible_to_all_arel_node
18
- # @type [Arel::Table]
19
- table = arel_table
20
- if user && !user.thredded_anonymous?
21
- # Own content
22
- visible = visible.or(table[:user_id].eq(user.id))
23
- # Content that one can moderate
24
- moderatable_messageboard_ids = user.thredded_can_moderate_messageboards.map(&:id)
25
- if moderatable_messageboard_ids.present?
26
- visible = visible.or(table[:messageboard_id].in(moderatable_messageboard_ids))
27
- end
28
- end
29
- where(visible)
30
- }
31
-
32
- # @return [Arel::Nodes::Node]
33
- # @api private
34
- def self.visible_to_all_arel_node
14
+ scope :moderation_state_visible_to_all, -> do
35
15
  if Thredded.content_visible_while_pending_moderation
36
16
  # All non-blocked content
37
- arel_table[:moderation_state].not_eq(moderation_states[:blocked])
17
+ where.not(moderation_state: moderation_states[:blocked])
38
18
  else
39
19
  # Only approved content
40
- arel_table[:moderation_state].eq(moderation_states[:approved])
20
+ where(moderation_state: moderation_states[:approved])
41
21
  end
42
22
  end
23
+
24
+ scope :moderation_state_visible_to_user, ->(user) {
25
+ moderatable_messageboards = user.thredded_can_moderate_messageboards
26
+ if moderatable_messageboards == Thredded::Messageboard.all
27
+ # If the user can moderate all messageboards, they can see all the content.
28
+ result = all
29
+ else
30
+ # Visible to all.
31
+ result = moderation_state_visible_to_all
32
+
33
+ # Own content.
34
+ result = result.or(where(user_id: user.id)) if user && !user.thredded_anonymous?
35
+
36
+ # Content that the user can moderate.
37
+ if moderatable_messageboards != Thredded::Messageboard.none
38
+ result = result.or(messageboard_id: moderatable_messageboards)
39
+ end
40
+ end
41
+ result
42
+ }
43
43
  end
44
44
 
45
45
  # Whether this is visible to anyone based on the moderation state.
@@ -55,7 +55,7 @@ module Thredded
55
55
  def moderation_state_visible_to_user?(user)
56
56
  moderation_state_visible_to_all? ||
57
57
  (!user.thredded_anonymous? &&
58
- (user_id == user.id || user.thredded_can_moderate_messageboards.map(&:id).include?(messageboard_id)))
58
+ (user_id == user.id || user.thredded_can_moderate_messageboard?(messageboard)))
59
59
  end
60
60
 
61
61
  private
@@ -6,7 +6,7 @@ module Thredded
6
6
  included do
7
7
  paginates_per Thredded.topics_per_page if respond_to?(:paginates_per)
8
8
 
9
- belongs_to :last_user,
9
+ belongs_to :last_user, # rubocop:disable Rails/InverseOf
10
10
  class_name: Thredded.user_class_name,
11
11
  foreign_key: 'last_user_id',
12
12
  **(Thredded.rails_gte_51? ? { optional: true } : {})
@@ -41,7 +41,7 @@ module Thredded
41
41
  # Adds `first_unread_post_page` and `last_read_post_page` columns onto the scope.
42
42
  # Skips the records that have no read posts.
43
43
  def with_page_info( # rubocop:disable Metrics/MethodLength
44
- posts_per_page: topic_class.default_per_page, posts_scope: post_class.all
44
+ posts_per_page: post_class.default_per_page, posts_scope: post_class.all
45
45
  )
46
46
  states = arel_table
47
47
  self_relation = is_a?(ActiveRecord::Relation) ? self : all
@@ -4,7 +4,7 @@ module Thredded
4
4
  class Category < ActiveRecord::Base
5
5
  extend FriendlyId
6
6
  belongs_to :messageboard
7
- has_many :topic_categories
7
+ has_many :topic_categories, inverse_of: :category, dependent: :delete_all
8
8
  has_many :topics, through: :topic_categories
9
9
  friendly_id :name, use: %i[history scoped], scope: :messageboard
10
10
 
@@ -20,7 +20,7 @@ module Thredded
20
20
  ]
21
21
  )
22
22
 
23
- validates :name, uniqueness: true, length: { maximum: 60 }, presence: true
23
+ validates :name, uniqueness: true, length: { within: Thredded.messageboard_name_length_range }, presence: true
24
24
  validates :topics_count, numericality: true
25
25
  validates :position, presence: true, on: :update
26
26
  before_save :ensure_position, on: :create
@@ -31,7 +31,7 @@ module Thredded
31
31
 
32
32
  has_many :categories, dependent: :destroy
33
33
  has_many :user_messageboard_preferences, dependent: :destroy
34
- has_many :posts, dependent: :destroy
34
+ has_many :posts, dependent: :destroy, inverse_of: :messageboard
35
35
  has_many :topics, dependent: :destroy, inverse_of: :messageboard
36
36
 
37
37
  belongs_to :last_topic, class_name: 'Thredded::Topic', **(Thredded.rails_gte_51? ? { optional: true } : {})
@@ -72,13 +72,13 @@ module Thredded
72
72
  ordered_by_topics_count_desc
73
73
  end.ordered_by_position.order(id: :asc)
74
74
  }
75
- scope :ordered_by_position, ->() { order(position: :asc) }
76
- scope :ordered_by_created_at_asc, ->() { order(created_at: :asc) }
77
- scope :ordered_by_last_post_at_desc, ->() {
75
+ scope :ordered_by_position, -> { order(position: :asc) }
76
+ scope :ordered_by_created_at_asc, -> { order(created_at: :asc) }
77
+ scope :ordered_by_last_post_at_desc, -> {
78
78
  joins('LEFT JOIN thredded_topics AS last_topics ON thredded_messageboards.last_topic_id = last_topics.id')
79
79
  .order(Arel.sql('COALESCE(last_topics.last_post_at, thredded_messageboards.created_at) DESC'))
80
80
  }
81
- scope :ordered_by_topics_count_desc, ->() {
81
+ scope :ordered_by_topics_count_desc, -> {
82
82
  order(topics_count: :desc)
83
83
  }
84
84