diff --git a/Readme.md b/Readme.md index 2e1ac12..19dbca8 100644 --- a/Readme.md +++ b/Readme.md @@ -1,8 +1,8 @@ # ArchivesSpace Request Fulfillment via Aeon -**Version:** 20190529 +**Version:** 20230302 -**Last Updated:** May 29, 2019 +**Last Updated:** March 2, 2023 ## Table of Contents @@ -11,7 +11,8 @@ 1. [Table of Contents](#table-of-contents) 2. [Overview](#overview) 3. [Changelog](#changelog) - 4. [Configuring Plugin Settings](#configuring-plugin-settings) + 4. [Requirements](#requirements) + 5. [Configuring Plugin Settings](#configuring-plugin-settings) 1. [Per Repository Configuration Options](#per-repository-configuration-options) 1. [`:aeon_web_url`](#aeonweburl) 2. [`:aeon_return_link_label`](#aeonreturnlinklabel) @@ -23,21 +24,25 @@ 8. [`:aeon_site_code`](#aeonsitecode) 9. [`:hide_button_for_access_restriction_types`](#hidebuttonforaccessrestrictiontypes) 10. [`:requestable_archival_record_levels`](#requestablearchivalrecordlevels) - 11. [`:user_defined_fields`](#userdefinedfields) + 11. [`:top_container_mode`](#topcontainermode) + 12. [`:disallowed_record_level_message`](#disallowedrecordlevelmessage) + 13. [`:no_containers_message`](#nocontainersmessage) + 14. [`:restrictions_message`](#restrictionsmessage) + 15. [`:user_defined_fields`](#userdefinedfields) 2. [Other Configuration Options](#other-configuration-options) 1. [`:aeon_fulfillment_record_types`](#aeonfulfillmentrecordtypes) 2. [`:aeon_fulfillment_button_position`](#aeonfulfillmentbuttonposition) 3. [Example Configuration](#example-configuration) - 5. [Aeon Remote Authentication Configurations](#aeon-remote-authentication-configurations) - 6. [Imported Fields](#imported-fields) + 6. [Aeon Remote Authentication Configurations](#aeon-remote-authentication-configurations) + 7. [Imported Fields](#imported-fields) 1. [Common Fields](#common-fields) 2. [Archival Object Fields](#archival-object-fields) 3. [Accession Fields](#accession-fields) 4. [Resource Fields](#resource-fields) 5. [User Defined Fields](#user-defined-fields) - 7. [OpenURL Mappings](#openurl-mappings) - 8. [Custom Mappers](#custom-mappers) - 9. [Configuring the Aeon Request Form Used](#configuring-the-aeon-request-form-used) + 8. [OpenURL Mappings](#openurl-mappings) + 9. [Custom Mappers](#custom-mappers) + 10. [Configuring the Aeon Request Form Used](#configuring-the-aeon-request-form-used) ## Overview @@ -58,7 +63,7 @@ options for the built in PUI requesting functionality, it is also possible to configure some repositories to continue using the built in PUI requesting feature for archival objects while allowing other repositories to use Aeon. -This plugin has been tested on ArchivesSpace version 2.2.0. Future releases of +This plugin has been tested on ArchivesSpace version 3.3.1, and requires Aeon Server version 5.2.0 or greater. Future releases of ArchivesSpace may cause changes in the functionality of this plugin. @@ -143,7 +148,15 @@ ArchivesSpace may cause changes in the functionality of this plugin. - Added the `:user_defined_fields` setting - **20190529** - Bug fixes for compatibility with ArchivesSpace v2.6.0 RC1 - +- **20230302** + - Added `:top_container_mode` setting to support new Aeon Archival Request form. + - Added some additional mapping options. + +## Requirements + +- Aeon Server 5.2.0 or greater +- ArchivesSpace 3.3.1 + ## Configuring Plugin Settings ***Please note that the Aeon OpenURLMapping table must be configured in the Customization Manager @@ -268,8 +281,8 @@ sent. #### `:hide_button_for_access_restriction_types` This setting allows the request button to be hidden for any records that have -any of the listed local access restriction types. The value of this config item -should be an array of restriction types, for example: +any of the listed values in the local_access_restriction_type field of the rights_restriction +of the accessrestrict note. The value of this config item should be an array of restriction types, for example: `:hide_button_for_access_restriction_types => ['RestrictedSpecColl']` @@ -337,6 +350,30 @@ AppConfig[:aeon_fulfillment] = { } ``` +#### `:top_container_mode` + +This true/false setting controls whether or not the new top container mode is active for the given repository. This mode has two effects: + +- Only top containers associated with the current record are requestable. If no top containers are associated with the current record, then the request button will be replaced by a message (see :no_containers_message setting below). + +- When the user clicks the Aeon Request button, they will be taken to the new Aeon Box-Picker form to submit their request(s). + +If this setting is true, then the :requests_permitted_for_containers_only should also be set to true. + +#### `:disallowed_record_level_message` + +This is the message that will be displayed instead of the Aeon Request button if the current record cannot be requested due to the values listed in the :requestable_archival_record_levels setting. If no value is provided, the default value will be "Not requestable". The message should be kept short (30 characters or less) for best appearance. + + +#### `:no_containers_message` + +This is the message that will be displayed instead of the Aeon Request button if the current record has no associated topcontainers and :top_container_mode is active. If no value is provided, the default value will be "No requestable containers". The message should be kept short (30 characters or less) for best appearance. + +#### `:restrictions_message` + +This is the message that will be displayed instead of the Aeon Request button if the current record cannot be requested because it has active restrictions matching the values in the :hide_button_for_access_restriction_types setting. If no value is provided, the default value will be "Access Restricted". The message should be kept short (30 characters or less) for best appearance. + + #### `:user_defined_fields` This setting allows sites to specify which user defined fields are imported. @@ -513,6 +550,9 @@ records. - `repo_code` - `repo_name` - `language` + - semi-colon (`;`) separated string list + - contains the content from the `language` elements listed in `lang_materials` + - for accessions, contains the single value in the `language` element - `restrictions_apply` (true/false value) - `display_string` - `creators` @@ -535,6 +575,18 @@ records. of values that could appear in place of the `{date_label}` placeholder is controlled by the `date_label` enumeration of your ArchivesSpace installation. +- `date_expression` + - semi-colon (`;`) separated string list + - contains the combined final_expressions of the single and inclusive dates associated with the record. +- `userestrict` + - semi-colon (`;`) separated string list + - contains the combined contents of all published userestrict notes associated with the record. +- `rights_type` + - semi-colon (`;`) separated string list + - contains the combined rights_type values of all rights statements associated with the record. +- `digital_objects` + - semi-colon (`;`) separated string list + - contains the IDs of all digital objects associated with the record. The following fields are common to both Accession records and Archival Object records, but are based on the number of instances associated with the record. @@ -567,6 +619,10 @@ and the values of each may differ from instance to instance. - `instance_top_container_collection_display_string` (semi-colon (`;`) separated string list) - `instance_top_container_series_identifer` (semi-colon (`;`) separated string list) - `instance_top_container_series_display_string` (semi-colon (`;`) separated string list) +- `instance_top_container_location_note` +- `instance_top_container_location_title` +- `instance_top_container_location_id` +- `instance_top_container_location_building` ### Archival Object Fields diff --git a/public/models/aeon_accession_mapper.rb b/public/models/aeon_accession_mapper.rb index 1711f80..8c0bc75 100644 --- a/public/models/aeon_accession_mapper.rb +++ b/public/models/aeon_accession_mapper.rb @@ -17,6 +17,11 @@ def json_fields .reject {|id_comp| id_comp.blank?} .join('-') + language = json['language'] + if language + mappings['language'] = language + end + mappings end @@ -32,8 +37,6 @@ def record_fields mappings['access_restrictions_note'] = record.access_restrictions_note end - mappings['language'] = self.record['language'] - mappings end end diff --git a/public/models/aeon_archival_object_mapper.rb b/public/models/aeon_archival_object_mapper.rb index 19c74ad..07800e8 100644 --- a/public/models/aeon_archival_object_mapper.rb +++ b/public/models/aeon_archival_object_mapper.rb @@ -6,11 +6,6 @@ def initialize(archival_object) super(archival_object) end - # Override of #show_action? from AeonRecordMapper - def show_action? - return false if !super - return self.requestable_based_on_archival_record_level? - end # Override for AeonRecordMapper json_fields method. def json_fields @@ -25,6 +20,8 @@ def json_fields mappings['repository_processing_note'] = json['repository_processing_note'] end + mappings['restrictions_apply'] = restrictions_apply? + mappings end @@ -36,4 +33,24 @@ def record_fields mappings end + + def restrictions_apply? + + if self.record.json['restrictions_apply'] + return true + end + + self.record['ancestors'].each do |ancestor| + Rails.logger.info("Aeon Fulfillment Plugin") { "Logging ancestor #{ancestor}" } + ancestor_record = archivesspace.get_record(ancestor) + Rails.logger.info("Aeon Fulfillment Plugin") { ancestor_record.to_yaml } + + + + if (ancestor_record.json['restrictions_apply'] == true or ancestor_record.json['restrictions'] == true) + return true + end + end + return false + end end diff --git a/public/models/aeon_record_mapper.rb b/public/models/aeon_record_mapper.rb index 9c82f88..d335b6f 100644 --- a/public/models/aeon_record_mapper.rb +++ b/public/models/aeon_record_mapper.rb @@ -11,8 +11,10 @@ def initialize(record) @container_instances = find_container_instances(record['json'] || {}) end + ExtendedRequestClient.init + def archivesspace - ArchivesSpaceClient.instance + ExtendedRequestClient.instance end def self.register_for_record_type(type) @@ -79,29 +81,100 @@ def user_defined_fields mappings end + def unrequestable_display_message + if !(self.repo_settings) + return ""; + end + + if !self.requestable_based_on_archival_record_level? + if (message = self.repo_settings[:disallowed_record_level_message]) + return message + else + return "Not requestable" + end + elsif !self.record_has_top_containers? + if (message = self.repo_settings[:no_containers_message]) + return message + else + return "No requestable containers" + end + elsif self.record_has_restrictions? + if (message = self.repo_settings[:restrictions_message]) + return message + else + return "Access restricted" + end + end + return "" + end + + def configured? + return true if self.repo_settings + end + # This method tests whether the button should be hidden. This determination is based # on the settings for the repository and defaults to false. def hide_button? # returning false to maintain the original behavior return false unless self.repo_settings - return true if self.repo_settings[:hide_request_button] - return true if self.repo_settings[:hide_button_for_accessions] && record.is_a?(Accession) + if self.repo_settings[:hide_request_button] + return true + elsif (self.repo_settings[:hide_button_for_accessions] == true && record.is_a?(Accession)) + return true + elsif self.requestable_based_on_archival_record_level? == false + return true + elsif self.record_has_top_containers? == false + return true + elsif self.record_has_restrictions? == true + return true + end + return false + end + def record_has_top_containers? + return record.is_a?(Container) || self.container_instances.any? + end + + def record_has_restrictions? if (types = self.repo_settings[:hide_button_for_access_restriction_types]) - notes = (record.json['notes'] || []).select {|n| n['type'] == 'accessrestrict' && n.has_key?('rights_restriction')} - .map {|n| n['rights_restriction']['local_access_restriction_type']} - .flatten.uniq + notes = (record.json['notes'] || []).select {|n| n['type'] == 'accessrestrict' && n.has_key?('rights_restriction')} + .map {|n| n['rights_restriction']['local_access_restriction_type']} + .flatten.uniq + + # hide if the record notes have any of the restriction types listed in config + access_restrictions = true if (notes - types).length < notes.length + + # check each top container for restrictions + # if all of them are unrequestable, we should hide the request button for this record + has_requestable_container = false + if (instances = self.container_instances) + instances.each do |instance| + if (container = instance['sub_container']) + if (top_container = container['top_container']) + if (top_container_resolved = top_container['_resolved']) + tc_has_restrictions = (top_container_resolved['active_restrictions'] || []) + .map{ |ar| ar['local_access_restriction_type'] } + .flatten.uniq + .select{ |ar| types.include?(ar)} + .any? + if tc_has_restrictions == false + has_requestable_container = true + end + end + end + end + end + end - # hide if the record notes have any of the restriction types listed in config - return true if (notes - types).length < notes.length + return access_restrictions || !has_requestable_container end - false + return false end # Determines if the :requestable_archival_record_levels setting is present - # and exlcudes the 'level' property of the current record. This method is + # and excludes the 'level' property of the current record. This method is # not used by this class, because not all implementations of "abstract_archival_object" # have a "level" property that uses the "archival_record_level" enumeration. def requestable_based_on_archival_record_level? @@ -141,40 +214,6 @@ def requestable_based_on_archival_record_level? true end - # If #show_action? returns false, then the button is shown disabled - def show_action? - begin - Rails.logger.debug("Aeon Fulfillment Plugin") { "Checking for plugin settings for the repository" } - - if !self.repo_settings - Rails.logger.info("Aeon Fulfillment Plugin") { "Could not find plugin settings for the repository: \"#{self.repo_code}\"." } - else - Rails.logger.debug("Aeon Fulfillment Plugin") { "Checking for top containers" } - - has_top_container = record.is_a?(Container) || self.container_instances.any? - - only_top_containers = self.repo_settings[:requests_permitted_for_containers_only] || false - - # if we're showing the button for accessions, and this is an accession, - # then don't require containers - only_top_containers = self.repo_settings.fetch(:hide_button_for_accessions, false) if record.is_a?(Accession) - - Rails.logger.debug("Aeon Fulfillment Plugin") { "Containers found? #{has_top_container}" } - Rails.logger.debug("Aeon Fulfillment Plugin") { "only_top_containers? #{only_top_containers}" } - - return (has_top_container || !only_top_containers) - end - - rescue Exception => e - Rails.logger.error("Aeon Fulfillment Plugin") { "Failed to create Aeon Request action." } - Rails.logger.error(e.message) - Rails.logger.error(e.backtrace.inspect) - - end - - false - end - # Pulls data from the contained record def map @@ -262,13 +301,28 @@ def record_fields mappings['repo_name'] = resolved_repository['name'] end - if record['creators'] + if self.record['creators'] mappings['creators'] = self.record['creators'] .select { |cr| cr.present? } .map { |cr| cr.strip } .join("; ") end + if self.record.dates + mappings['date_expression'] = self.record.dates + .select{ |date| date['date_type'] == 'single' or date['date_type'] == 'inclusive'} + .map{ |date| date['final_expression'] } + .join(';') + end + + if (self.record.notes['userestrict']) + mappings['userestrict'] = self.record.notes['userestrict'] + .map { |note| note['subnotes'] }.flatten + .select { |subnote| subnote['content'].present? and subnote['publish'] == true } + .map { |subnote| subnote['content'] }.flatten + .join("; ") + end + mappings end @@ -281,14 +335,25 @@ def json_fields json = self.record.json return mappings unless json - Rails.logger.debug("Aeon Fulfillment Plugin") { "Mapping Record JSON: #{json}" } + lang_materials = json['lang_materials'] + if lang_materials + mappings['language'] = lang_materials + .select { |lm| lm['language_and_script'].present? and lm['language_and_script']['language'].present?} + .map{ |lm| lm['language_and_script']['language'] } + .flatten + .join(";") + end + + language = json['language'] + if language + mappings['language'] = language + end - mappings['language'] = json['language'] notes = json['notes'] if notes mappings['physical_location_note'] = notes - .select { |note| note['type'] == 'physloc' and note['content'].present? } + .select { |note| note['type'] == 'physloc' and note['content'].present? and note['publish'] == true } .map { |note| note['content'] } .flatten .join("; ") @@ -297,7 +362,7 @@ def json_fields .select { |note| note['type'] == 'accessrestrict' and note['subnotes'] } .map { |note| note['subnotes'] } .flatten - .select { |subnote| subnote['content'].present? } + .select { |subnote| subnote['content'].present? and subnote['publish'] == true} .map { |subnote| subnote['content'] } .flatten .join("; ") @@ -314,6 +379,25 @@ def json_fields } end + + if json['linked_agents'] + mappings['creators'] = json['linked_agents'] + .select { |l| l['role'] == 'creator' and l['_resolved'] } + .map { |l| l['_resolved']['names'] }.flatten + .select { |n| n['is_display_name'] == true} + .map { |n| n['sort_name']} + .join("; ") + end + + if json['rights_statements'] + mappings['rights_type'] = json['rights_statements'].map{ |r| r['rights_type']}.uniq.join(';') + end + + digital_instances = json['instances'].select { |instance| instance['instance_type'] == 'digital_object'} + if (digital_instances.any?) + mappings["digital_objects"] = digital_instances.map{|d| d['digital_object']['ref']}.join(';') + end + mappings['restrictions_apply'] = json['restrictions_apply'] mappings['display_string'] = json['display_string'] @@ -324,7 +408,7 @@ def json_fields .each_with_index .map { |instance, i| request = {} - + instance_count = i + 1 request['Request'] = "#{instance_count}" @@ -362,6 +446,24 @@ def json_fields request["instance_top_container_type_#{instance_count}"] = top_container_resolved['type'] request["instance_top_container_uri_#{instance_count}"] = top_container_resolved['uri'] + if (top_container_resolved['container_locations']) + request["instance_top_container_location_note_#{instance_count}"] = top_container_resolved['container_locations'].map{ |l| l['note']}.join{';'} + end + + request["requestable_#{instance_count}"] = (top_container_resolved['active_restrictions'] || []) + .map{ |ar| ar['local_access_restriction_type'] } + .flatten.uniq + .select{ |ar| (self.repo_settings[:hide_button_for_access_restriction_types] || []).include?(ar)} + .empty? + + locations = top_container_resolved["container_locations"] + if locations.any? + location_id = locations.sort_by { |l| l["start_date"]}.last()["ref"] + location = archivesspace.get_location(location_id) + request["instance_top_container_location_#{instance_count}"] = location['title'] + request["instance_top_container_location_id_#{instance_count}"] = location_id + request["instance_top_container_location_building_#{instance_count}"] = location['building'] + end collection = top_container_resolved['collection'] if collection @@ -401,7 +503,7 @@ def json_fields # method will recurse up the record's resource tree, until it finds a record that does # have top container instances, and will pull the list of instances from there. def find_container_instances (record_json) - + current_uri = record_json['uri'] Rails.logger.info("Aeon Fulfillment Plugin") { "Checking \"#{current_uri}\" for Top Container instances..." } @@ -415,21 +517,25 @@ def find_container_instances (record_json) return instances end - parent_uri = '' - - if record_json['parent'].present? - parent_uri = record_json['parent']['ref'] - parent_uri = record_json['parent'] unless parent_uri.present? - elsif record_json['resource'].present? - parent_uri = record_json['resource']['ref'] - parent_uri = record_json['resource'] unless parent_uri.present? - end + # If we're in top container mode, we can skip this step, + # since we only want to present containers associated with the current record. + if (!self.repo_settings[:top_container_mode]) + parent_uri = '' + + if record_json['parent'].present? + parent_uri = record_json['parent']['ref'] + parent_uri = record_json['parent'] unless parent_uri.present? + elsif record_json['resource'].present? + parent_uri = record_json['resource']['ref'] + parent_uri = record_json['resource'] unless parent_uri.present? + end - if parent_uri.present? - Rails.logger.debug("Aeon Fulfillment Plugin") { "No Top Container instances found. Checking parent. (#{parent_uri})" } - parent = archivesspace.get_record(parent_uri) - parent_json = parent['json'] - return find_container_instances(parent_json) + if parent_uri.present? + Rails.logger.debug("Aeon Fulfillment Plugin") { "No Top Container instances found. Checking parent. (#{parent_uri})" } + parent = archivesspace.get_record(parent_uri) + parent_json = parent['json'] + return find_container_instances(parent_json) + end end Rails.logger.debug("Aeon Fulfillment Plugin") { "No Top Container instances found." } diff --git a/public/models/aeon_resource_mapper.rb b/public/models/aeon_resource_mapper.rb index 64038b1..dfd2a4a 100644 --- a/public/models/aeon_resource_mapper.rb +++ b/public/models/aeon_resource_mapper.rb @@ -8,13 +8,6 @@ def initialize(resource) super(resource) end - # Override of #show_action? from AeonRecordMapper - def show_action? - return false if !super - - self.requestable_based_on_archival_record_level? - end - # Override for AeonRecordMapper json_fields method. def json_fields mappings = super @@ -48,6 +41,7 @@ def json_fields mappings['finding_aid_series_statement'] = json['finding_aid_series_statement'] mappings['finding_aid_status'] = json['finding_aid_status'] mappings['finding_aid_note'] = json['finding_aid_note'] + mappings['restrictions_apply'] = json['restrictions'] mappings end diff --git a/public/models/extended_request_client.rb b/public/models/extended_request_client.rb new file mode 100644 index 0000000..c288822 --- /dev/null +++ b/public/models/extended_request_client.rb @@ -0,0 +1,7 @@ +class ExtendedRequestClient < ArchivesSpaceClient + def get_location(location_id) + url = build_url("#{location_id}") + results = do_search(url, true) + results + end +end \ No newline at end of file diff --git a/public/views/aeon/_aeon_request_action.html.erb b/public/views/aeon/_aeon_request_action.html.erb index 8972af6..3ccc5df 100644 --- a/public/views/aeon/_aeon_request_action.html.erb +++ b/public/views/aeon/_aeon_request_action.html.erb @@ -4,12 +4,17 @@ Rails.logger.info("Aeon Fulfillment Plugin") { "Initializing Plugin..." } mapper = AeonRecordMapper.mapper_for(record) %> -<%= javascript_include_tag "#{@base_url}/assets/js/aeon_request_action.js" %> +<% if mapper.configured? %> -<% unless mapper.hide_button? %> + <%= javascript_include_tag "#{@base_url}/assets/js/aeon_request_action.js" %> - <% if mapper.show_action? %> - <%= form_tag "#{mapper.repo_settings[:aeon_web_url]}?action=11&type=200", :id => 'aeon_request_sub', :target => (mapper.repo_settings.fetch(:request_in_new_tab, false) ? 'aeon_request' : '_self') do |f| %> + <% if mapper.hide_button? %> +
+ <%= mapper.unrequestable_display_message %> +
+ <% else %> + + <%= form_tag "#{mapper.repo_settings[:aeon_web_url]}?#{mapper.repo_settings.fetch(:top_container_mode, false) ? "action=10&form=35" : "action=11&type=200"}", :id => 'aeon_request_sub', :target => (mapper.repo_settings.fetch(:request_in_new_tab, false) ? 'aeon_request' : '_self') do |f| %> <% mapper.map.each do |name, value| %> <% if name.casecmp('requests').zero? %> <% value.each do |request| %> @@ -27,17 +32,11 @@ mapper = AeonRecordMapper.mapper_for(record) <% end %> - <% else %> - -
- -
- <% end %> - +<% else %> +<% + Rails.logger.info("Aeon Fulfillment Plugin") { "Plugin not configured." } +%> <% end %> <%