decidim-transparent_trash 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +62 -0
- data/Rakefile +9 -0
- data/app/cells/decidim/initiatives/initiative_m_cell.rb +91 -0
- data/app/commands/decidim/initiatives/admin/illegal_initiative.rb +45 -0
- data/app/commands/decidim/initiatives/admin/invalidate_initiative.rb +45 -0
- data/app/controllers/decidim/transparent_trash/admin/application_controller.rb +26 -0
- data/app/controllers/decidim/transparent_trash/application_controller.rb +13 -0
- data/app/helpers/decidim/transparent_trash/application_helper.rb +10 -0
- data/app/models/decidim/initiative.rb +576 -0
- data/app/models/decidim/transparent_trash/application_record.rb +10 -0
- data/app/overrides/decidim/initiatives/initiatives/index/overriden.html.erb.deface +9 -0
- data/app/packs/entrypoints/decidim_transparent_trash.js +2 -0
- data/app/packs/images/decidim/transparent_trash/icon.svg +1 -0
- data/app/packs/stylesheets/decidim/transparent_trash/transparent_trash.scss +9 -0
- data/app/permissions/decidim/transparent_trash/admin/permissions.rb +37 -0
- data/app/views/decidim/initiatives/admin/initiatives/edit.html.erb +77 -0
- data/app/views/decidim/initiatives/initiatives/_count.html.erb +1 -0
- data/app/views/decidim/initiatives/initiatives/show.html.erb +102 -0
- data/app/views/decidim/transparent_trash/initiatives/_filters.html.erb +15 -0
- data/app/views/decidim/transparent_trash/initiatives/_filters_small_view.html.erb +18 -0
- data/app/views/decidim/transparent_trash/initiatives/_index_header.html.erb +8 -0
- data/app/views/decidim/transparent_trash/initiatives/index.html.erb +29 -0
- data/app/views/decidim/transparent_trash/initiatives/index.js.erb +10 -0
- data/app/views/layouts/decidim/_initiative_header.html.erb +51 -0
- data/config/assets.rb +9 -0
- data/config/i18n-tasks.yml +10 -0
- data/config/locales/en.yml +48 -0
- data/config/locales/fr.yml +48 -0
- data/lib/decidim/transparent_trash/admin.rb +10 -0
- data/lib/decidim/transparent_trash/admin_engine.rb +38 -0
- data/lib/decidim/transparent_trash/component.rb +40 -0
- data/lib/decidim/transparent_trash/engine.rb +38 -0
- data/lib/decidim/transparent_trash/extends/comments_seed.rb +52 -0
- data/lib/decidim/transparent_trash/extends/initiative_presenter.rb +32 -0
- data/lib/decidim/transparent_trash/extends/initiatives_admin_controller.rb +52 -0
- data/lib/decidim/transparent_trash/extends/initiatives_admin_permissions.rb +53 -0
- data/lib/decidim/transparent_trash/extends/initiatives_controller.rb +84 -0
- data/lib/decidim/transparent_trash/extends/initiatives_permissions.rb +31 -0
- data/lib/decidim/transparent_trash/extends/unpublish_initiative.rb +37 -0
- data/lib/decidim/transparent_trash/test/factories.rb +13 -0
- data/lib/decidim/transparent_trash/version.rb +13 -0
- data/lib/decidim/transparent_trash.rb +21 -0
- metadata +92 -0
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Override
|
|
4
|
+
# module: decidim-initiatives
|
|
5
|
+
# path: decidim-initiatives/app/models/decidim/initiative.rb
|
|
6
|
+
module Decidim
|
|
7
|
+
# The data store for a Initiative in the Decidim::Initiatives component.
|
|
8
|
+
class Initiative < ApplicationRecord
|
|
9
|
+
include ActiveModel::Dirty
|
|
10
|
+
include Decidim::Authorable
|
|
11
|
+
include Decidim::Participable
|
|
12
|
+
include Decidim::Publicable
|
|
13
|
+
include Decidim::ScopableParticipatorySpace
|
|
14
|
+
include Decidim::Comments::Commentable
|
|
15
|
+
include Decidim::Followable
|
|
16
|
+
include Decidim::HasAttachments
|
|
17
|
+
include Decidim::HasAttachmentCollections
|
|
18
|
+
include Decidim::Traceable
|
|
19
|
+
include Decidim::Loggable
|
|
20
|
+
include Decidim::Initiatives::InitiativeSlug
|
|
21
|
+
include Decidim::Resourceable
|
|
22
|
+
include Decidim::HasReference
|
|
23
|
+
include Decidim::Randomable
|
|
24
|
+
include Decidim::Searchable
|
|
25
|
+
include Decidim::Initiatives::HasArea
|
|
26
|
+
include Decidim::TranslatableResource
|
|
27
|
+
include Decidim::HasResourcePermission
|
|
28
|
+
include Decidim::HasArea
|
|
29
|
+
include Decidim::FilterableResource
|
|
30
|
+
|
|
31
|
+
TRANSPARENT_STATES = %w(invalidated illegal).freeze
|
|
32
|
+
|
|
33
|
+
translatable_fields :title, :description, :answer
|
|
34
|
+
|
|
35
|
+
belongs_to :organization,
|
|
36
|
+
foreign_key: "decidim_organization_id",
|
|
37
|
+
class_name: "Decidim::Organization"
|
|
38
|
+
|
|
39
|
+
belongs_to :scoped_type,
|
|
40
|
+
class_name: "Decidim::InitiativesTypeScope",
|
|
41
|
+
inverse_of: :initiatives
|
|
42
|
+
|
|
43
|
+
delegate :type, :scope, :scope_name, :supports_required, to: :scoped_type, allow_nil: true
|
|
44
|
+
delegate :attachments_enabled?, :promoting_committee_enabled?, :custom_signature_end_date_enabled?, :area_enabled?, to: :type
|
|
45
|
+
delegate :name, to: :area, prefix: true, allow_nil: true
|
|
46
|
+
|
|
47
|
+
has_many :votes,
|
|
48
|
+
foreign_key: "decidim_initiative_id",
|
|
49
|
+
class_name: "Decidim::InitiativesVote",
|
|
50
|
+
dependent: :destroy,
|
|
51
|
+
inverse_of: :initiative
|
|
52
|
+
|
|
53
|
+
has_many :committee_members,
|
|
54
|
+
foreign_key: "decidim_initiatives_id",
|
|
55
|
+
class_name: "Decidim::InitiativesCommitteeMember",
|
|
56
|
+
dependent: :destroy,
|
|
57
|
+
inverse_of: :initiative
|
|
58
|
+
|
|
59
|
+
has_many :components, as: :participatory_space, dependent: :destroy
|
|
60
|
+
|
|
61
|
+
# This relationship exists only by compatibility reasons.
|
|
62
|
+
# Initiatives are not intended to have categories.
|
|
63
|
+
has_many :categories,
|
|
64
|
+
foreign_key: "decidim_participatory_space_id",
|
|
65
|
+
foreign_type: "decidim_participatory_space_type",
|
|
66
|
+
dependent: :destroy,
|
|
67
|
+
as: :participatory_space
|
|
68
|
+
|
|
69
|
+
enum signature_type: { :online => 0, :offline => 1, :any => 2 }, _suffix: true
|
|
70
|
+
enum state: { :created => 0, :validating => 1, :discarded => 2, :published => 3, :rejected => 4, :accepted => 5, :invalidated => 6, :illegal => 7 }
|
|
71
|
+
|
|
72
|
+
validates :title, :description, :state, :signature_type, presence: true
|
|
73
|
+
validates :hashtag,
|
|
74
|
+
uniqueness: { allow_blank: true, case_sensitive: false }
|
|
75
|
+
|
|
76
|
+
validate :signature_type_allowed
|
|
77
|
+
|
|
78
|
+
scope :open, lambda {
|
|
79
|
+
where.not(state: [:discarded, :rejected, :accepted, :created])
|
|
80
|
+
.currently_signable
|
|
81
|
+
}
|
|
82
|
+
scope :closed, lambda {
|
|
83
|
+
where(state: [:discarded, :rejected, :accepted])
|
|
84
|
+
.or(currently_unsignable)
|
|
85
|
+
}
|
|
86
|
+
scope :published, -> { where.not(published_at: nil) }
|
|
87
|
+
scope :with_state, ->(state) { where(state: state) if state.present? }
|
|
88
|
+
scope :transparent, -> { where(state: TRANSPARENT_STATES) }
|
|
89
|
+
scope :not_transparent, -> { where.not(state: TRANSPARENT_STATES) }
|
|
90
|
+
|
|
91
|
+
scope_search_multi :with_any_state, [:accepted, :rejected, :answered, :open, :closed]
|
|
92
|
+
|
|
93
|
+
scope :currently_signable, lambda {
|
|
94
|
+
where("signature_start_date <= ?", Date.current)
|
|
95
|
+
.where("signature_end_date >= ?", Date.current)
|
|
96
|
+
}
|
|
97
|
+
scope :currently_unsignable, lambda {
|
|
98
|
+
where("signature_start_date > ?", Date.current)
|
|
99
|
+
.or(where("signature_end_date < ?", Date.current))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
scope :answered, -> { where.not(answered_at: nil) }
|
|
103
|
+
|
|
104
|
+
scope :public_spaces, -> { published }
|
|
105
|
+
scope :signature_type_updatable, -> { created }
|
|
106
|
+
|
|
107
|
+
scope :order_by_most_recent, -> { order(created_at: :desc) }
|
|
108
|
+
scope :order_by_supports, -> { order(Arel.sql("(coalesce((online_votes->>'total')::int,0) + coalesce((offline_votes->>'total')::int,0)) DESC")) }
|
|
109
|
+
scope :order_by_most_recently_published, -> { order(published_at: :desc) }
|
|
110
|
+
scope :order_by_most_commented, lambda {
|
|
111
|
+
select("decidim_initiatives.*")
|
|
112
|
+
.left_joins(:comments)
|
|
113
|
+
.group("decidim_initiatives.id")
|
|
114
|
+
.order(Arel.sql("count(decidim_comments_comments.id) desc"))
|
|
115
|
+
}
|
|
116
|
+
scope :future_spaces, -> { none }
|
|
117
|
+
scope :past_spaces, -> { closed }
|
|
118
|
+
|
|
119
|
+
scope :with_any_type, lambda { |*original_type_ids|
|
|
120
|
+
type_ids = original_type_ids.flatten
|
|
121
|
+
return self if type_ids.include?("all")
|
|
122
|
+
|
|
123
|
+
types = InitiativesTypeScope.where(decidim_initiatives_types_id: type_ids).pluck(:id)
|
|
124
|
+
where(scoped_type: types)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Redefine the with_any_scope method as the initiative scope is defined by
|
|
128
|
+
# the initiative type scope.
|
|
129
|
+
scope :with_any_scope, lambda { |*original_scope_ids|
|
|
130
|
+
scope_ids = original_scope_ids.flatten
|
|
131
|
+
return self if scope_ids.include?("all")
|
|
132
|
+
|
|
133
|
+
clean_scope_ids = scope_ids
|
|
134
|
+
|
|
135
|
+
conditions = []
|
|
136
|
+
conditions << "decidim_initiatives_type_scopes.decidim_scopes_id IS NULL" if clean_scope_ids.delete("global")
|
|
137
|
+
conditions.concat(["? = ANY(decidim_scopes.part_of)"] * clean_scope_ids.count) if clean_scope_ids.any?
|
|
138
|
+
|
|
139
|
+
joins(:scoped_type).references(:decidim_scopes).where(conditions.join(" OR "), *clean_scope_ids.map(&:to_i))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
scope :authored_by, lambda { |author|
|
|
143
|
+
co_authoring_initiative_ids = Decidim::InitiativesCommitteeMember.where(
|
|
144
|
+
decidim_users_id: author
|
|
145
|
+
).pluck(:decidim_initiatives_id)
|
|
146
|
+
|
|
147
|
+
where(
|
|
148
|
+
decidim_author_id: author,
|
|
149
|
+
decidim_author_type: Decidim::UserBaseEntity.name
|
|
150
|
+
).or(
|
|
151
|
+
where(id: co_authoring_initiative_ids)
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
after_create :notify_creation
|
|
156
|
+
before_update :set_offline_votes_total
|
|
157
|
+
after_commit :notify_state_change
|
|
158
|
+
|
|
159
|
+
searchable_fields({
|
|
160
|
+
participatory_space: :itself,
|
|
161
|
+
A: :title,
|
|
162
|
+
D: :description,
|
|
163
|
+
datetime: :published_at
|
|
164
|
+
},
|
|
165
|
+
index_on_create: ->(_initiative) { false },
|
|
166
|
+
# is Resourceable instead of ParticipatorySpaceResourceable so we can't use `visible?`
|
|
167
|
+
index_on_update: ->(initiative) { initiative.published? })
|
|
168
|
+
|
|
169
|
+
def self.log_presenter_class_for(_log)
|
|
170
|
+
Decidim::Initiatives::AdminLog::InitiativePresenter
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def self.ransackable_scopes(_auth_object = nil)
|
|
174
|
+
[:with_any_state, :with_any_type, :with_any_scope, :with_any_area]
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
delegate :document_number_authorization_handler, :promoting_committee_enabled?, to: :type
|
|
178
|
+
delegate :type, :scope, :scope_name, to: :scoped_type, allow_nil: true
|
|
179
|
+
|
|
180
|
+
# Public: Overrides participatory space's banner image with the banner image defined
|
|
181
|
+
# for the initiative type.
|
|
182
|
+
#
|
|
183
|
+
# Returns Decidim::BannerImageUploader
|
|
184
|
+
def banner_image
|
|
185
|
+
type.attached_uploader(:banner_image)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Public: Whether the object's comments are visible or not.
|
|
189
|
+
# Initiatives invalidated and illegal are not commentable
|
|
190
|
+
def commentable?
|
|
191
|
+
type.comments_enabled? && !invalidated? && !illegal?
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Public: Check if an initiative has been created by an individual person.
|
|
195
|
+
# If it's false, then it has been created by an authorized organization.
|
|
196
|
+
#
|
|
197
|
+
# Returns a Boolean
|
|
198
|
+
def created_by_individual?
|
|
199
|
+
decidim_user_group_id.nil?
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Public: check if an initiative is open
|
|
203
|
+
#
|
|
204
|
+
# Returns a Boolean
|
|
205
|
+
def open?
|
|
206
|
+
!closed?
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Public: Checks if an initiative is closed. An initiative is closed when
|
|
210
|
+
# at least one of the following conditions is true:
|
|
211
|
+
#
|
|
212
|
+
# * It has been discarded.
|
|
213
|
+
# * It has been rejected.
|
|
214
|
+
# * It has been accepted.
|
|
215
|
+
# * It has been invalidated.
|
|
216
|
+
# * It has been illegal.
|
|
217
|
+
# * Signature collection period has finished.
|
|
218
|
+
#
|
|
219
|
+
# Returns a Boolean
|
|
220
|
+
def closed?
|
|
221
|
+
discarded? || rejected? || accepted? || invalidated? || illegal? || !votes_enabled?
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Public: Returns the author name. If it has been created by an organization it will
|
|
225
|
+
# return the organization's name. Otherwise it will return author's name.
|
|
226
|
+
#
|
|
227
|
+
# Returns a string
|
|
228
|
+
def author_name
|
|
229
|
+
user_group&.name || author.name
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def votes_enabled?
|
|
233
|
+
published? &&
|
|
234
|
+
signature_start_date <= Date.current &&
|
|
235
|
+
signature_end_date >= Date.current
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Public: Check if the user has voted the question.
|
|
239
|
+
#
|
|
240
|
+
# Returns Boolean.
|
|
241
|
+
def voted_by?(user)
|
|
242
|
+
votes.where(author: user).any?
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Public: Checks if the organization has given an answer for the initiative.
|
|
246
|
+
#
|
|
247
|
+
# Returns a Boolean.
|
|
248
|
+
def answered?
|
|
249
|
+
answered_at.present?
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Public: Overrides scopes enabled flag available in other models like
|
|
253
|
+
# participatory space or assemblies. For initiatives it won't be directly
|
|
254
|
+
# managed by the user and it will be enabled by default.
|
|
255
|
+
def scopes_enabled?
|
|
256
|
+
true
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Public: Overrides scopes enabled attribute value.
|
|
260
|
+
# For initiatives it won't be directly
|
|
261
|
+
# managed by the user and it will be enabled by default.
|
|
262
|
+
def scopes_enabled
|
|
263
|
+
true
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Public: Publishes this initiative
|
|
267
|
+
#
|
|
268
|
+
# Returns true if the record was properly saved, false otherwise.
|
|
269
|
+
def publish!
|
|
270
|
+
return false if published?
|
|
271
|
+
|
|
272
|
+
update(
|
|
273
|
+
published_at: Time.current,
|
|
274
|
+
state: "published",
|
|
275
|
+
signature_start_date: Date.current,
|
|
276
|
+
signature_end_date: signature_end_date || (Date.current + Decidim::Initiatives.default_signature_time_period_length)
|
|
277
|
+
)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Public: Publishes this initiative as invalidated
|
|
281
|
+
#
|
|
282
|
+
# Returns true if the record was properly saved, false otherwise.
|
|
283
|
+
def invalidate!
|
|
284
|
+
return false if published?
|
|
285
|
+
|
|
286
|
+
update(
|
|
287
|
+
published_at: Time.current,
|
|
288
|
+
state: "invalidated",
|
|
289
|
+
signature_start_date: nil,
|
|
290
|
+
signature_end_date: nil
|
|
291
|
+
)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Public: Publishes this initiative as illegal
|
|
295
|
+
#
|
|
296
|
+
# Returns true if the record was properly saved, false otherwise.
|
|
297
|
+
def illegal!
|
|
298
|
+
return false if published?
|
|
299
|
+
|
|
300
|
+
update(
|
|
301
|
+
published_at: Time.current,
|
|
302
|
+
state: "illegal",
|
|
303
|
+
signature_start_date: nil,
|
|
304
|
+
signature_end_date: nil
|
|
305
|
+
)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Public: Unpublishes this initiative
|
|
309
|
+
#
|
|
310
|
+
# Allows to unpublish initiatives published, invalidated, illegal
|
|
311
|
+
# Returns true if the record was properly saved, false otherwise.
|
|
312
|
+
def unpublish!
|
|
313
|
+
return false unless published? || invalidated? || illegal?
|
|
314
|
+
|
|
315
|
+
update(published_at: nil, state: "discarded")
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Public: Returns wether the signature interval is already defined or not.
|
|
319
|
+
def has_signature_interval_defined?
|
|
320
|
+
signature_end_date.present? && signature_start_date.present?
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Public: Returns the hashtag for the initiative.
|
|
324
|
+
def hashtag
|
|
325
|
+
attributes["hashtag"].to_s.delete("#")
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Public: Calculates the number of total current supports.
|
|
329
|
+
#
|
|
330
|
+
# Returns an Integer.
|
|
331
|
+
def supports_count
|
|
332
|
+
online_votes_count + offline_votes_count
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Public: Calculates the number of current supports for a scope.
|
|
336
|
+
#
|
|
337
|
+
# Returns an Integer.
|
|
338
|
+
def supports_count_for(scope)
|
|
339
|
+
online_votes_count_for(scope) + offline_votes_count_for(scope)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Public: Calculates the number of supports required to accept the initiative for a scope.
|
|
343
|
+
#
|
|
344
|
+
# Returns an Integer.
|
|
345
|
+
def supports_required_for(scope)
|
|
346
|
+
initiative_type_scopes.find_by(decidim_scopes_id: scope&.id).supports_required
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Public: Returns the percentage of required supports reached
|
|
350
|
+
def percentage
|
|
351
|
+
[supports_count * 100 / supports_required, 100].min
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Public: Whether the supports required objective has been reached
|
|
355
|
+
def supports_goal_reached?
|
|
356
|
+
votable_initiative_type_scopes.map(&:scope).all? { |scope| supports_goal_reached_for?(scope) }
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# Public: Whether the supports required objective has been reached for a scope
|
|
360
|
+
def supports_goal_reached_for?(scope)
|
|
361
|
+
supports_count_for(scope) >= supports_required_for(scope)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Public: Calculates all the votes across all the scopes.
|
|
365
|
+
#
|
|
366
|
+
# Returns an Integer.
|
|
367
|
+
def online_votes_count
|
|
368
|
+
return 0 if offline_signature_type?
|
|
369
|
+
|
|
370
|
+
online_votes["total"].to_i
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def offline_votes_count
|
|
374
|
+
return 0 if online_signature_type?
|
|
375
|
+
|
|
376
|
+
offline_votes["total"].to_i
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def online_votes_count_for(scope)
|
|
380
|
+
return 0 if offline_signature_type?
|
|
381
|
+
|
|
382
|
+
scope_key = (scope&.id || "global").to_s
|
|
383
|
+
|
|
384
|
+
(online_votes || {}).fetch(scope_key, 0).to_i
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def offline_votes_count_for(scope)
|
|
388
|
+
return 0 if online_signature_type?
|
|
389
|
+
|
|
390
|
+
scope_key = (scope&.id || "global").to_s
|
|
391
|
+
|
|
392
|
+
(offline_votes || {}).fetch(scope_key, 0).to_i
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def update_online_votes_counters
|
|
396
|
+
online_votes = votes.group(:scope).count.each_with_object({}) do |(scope, count), counters|
|
|
397
|
+
counters[scope&.id || "global"] = count
|
|
398
|
+
counters["total"] ||= 0
|
|
399
|
+
counters["total"] += count
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# rubocop:disable Rails/SkipsModelValidations
|
|
403
|
+
update_column("online_votes", online_votes)
|
|
404
|
+
# rubocop:enable Rails/SkipsModelValidations
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def set_offline_votes_total
|
|
408
|
+
return if offline_votes.blank?
|
|
409
|
+
|
|
410
|
+
offline_votes["total"] = offline_votes[scope&.id.to_s] || offline_votes["global"]
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Public: Finds all the InitiativeTypeScopes that are eligible to be voted by a user.
|
|
414
|
+
# Usually this is only the `scoped_type` but voting on children of the scoped type is
|
|
415
|
+
# enabled we have to filter all the available scopes in the initiative type to select
|
|
416
|
+
# the ones that are a descendant of the initiative type.
|
|
417
|
+
#
|
|
418
|
+
# Returns an Array of Decidim::InitiativesScopeType.
|
|
419
|
+
def votable_initiative_type_scopes
|
|
420
|
+
return Array(scoped_type) unless type.child_scope_threshold_enabled?
|
|
421
|
+
|
|
422
|
+
initiative_type_scopes.select do |initiative_type_scope|
|
|
423
|
+
initiative_type_scope.scope.present? && (scoped_type.global_scope? || scoped_type.scope.ancestor_of?(initiative_type_scope.scope))
|
|
424
|
+
end.prepend(scoped_type).uniq
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Public: Overrides slug attribute from participatory processes.
|
|
428
|
+
def slug
|
|
429
|
+
slug_from_id(id)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def to_param
|
|
433
|
+
slug
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# Public: Overrides the `comments_have_alignment?`
|
|
437
|
+
# Commentable concern method.
|
|
438
|
+
def comments_have_alignment?
|
|
439
|
+
true
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# Public: Overrides the `comments_have_votes?` Commentable concern method.
|
|
443
|
+
def comments_have_votes?
|
|
444
|
+
true
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# Public: Checks if user is the author or is part of the promotal committee
|
|
448
|
+
# of the initiative.
|
|
449
|
+
#
|
|
450
|
+
# Returns a Boolean.
|
|
451
|
+
def has_authorship?(user)
|
|
452
|
+
return true if author.id == user.id
|
|
453
|
+
|
|
454
|
+
committee_members.approved.where(decidim_users_id: user.id).any?
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def author_users
|
|
458
|
+
[author].concat(committee_members.excluding_author.map(&:user))
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def accepts_offline_votes?
|
|
462
|
+
published? && (offline_signature_type? || any_signature_type?)
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
def accepts_online_votes?
|
|
466
|
+
votes_enabled? && (online_signature_type? || any_signature_type?)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def accepts_online_unvotes?
|
|
470
|
+
accepts_online_votes? && type.undo_online_signatures_enabled?
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def minimum_committee_members
|
|
474
|
+
type.minimum_committee_members || Decidim::Initiatives.minimum_committee_members
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def enough_committee_members?
|
|
478
|
+
committee_members.approved.count >= minimum_committee_members
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def component
|
|
482
|
+
nil
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# Public: Checks if the type the initiative belongs to enables SMS code
|
|
486
|
+
# verification step. Tis configuration is ignored if the organization
|
|
487
|
+
# doesn't have the sms authorization available
|
|
488
|
+
#
|
|
489
|
+
# Returns a Boolean
|
|
490
|
+
def validate_sms_code_on_votes?
|
|
491
|
+
organization.available_authorizations.include?("sms") && type.validate_sms_code_on_votes?
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# Public: Returns an empty object. This method should be implemented by
|
|
495
|
+
# `ParticipatorySpaceResourceable`, but for some reason this model does not
|
|
496
|
+
# implement this interface.
|
|
497
|
+
def user_role_config_for(_user, _role_name)
|
|
498
|
+
Decidim::ParticipatorySpaceRoleConfig::Base.new(:empty_role_name)
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
# Public: Overrides the `allow_resource_permissions?` Resourceable concern method.
|
|
502
|
+
def allow_resource_permissions?
|
|
503
|
+
true
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def user_allowed_to_comment?(user)
|
|
507
|
+
ActionAuthorizer.new(user, "comment", self, nil).authorize.ok?
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def self.ransack(params = {}, options = {})
|
|
511
|
+
Initiatives::InitiativeSearch.new(self, params, options)
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
private
|
|
515
|
+
|
|
516
|
+
# Private: This is just an alias because the naming on InitiativeTypeScope
|
|
517
|
+
# is very confusing. The `scopes` method doesn't return Decidim::Scope but
|
|
518
|
+
# Decidim::InitiativeTypeScopes.
|
|
519
|
+
#
|
|
520
|
+
# ¯\_(ツ)_/¯
|
|
521
|
+
#
|
|
522
|
+
# Returns an Array of Decidim::InitiativesScopeType.
|
|
523
|
+
def initiative_type_scopes
|
|
524
|
+
type.scopes
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
# Private: A validator that verifies the signature type is allowed by the InitiativeType.
|
|
528
|
+
def signature_type_allowed
|
|
529
|
+
return if published?
|
|
530
|
+
|
|
531
|
+
errors.add(:signature_type, :invalid) if type.allowed_signature_types_for_initiatives.exclude?(signature_type)
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def notify_state_change
|
|
535
|
+
return unless saved_change_to_state?
|
|
536
|
+
|
|
537
|
+
notifier = Decidim::Initiatives::StatusChangeNotifier.new(initiative: self)
|
|
538
|
+
notifier.notify
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def notify_creation
|
|
542
|
+
notifier = Decidim::Initiatives::StatusChangeNotifier.new(initiative: self)
|
|
543
|
+
notifier.notify
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
# Allow ransacker to search for a key in a hstore column (`title`.`en`)
|
|
547
|
+
[:title, :description].each { |column| ransacker_i18n(column) }
|
|
548
|
+
|
|
549
|
+
# Alias search_text as a grouped OR query with all the text searchable fields.
|
|
550
|
+
ransack_alias :search_text, :id_string_or_title_or_description_or_author_name_or_author_nickname
|
|
551
|
+
|
|
552
|
+
# Allow ransacker to search on an Enum Field
|
|
553
|
+
ransacker :state, formatter: proc { |int| states[int] }
|
|
554
|
+
|
|
555
|
+
ransacker :type_id do
|
|
556
|
+
Arel.sql("decidim_initiatives_type_scopes.decidim_initiatives_types_id")
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
# method for sort_link by number of supports
|
|
560
|
+
ransacker :supports_count do
|
|
561
|
+
Arel.sql("(coalesce((online_votes->>'total')::int,0) + coalesce((offline_votes->>'total')::int,0))")
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
ransacker :id_string do
|
|
565
|
+
Arel.sql(%{cast("decidim_initiatives"."id" as text)})
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
ransacker :author_name do
|
|
569
|
+
Arel.sql("decidim_users.name")
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
ransacker :author_nickname do
|
|
573
|
+
Arel.sql("decidim_users.nickname")
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- insert_after 'div#initiatives' -->
|
|
2
|
+
|
|
3
|
+
<div class="row align-right">
|
|
4
|
+
<div class="column flex justify-content-end">
|
|
5
|
+
<%= link_to initiatives_path(visibility: :transparent), class: "title-action__action button" do %>
|
|
6
|
+
<%= t("transparent_trash", scope: "decidim.initiatives.index") %>
|
|
7
|
+
<% end %>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35 35"><path d="M17.5 35A17.5 17.5 0 1 1 35 17.5 17.52 17.52 0 0 1 17.5 35zm0-33.06A15.56 15.56 0 1 0 33.06 17.5 15.57 15.57 0 0 0 17.5 1.94zm9.5 13.7H8a1 1 0 0 1 0-1.94h19a1 1 0 0 1 0 1.94zm0 3.68H8a1 1 0 0 1 0-1.94h19a1 1 0 0 1 0 1.94zM22.26 23H8a1 1 0 0 1 0-1.94h14.26a1 1 0 0 1 0 1.94z"/></svg>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
module TransparentTrash
|
|
5
|
+
module Admin
|
|
6
|
+
class Permissions < Decidim::DefaultPermissions
|
|
7
|
+
def permissions
|
|
8
|
+
return permission_action if permission_action.scope != :admin
|
|
9
|
+
return permission_action unless user&.admin?
|
|
10
|
+
|
|
11
|
+
allow! if can_access?
|
|
12
|
+
initiative_admin_user_action?
|
|
13
|
+
|
|
14
|
+
permission_action
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def can_access?
|
|
18
|
+
permission_action.subject == :transparent_trash &&
|
|
19
|
+
permission_action.action == :read
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initiative_admin_user_action?
|
|
23
|
+
return unless permission_action.subject == :initiative
|
|
24
|
+
|
|
25
|
+
case permission_action.action
|
|
26
|
+
when :invalidate, :illegal
|
|
27
|
+
toggle_allow(initiative.validating?)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initiative
|
|
32
|
+
@initiative ||= context.fetch(:initiative, nil) || context.fetch(:current_participatory_space, nil)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|