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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +62 -0
  4. data/Rakefile +9 -0
  5. data/app/cells/decidim/initiatives/initiative_m_cell.rb +91 -0
  6. data/app/commands/decidim/initiatives/admin/illegal_initiative.rb +45 -0
  7. data/app/commands/decidim/initiatives/admin/invalidate_initiative.rb +45 -0
  8. data/app/controllers/decidim/transparent_trash/admin/application_controller.rb +26 -0
  9. data/app/controllers/decidim/transparent_trash/application_controller.rb +13 -0
  10. data/app/helpers/decidim/transparent_trash/application_helper.rb +10 -0
  11. data/app/models/decidim/initiative.rb +576 -0
  12. data/app/models/decidim/transparent_trash/application_record.rb +10 -0
  13. data/app/overrides/decidim/initiatives/initiatives/index/overriden.html.erb.deface +9 -0
  14. data/app/packs/entrypoints/decidim_transparent_trash.js +2 -0
  15. data/app/packs/images/decidim/transparent_trash/icon.svg +1 -0
  16. data/app/packs/stylesheets/decidim/transparent_trash/transparent_trash.scss +9 -0
  17. data/app/permissions/decidim/transparent_trash/admin/permissions.rb +37 -0
  18. data/app/views/decidim/initiatives/admin/initiatives/edit.html.erb +77 -0
  19. data/app/views/decidim/initiatives/initiatives/_count.html.erb +1 -0
  20. data/app/views/decidim/initiatives/initiatives/show.html.erb +102 -0
  21. data/app/views/decidim/transparent_trash/initiatives/_filters.html.erb +15 -0
  22. data/app/views/decidim/transparent_trash/initiatives/_filters_small_view.html.erb +18 -0
  23. data/app/views/decidim/transparent_trash/initiatives/_index_header.html.erb +8 -0
  24. data/app/views/decidim/transparent_trash/initiatives/index.html.erb +29 -0
  25. data/app/views/decidim/transparent_trash/initiatives/index.js.erb +10 -0
  26. data/app/views/layouts/decidim/_initiative_header.html.erb +51 -0
  27. data/config/assets.rb +9 -0
  28. data/config/i18n-tasks.yml +10 -0
  29. data/config/locales/en.yml +48 -0
  30. data/config/locales/fr.yml +48 -0
  31. data/lib/decidim/transparent_trash/admin.rb +10 -0
  32. data/lib/decidim/transparent_trash/admin_engine.rb +38 -0
  33. data/lib/decidim/transparent_trash/component.rb +40 -0
  34. data/lib/decidim/transparent_trash/engine.rb +38 -0
  35. data/lib/decidim/transparent_trash/extends/comments_seed.rb +52 -0
  36. data/lib/decidim/transparent_trash/extends/initiative_presenter.rb +32 -0
  37. data/lib/decidim/transparent_trash/extends/initiatives_admin_controller.rb +52 -0
  38. data/lib/decidim/transparent_trash/extends/initiatives_admin_permissions.rb +53 -0
  39. data/lib/decidim/transparent_trash/extends/initiatives_controller.rb +84 -0
  40. data/lib/decidim/transparent_trash/extends/initiatives_permissions.rb +31 -0
  41. data/lib/decidim/transparent_trash/extends/unpublish_initiative.rb +37 -0
  42. data/lib/decidim/transparent_trash/test/factories.rb +13 -0
  43. data/lib/decidim/transparent_trash/version.rb +13 -0
  44. data/lib/decidim/transparent_trash.rb +21 -0
  45. 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,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TransparentTrash
5
+ # Abstract class from which all models in this engine inherit.
6
+ class ApplicationRecord < ActiveRecord::Base
7
+ self.abstract_class = true
8
+ end
9
+ end
10
+ 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,2 @@
1
+ // Images
2
+ require.context("../images", true)
@@ -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,9 @@
1
+ /* css for decidim_transparent_trash */
2
+
3
+ .flex {
4
+ display: flex;
5
+ }
6
+
7
+ .justify-content-end {
8
+ justify-content: end;
9
+ }
@@ -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