hyrax 3.0.0.pre.rc3 → 3.0.0.pre.rc4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.dassie/config/locales/hyrax.es.yml +3 -1
  3. data/.dassie/config/tinymce.yml +3 -2
  4. data/.regen +1 -1
  5. data/CONTAINERS.md +12 -0
  6. data/Dockerfile +10 -8
  7. data/README.md +5 -0
  8. data/app/assets/javascripts/hyrax.js +1 -0
  9. data/app/assets/javascripts/hyrax/app.js.erb +0 -10
  10. data/app/assets/stylesheets/hyrax/_catalog.scss +1 -0
  11. data/app/assets/stylesheets/hyrax/_representative-media.scss +2 -2
  12. data/app/controllers/hyrax/file_sets_controller.rb +55 -4
  13. data/app/controllers/hyrax/my_controller.rb +1 -1
  14. data/app/forms/hyrax/forms/file_manager_form.rb +9 -2
  15. data/app/helpers/hyrax/ability_helper.rb +2 -1
  16. data/app/helpers/hyrax/contact_form_helper.rb +1 -2
  17. data/app/helpers/hyrax/hyrax_helper_behavior.rb +16 -1
  18. data/app/helpers/hyrax/workflows_helper.rb +42 -0
  19. data/app/models/concerns/hyrax/ability.rb +2 -2
  20. data/app/models/hyrax/contact_form.rb +1 -7
  21. data/app/models/hyrax/permission_template.rb +14 -0
  22. data/app/models/proxy_deposit_request.rb +8 -4
  23. data/app/presenters/hyrax/file_set_presenter.rb +10 -0
  24. data/app/presenters/hyrax/google_scholar_presenter.rb +86 -0
  25. data/app/presenters/hyrax/work_show_presenter.rb +1 -1
  26. data/app/search_builders/hyrax/dashboard/nested_collections_search_builder.rb +0 -24
  27. data/app/services/hyrax/admin_set_member_service.rb +1 -1
  28. data/app/services/hyrax/change_content_depositor_service.rb +29 -7
  29. data/app/services/hyrax/collections/permissions_service.rb +5 -6
  30. data/app/services/hyrax/custom_queries/find_ids_by_model.rb +37 -0
  31. data/app/services/hyrax/edit_permissions_service.rb +201 -0
  32. data/app/services/hyrax/identifier/dispatcher.rb +1 -1
  33. data/app/services/hyrax/listeners/active_fedora_acl_index_listener.rb +26 -0
  34. data/app/services/hyrax/listeners/metadata_index_listener.rb +5 -1
  35. data/app/views/catalog/_index_header_list_collection.html.erb +1 -1
  36. data/app/views/catalog/_index_header_list_default.html.erb +1 -1
  37. data/app/views/catalog/_index_list_default.html.erb +1 -1
  38. data/app/views/hyrax/admin/admin_sets/_show_document_list_row.html.erb +1 -1
  39. data/app/views/hyrax/base/_currently_shared.html.erb +46 -0
  40. data/app/views/hyrax/base/_form_share.html.erb +5 -33
  41. data/app/views/hyrax/base/_show_actions.html.erb +16 -14
  42. data/app/views/hyrax/base/unavailable.html.erb +1 -1
  43. data/app/views/hyrax/batch_edits/_currently_shared.html.erb +8 -0
  44. data/app/views/hyrax/collections/_show_document_list_row.html.erb +1 -1
  45. data/app/views/hyrax/collections/_sort_and_per_page.html.erb +1 -1
  46. data/app/views/hyrax/contact_form/new.html.erb +3 -1
  47. data/app/views/hyrax/dashboard/collections/_show_document_list_menu.html.erb +1 -1
  48. data/app/views/hyrax/dashboard/collections/_show_document_list_row.html.erb +1 -1
  49. data/app/views/hyrax/dashboard/collections/_sort_and_per_page.html.erb +1 -1
  50. data/app/views/hyrax/file_sets/_actions.html.erb +1 -2
  51. data/app/views/hyrax/file_sets/_permission_form.html.erb +2 -31
  52. data/app/views/hyrax/file_sets/_show_actions.html.erb +1 -1
  53. data/app/views/hyrax/file_sets/media_display/_audio.html.erb +2 -2
  54. data/app/views/hyrax/file_sets/media_display/_default.html.erb +1 -1
  55. data/app/views/hyrax/file_sets/media_display/_image.html.erb +1 -1
  56. data/app/views/hyrax/file_sets/media_display/_office_document.html.erb +1 -1
  57. data/app/views/hyrax/file_sets/media_display/_pdf.html.erb +2 -2
  58. data/app/views/hyrax/file_sets/media_display/_video.html.erb +2 -2
  59. data/app/views/hyrax/my/_sort_and_per_page.html.erb +1 -1
  60. data/app/views/layouts/_head_tag_content.html.erb +1 -1
  61. data/app/views/shared/_citations.html.erb +16 -12
  62. data/bin/hyrax-entrypoint.sh +9 -2
  63. data/chart/hyrax/Chart.yaml +2 -2
  64. data/config/initializers/listeners.rb +1 -0
  65. data/config/locales/hyrax.de.yml +71 -13
  66. data/config/locales/hyrax.en.yml +31 -19
  67. data/config/locales/hyrax.es.yml +60 -2
  68. data/config/locales/hyrax.fr.yml +60 -2
  69. data/config/locales/hyrax.it.yml +60 -2
  70. data/config/locales/hyrax.pt-BR.yml +60 -2
  71. data/config/locales/hyrax.zh.yml +60 -2
  72. data/documentation/developing-your-hyrax-based-app.md +2 -2
  73. data/hyrax.gemspec +2 -2
  74. data/lib/generators/hyrax/templates/catalog_controller.rb +4 -0
  75. data/lib/generators/hyrax/templates/config/tinymce.yml +3 -2
  76. data/lib/generators/hyrax/work_resource/work_resource_generator.rb +11 -0
  77. data/lib/hyrax/configuration.rb +14 -0
  78. data/lib/hyrax/engine.rb +1 -0
  79. data/lib/hyrax/version.rb +1 -1
  80. data/lib/wings.rb +1 -0
  81. data/lib/wings/services/custom_queries/find_ids_by_model.rb +46 -0
  82. data/lib/wings/setup.rb +1 -0
  83. data/template.rb +1 -1
  84. metadata +21 -7
@@ -367,8 +367,8 @@ module Hyrax
367
367
  .select(:source_id)
368
368
  .distinct
369
369
  .pluck(:source_id)
370
- query = "_query_:\"{!raw f=has_model_ssim}AdminSet\" AND {!terms f=id}#{ids.join(',')}"
371
- Hyrax::SolrService.count(query).positive?
370
+
371
+ Hyrax.custom_queries.find_ids_by_model(model: Hyrax::AdministrativeSet, ids: ids).any?
372
372
  end
373
373
 
374
374
  def registered_user?
@@ -24,13 +24,7 @@ module Hyrax
24
24
  end
25
25
 
26
26
  def self.issue_types_for_locale
27
- [
28
- I18n.t('hyrax.contact_form.issue_types.depositing'),
29
- I18n.t('hyrax.contact_form.issue_types.changing'),
30
- I18n.t('hyrax.contact_form.issue_types.browsing'),
31
- I18n.t('hyrax.contact_form.issue_types.reporting'),
32
- I18n.t('hyrax.contact_form.issue_types.general')
33
- ]
27
+ I18n.t('hyrax.contact_form.issue_types').values.select(&:present?)
34
28
  end
35
29
  end
36
30
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Hyrax
3
+ ##
3
4
  # Defines behavior that is applied to objects added as members of an AdminSet
4
5
  #
5
6
  # * access rights to stamp on each object
@@ -14,10 +15,14 @@ module Hyrax
14
15
  has_many :access_grants, class_name: 'Hyrax::PermissionTemplateAccess', dependent: :destroy
15
16
  accepts_nested_attributes_for :access_grants, reject_if: :all_blank
16
17
 
18
+ ##
17
19
  # @api public
20
+ #
18
21
  # Retrieve the agent_ids associated with the given agent_type and access
22
+ #
19
23
  # @param [String] agent_type
20
24
  # @param [String] access
25
+ #
21
26
  # @return [Array<String>] of agent_ids that match the given parameters
22
27
  def agent_ids_for(agent_type:, access:)
23
28
  access_grants.where(agent_type: agent_type, access: access).pluck(:agent_id)
@@ -29,6 +34,15 @@ module Hyrax
29
34
  # In a perfect world, there would be a join table that enforced uniqueness on the ID.
30
35
  has_one :active_workflow, -> { where(active: true) }, class_name: 'Sipity::Workflow', foreign_key: :permission_template_id
31
36
 
37
+ ##
38
+ # @note this is a convenience method for +Hyrax.query_service.find_by(id: template.source_id)+
39
+ #
40
+ # @return [Hyrax::Resource] the collection this template is associated with
41
+ def source
42
+ Hyrax.query_service.find_by(id: source_id)
43
+ end
44
+
45
+ ##
32
46
  # A bit of an analogue for a `belongs_to :source_model` as it crosses from Fedora to the DB
33
47
  # @return [AdminSet, ::Collection]
34
48
  # @raise [Hyrax::ObjectNotFoundError] when neither an AdminSet or Collection is found
@@ -42,7 +42,7 @@ class ProxyDepositRequest < ActiveRecord::Base
42
42
 
43
43
  validates :sending_user, :work_id, presence: true
44
44
  validate :transfer_to_should_be_a_valid_username
45
- validate :sending_user_should_not_be_receiving_user
45
+ validate :sending_user_should_not_be_receiving_user, unless: :sender_is_admin?
46
46
  validate :should_not_be_already_part_of_a_transfer
47
47
 
48
48
  after_save :send_request_transfer_message
@@ -63,16 +63,20 @@ class ProxyDepositRequest < ActiveRecord::Base
63
63
  private
64
64
 
65
65
  def transfer_to_should_be_a_valid_username
66
- errors.add(:transfer_to, "must be an existing user") unless receiving_user
66
+ errors.add(:transfer_to, I18n.t('hyrax.notifications.proxy_deposit_request.validation.valid_username')) unless receiving_user
67
67
  end
68
68
 
69
69
  def sending_user_should_not_be_receiving_user
70
- errors.add(:transfer_to, 'specify a different user to receive the work') if receiving_user && receiving_user.user_key == sending_user.user_key
70
+ errors.add(:transfer_to, I18n.t('hyrax.notifications.proxy_deposit_request.validation.sender_is_not_receiver')) if receiving_user && receiving_user.user_key == sending_user.user_key
71
71
  end
72
72
 
73
73
  def should_not_be_already_part_of_a_transfer
74
74
  transfers = ProxyDepositRequest.where(work_id: work_id, status: PENDING)
75
- errors.add(:open_transfer, 'must close open transfer on the work before creating a new one') unless transfers.blank? || (transfers.count == 1 && transfers[0].id == id)
75
+ errors.add(:open_transfer, I18n.t('hyrax.notifications.proxy_deposit_request.validation.open_transfer')) unless transfers.blank? || (transfers.count == 1 && transfers[0].id == id)
76
+ end
77
+
78
+ def sender_is_admin?
79
+ sending_user.ability.admin?
76
80
  end
77
81
 
78
82
  public
@@ -34,6 +34,10 @@ module Hyrax
34
34
  :original_file_id,
35
35
  to: :solr_document
36
36
 
37
+ def workflow
38
+ nil
39
+ end
40
+
37
41
  def single_use_links
38
42
  @single_use_links ||= SingleUseLink.where(item_id: id).map { |link| link_presenter_class.new(link) }
39
43
  end
@@ -90,6 +94,7 @@ module Hyrax
90
94
  end
91
95
 
92
96
  def user_can_perform_any_action?
97
+ Deprecation.warn("We're removing Hyrax::FileSetPresenter.user_can_perform_any_action? in Hyrax 4.0.0; Instead use can? in view contexts.")
93
98
  current_ability.can?(:edit, id) || current_ability.can?(:destroy, id) || current_ability.can?(:download, id)
94
99
  end
95
100
 
@@ -103,6 +108,11 @@ module Hyrax
103
108
  ids = Hyrax::SolrService.query("{!field f=member_ids_ssim}#{id}", fl: Hyrax.config.id_field)
104
109
  .map { |x| x.fetch(Hyrax.config.id_field) }
105
110
  Hyrax.logger.warn("Couldn't find a parent work for FileSet: #{id}.") if ids.empty?
111
+ ids.each do |id|
112
+ doc = ::SolrDocument.find(id)
113
+ next if current_ability.can?(:edit, doc)
114
+ raise WorkflowAuthorizationException if doc.suppressed? && current_ability.can?(:read, doc)
115
+ end
106
116
  Hyrax::PresenterFactory.build_for(ids: ids,
107
117
  presenter_class: WorkShowPresenter,
108
118
  presenter_args: current_ability).first
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ ##
5
+ # Handles presentation for google scholar meta tags.
6
+ #
7
+ # @example
8
+ # my_book = Monograph.new(title: ['On Moomins'], creator: ['Tove', 'Lars'])
9
+ # scholar = GoogleScholarPresenter.new(my_book)
10
+ #
11
+ # scholar.title => 'On Moomins'
12
+ #
13
+ # @see https://scholar.google.com/intl/en/scholar/inclusion.html#overview
14
+ class GoogleScholarPresenter < Draper::Decorator
15
+ ##
16
+ # @note Scholar content inclusion docs indicate we should embed metadata
17
+ # for "scholarly articles - journal papers, conference papers,
18
+ # technical reports, or their drafts, dissertations, pre-prints,
19
+ # post-prints, or abstracts." Implementations should try to return
20
+ # `false` for other content.
21
+ #
22
+ # @return [Boolean] whether this content is "scholarly" for Google Scholar's
23
+ # purposes. delegates to decorated object if possible.
24
+ #
25
+ # @see https://scholar.google.com/intl/en/scholar/inclusion.html#content
26
+ def scholarly?
27
+ return object.scholarly? if object.respond_to?(:scholarly?)
28
+
29
+ true
30
+ end
31
+
32
+ ##
33
+ # @note Google Scholar cares about author order. when possible, this should
34
+ # return the othors in order. delegates to `#ordered_authors` when
35
+ # available.
36
+ #
37
+ # @return [Array<String>] an ordered array of author names
38
+ def authors
39
+ return object.ordered_authors if object.respond_to?(:ordered_authors)
40
+
41
+ Array(object.creator)
42
+ end
43
+
44
+ ##
45
+ # @note falls back on {#title} if no description can be found. this
46
+ # probably isn't great.
47
+ #
48
+ # @return [String] a description
49
+ def description
50
+ (Array(object.try(:description)).first || title).truncate(200)
51
+ end
52
+
53
+ ##
54
+ # @return [String] the keywords
55
+ def keywords
56
+ Array(object.try(:keyword)).join('; ')
57
+ end
58
+
59
+ ##
60
+ # @todo this should probably only return a present value if a PDF is
61
+ # available!
62
+ #
63
+ # @return [#to_s]
64
+ def pdf_url
65
+ object.try(:download_url)
66
+ end
67
+
68
+ ##
69
+ # @return [String] the publication date
70
+ def publication_date
71
+ Array(object.try(:date_created)).first || ''
72
+ end
73
+
74
+ ##
75
+ # @return [String] a string representing the publisher
76
+ def publisher
77
+ Array(object.try(:publisher)).join('; ')
78
+ end
79
+
80
+ ##
81
+ # @return [String] exactly one title; the same one every time
82
+ def title
83
+ Array(object.try(:title)).sort.first || ""
84
+ end
85
+ end
86
+ end
@@ -37,7 +37,7 @@ module Hyrax
37
37
  end
38
38
 
39
39
  # CurationConcern methods
40
- delegate :stringify_keys, :human_readable_type, :collection?, :to_s,
40
+ delegate :stringify_keys, :human_readable_type, :collection?, :to_s, :suppressed?,
41
41
  to: :solr_document
42
42
 
43
43
  # Metadata Methods
@@ -33,32 +33,8 @@ module Hyrax
33
33
  solr_parameters[:fq] += limit_clause if limit_clause # add limits to prevent illegal nesting arrangements
34
34
  end
35
35
 
36
- # If :deposit access is requested, check to see which collections the user has
37
- # deposit or manage access to.
38
- # @return [Array<String>] a list of filters to apply to the solr query
39
- def gated_discovery_filters(permission_types = discovery_permissions, ability = current_ability)
40
- return super unless permission_types.include?("deposit")
41
- ["{!terms f=id}#{collection_ids_for_deposit.join(',')}"]
42
- end
43
-
44
36
  private
45
37
 
46
- def collection_ids_for_deposit
47
- Hyrax::Collections::PermissionsService.collection_ids_for_deposit(ability: current_ability)
48
- end
49
-
50
- # My intention in this implementation is that if I need at least edit access on the queried document,
51
- # then I must have one of the following access-levels
52
- ACCESS_LEVELS_FOR_LEVEL = ActiveSupport::HashWithIndifferentAccess.new(
53
- edit: ["edit"],
54
- deposit: ["deposit"],
55
- read: ["edit", "read"],
56
- discover: ["edit", "discover", "read"]
57
- ).freeze
58
- def extract_discovery_permissions(access)
59
- ACCESS_LEVELS_FOR_LEVEL.fetch(access)
60
- end
61
-
62
38
  def limit_ids
63
39
  # exclude current collection from returned list
64
40
  limit_ids = [@collection.id]
@@ -18,7 +18,7 @@ module Hyrax
18
18
  delegate :repository, to: :scope
19
19
 
20
20
  ##
21
- # @param [#repository] scope Typically acontroller object which responds to :repository
21
+ # @param [#repository] scope Typically a controller object which responds to +#repository+
22
22
  # @param [::Collection] collection an collection of type admin set
23
23
  # @param [ActionController::Parameters] params query params
24
24
  def initialize(scope:, collection:, params:)
@@ -1,9 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
  module Hyrax
3
3
  class ChangeContentDepositorService
4
- # @param [ActiveFedora::Base, Valkyrie::Resource] work
5
- # @param [User] user
6
- # @param [TrueClass, FalseClass] reset
4
+ # Set the given `user` as the depositor of the given `work`; If
5
+ # `reset` is true, first remove all previous permissions.
6
+ #
7
+ # @param work [ActiveFedora::Base, Valkyrie::Resource] the work
8
+ # that is receiving a change of depositor
9
+ # @param user [User] the user that will "become" the depositor of
10
+ # the given work
11
+ # @param reset [TrueClass, FalseClass] when true, first clear
12
+ # permissions for the given work and contained file
13
+ # sets; regardless of true/false make the given user
14
+ # the depositor of the given work
7
15
  def self.call(work, user, reset)
8
16
  case work
9
17
  when ActiveFedora::Base
@@ -18,6 +26,7 @@ module Hyrax
18
26
  work.permissions = [] if reset
19
27
  work.apply_depositor_metadata(user)
20
28
  work.file_sets.each do |f|
29
+ f.permissions = [] if reset
21
30
  f.apply_depositor_metadata(user)
22
31
  f.save!
23
32
  end
@@ -26,6 +35,8 @@ module Hyrax
26
35
  end
27
36
  private_class_method :call_af
28
37
 
38
+ # @todo Should this include some dependency injection regarding
39
+ # the Hyrax.persister and Hyrax.custom_queries?
29
40
  def self.call_valkyrie(work, user, reset)
30
41
  if reset
31
42
  work.permission_manager.acl.permissions = []
@@ -35,19 +46,30 @@ module Hyrax
35
46
  work.proxy_depositor = work.depositor
36
47
  apply_depositor_metadata(work, user)
37
48
 
38
- Hyrax.custom_queries.find_child_filesets(resource: work).each do |f|
39
- apply_depositor_metadata(f, user)
40
- end
49
+ apply_valkyrie_changes_to_file_sets(work: work, user: user, reset: reset)
41
50
 
51
+ Hyrax.persister.save(resource: work)
42
52
  work
43
53
  end
44
54
  private_class_method :call_valkyrie
45
55
 
46
56
  def self.apply_depositor_metadata(resource, depositor)
47
57
  depositor_id = depositor.respond_to?(:user_key) ? depositor.user_key : depositor
48
- resource.depositor = depositor_id if resource.respond_to? :depositor
58
+ resource.depositor = depositor_id if resource.respond_to? :depositor=
49
59
  Hyrax::AccessControlList.new(resource: resource).grant(:edit).to(::User.find_by_user_key(depositor_id)).save
50
60
  end
51
61
  private_class_method :apply_depositor_metadata
62
+
63
+ def self.apply_valkyrie_changes_to_file_sets(work:, user:, reset:)
64
+ Hyrax.custom_queries.find_child_filesets(resource: work).each do |f|
65
+ if reset
66
+ f.permission_manager.acl.permissions = []
67
+ f.permission_manager.acl.save
68
+ end
69
+ apply_depositor_metadata(f, user)
70
+ Hyrax.persister.save(resource: f)
71
+ end
72
+ end
73
+ private_class_method :apply_valkyrie_changes_to_file_sets
52
74
  end
53
75
  end
@@ -22,15 +22,14 @@ module Hyrax
22
22
 
23
23
  def self.filter_source(source_type:, ids:)
24
24
  return [] if ids.empty?
25
- id_clause = "{!terms f=id}#{ids.join(',')}"
26
- query = case source_type
25
+ model = case source_type
27
26
  when 'admin_set'
28
- "_query_:\"{!raw f=has_model_ssim}AdminSet\""
27
+ Hyrax::AdministrativeSet
29
28
  when 'collection'
30
- "_query_:\"{!raw f=has_model_ssim}Collection\""
29
+ Hyrax::PcdmCollection
31
30
  end
32
- query += " AND #{id_clause}"
33
- Hyrax::SolrService.query(query, fl: 'id', rows: ids.count).map { |hit| hit['id'] }
31
+
32
+ Hyrax.custom_queries.find_ids_by_model(model: model, ids: ids).to_a
34
33
  end
35
34
  private_class_method :filter_source
36
35
 
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module CustomQueries
4
+ ##
5
+ # @see https://github.com/samvera/valkyrie/wiki/Queries#custom-queries
6
+ class FindIdsByModel
7
+ def self.queries
8
+ [:find_ids_by_model]
9
+ end
10
+
11
+ def initialize(query_service:)
12
+ @query_service = query_service
13
+ end
14
+
15
+ attr_reader :query_service
16
+ delegate :resource_factory, to: :query_service
17
+
18
+ ##
19
+ # @note this is an unoptimized default implementation of this custom
20
+ # query. it's Hyrax's policy to provide such implementations of custom
21
+ # queries in use for cross-compatibility of Valkyrie query services.
22
+ # it's advisable to provide an optimized query for the specific adapter.
23
+ #
24
+ # @param model [Class]
25
+ # @param ids [Enumerable<#to_s>, Symbol]
26
+ #
27
+ # @return [Enumerable<Valkyrie::ID>]
28
+ def find_ids_by_model(model:, ids: :all)
29
+ return query_service.find_all_of_model(model: model).map(&:id) if ids == :all
30
+
31
+ query_service.find_many_by_ids(ids: ids).select do |resource|
32
+ resource.is_a?(model)
33
+ end.map(&:id)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ # Encapsulates the logic to determine which object permissions may be edited by a given user
4
+ # - user is permitted to update any work permissions coming ONLY from collections they manage
5
+ # - user is not permitted to update a work permission if it comes from a collection they do not manage, even if also from a managed collection
6
+ # - user is permitted to update only non-manager permissions from any Collections
7
+ # - user is permitted to update any non-collection permissions
8
+ class EditPermissionsService
9
+ # @api public
10
+ # @since v3.0.0
11
+ #
12
+ # @param form [SimpleForm::FormBuilder]
13
+ # @param current_ability [Ability]
14
+ # @return [Hyrax::EditPermissionService]
15
+ #
16
+ # @note
17
+ # form object.class = SimpleForm::FormBuilder
18
+ # For works (i.e. GenericWork):
19
+ # - form object.object = Hyrax::GenericWorkForm
20
+ # - form object.object.model = GenericWork
21
+ # - use the work itself
22
+ # For file_sets:
23
+ # - form object.object.class = FileSet
24
+ # - use work the file_set is in
25
+ # No other object types are supported by this view. %>
26
+ def self.build_service_object_from(form:, ability:)
27
+ if form.object.respond_to?(:model) && form.object.model.work?
28
+ new(object: form.object, ability: ability)
29
+ elsif form.object.file_set?
30
+ new(object: form.object.in_works.first, ability: ability)
31
+ end
32
+ end
33
+
34
+ attr_reader :depositor, :unauthorized_collection_managers
35
+
36
+ # @param object [#depositor, #admin_set_id, #member_of_collection_ids] GenericWorkForm (if called for object) or GenericWork (if called for file set)
37
+ # @param ability [Ability] user's current_ability
38
+ def initialize(object:, ability:)
39
+ @object = object
40
+ @ability = ability
41
+ @depositor = object.depositor
42
+ unauthorized = manager_permissions_to_block
43
+ @unauthorized_managers = unauthorized.unauthorized_managers
44
+ @unauthorized_collection_managers = unauthorized.unauthorized_collection_managers
45
+ end
46
+
47
+ # @api private
48
+ # @todo refactor this code to use "can_edit?"; Thinking in negations can be challenging.
49
+ #
50
+ # @param permission_hash [Hash] one set of permission fields for object {:name, :access}
51
+ # @return [Boolean] true if user cannot edit the given permissions
52
+ def cannot_edit_permissions?(permission_hash)
53
+ permission_hash.fetch(:access) == "edit" && @unauthorized_managers.include?(permission_hash.fetch(:name))
54
+ end
55
+
56
+ # @api private
57
+ #
58
+ # @param permission_hash [Hash] one set of permission fields for object {:name, :access}
59
+ # @return [Boolean] true if given permissions are one of fixed exclusions
60
+ def excluded_permission?(permission_hash)
61
+ exclude_from_display.include? permission_hash.fetch(:name).downcase
62
+ end
63
+
64
+ # @api public
65
+ #
66
+ # This method either:
67
+ #
68
+ # * returns false if the given permission_hash is part of the fixed exclusions.
69
+ # * yields a PermissionPresenter to provide additional logic and text for rendering
70
+ #
71
+ # @param permission_hash [Hash<:name, :access>]
72
+ # @return false if the given permission_hash is a fixed exclusion
73
+ # @yield PermissionPresenter
74
+ #
75
+ # @see #excluded_permission?
76
+ def with_applicable_permission(permission_hash:)
77
+ return false if excluded_permission?(permission_hash)
78
+ yield(PermissionPresenter.new(service: self, permission_hash: permission_hash))
79
+ end
80
+
81
+ # @api private
82
+ #
83
+ # A helper class to contain specific presentation logic related to
84
+ # the EditPermissionsService
85
+ class PermissionPresenter
86
+ # @param service [Hyrax::EditPermissionsService]
87
+ # @param permission_hash [Hash]
88
+ def initialize(service:, permission_hash:)
89
+ @service = service
90
+ @permission_hash = permission_hash
91
+ end
92
+
93
+ # A hint at how permissions are granted.
94
+ #
95
+ # @return String
96
+ # rubocop:disable Rails/OutputSafety
97
+ def granted_by_html_hint
98
+ html = ""
99
+ @service.unauthorized_collection_managers.each do |managers|
100
+ next unless name == managers.fetch(:name)
101
+ html += "<br />Access granted via collection #{managers.fetch(:id)}"
102
+ end
103
+ html.html_safe
104
+ end
105
+ # rubocop:enable Rails/OutputSafety
106
+
107
+ # @return String
108
+ def name
109
+ @permission_hash.fetch(:name)
110
+ end
111
+
112
+ # @return String
113
+ def access
114
+ @permission_hash.fetch(:access)
115
+ end
116
+
117
+ # @return Boolean
118
+ # @see EditPermissionsService#cannot_edit_permissions?
119
+ def can_edit?
120
+ !@service.cannot_edit_permissions?(@permission_hash)
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ # Fixed set of users & groups to exclude from "editable" section of display
127
+ def exclude_from_display
128
+ [::Ability.public_group_name, ::Ability.registered_group_name, ::Ability.admin_group_name, @depositor]
129
+ end
130
+
131
+ BlockedPermissions = Struct.new(:unauthorized_managers, :unauthorized_collection_managers)
132
+
133
+ # find all of the other managers of collections which a user cannot manage
134
+ #
135
+ # Process used:
136
+ # - find all of the work's collections which a user can manage
137
+ # - find all of the work's collections (of a type which shares permissions) that a user cannot manage
138
+ # - find all of the managers of these collections the user cannot manage
139
+ # This gives us the manager permissions the user is not authorized to update.
140
+ #
141
+ # @return [Struct] BlockedPermissions
142
+ # - unauthorized_managers [Array] ids of managers of all collections
143
+ # - unauthorized_collection_managers [Array hashes] manager ids & collection_ids [{:name, :id}]
144
+ def manager_permissions_to_block
145
+ unauthorized_managers = []
146
+ unauthorized_collection_managers = []
147
+ object_unauthorized_collection_ids.each do |id|
148
+ Hyrax::PermissionTemplate.find_by(source_id: id).access_grants.each do |grant|
149
+ if grant.access == "manage"
150
+ unauthorized_managers << grant.agent_id
151
+ unauthorized_collection_managers += Array.wrap({ name: grant.agent_id }.merge(id: id))
152
+ end
153
+ end
154
+ end
155
+ BlockedPermissions.new(unauthorized_managers, unauthorized_collection_managers)
156
+ end
157
+
158
+ # find all of the work's collections a user can manage
159
+ # @return [Array] of collection ids
160
+ def object_managed_collection_ids
161
+ @object_managed_collection_ids ||= object_member_of & managed_collection_ids
162
+ end
163
+
164
+ # find all of the work's collections a user cannot manage
165
+ # note: if the collection type doesn't include "sharing_applies_to_new_works", we don't limit access
166
+ # @return [Array] of collection ids with limited access
167
+ def object_unauthorized_collection_ids
168
+ @object_unauthorized_collection_ids ||= begin
169
+ limited_access = []
170
+ unauthorized_collection_ids = object_member_of - object_managed_collection_ids
171
+ if unauthorized_collection_ids.any?
172
+ unauthorized_collection_ids.each do |id|
173
+ # TODO: Can we instead use a SOLR query? This seems to be somewhat expensive. However, as this is
174
+ # used in administration instead of user front-end displays, I'm not as concerned.
175
+ collection = ActiveFedora::Base.find(id)
176
+ limited_access << id if (collection.instance_of? AdminSet) || collection.share_applies_to_new_works?
177
+ end
178
+ end
179
+ limited_access
180
+ end
181
+ end
182
+
183
+ # find all of the collection ids an object is a member of
184
+ # @return [Array] array of collection ids
185
+ def object_member_of
186
+ @object_member_of ||= begin
187
+ belongs_to = []
188
+ # get all of work's collection ids from the form
189
+ belongs_to += @object.member_of_collection_ids
190
+ belongs_to << @object.admin_set_id if @object.admin_set_id.present?
191
+ belongs_to
192
+ end
193
+ end
194
+
195
+ # The list of all collections this user has manage rights on
196
+ # @return [Array] array of all collection ids that user can manage
197
+ def managed_collection_ids
198
+ Hyrax::Collections::PermissionsService.source_ids_for_manage(ability: @ability)
199
+ end
200
+ end
201
+ end