diff --git a/app/helpers/blacklight/facets_helper_behavior.rb b/app/helpers/blacklight/facets_helper_behavior.rb deleted file mode 100644 index 8268634af..000000000 --- a/app/helpers/blacklight/facets_helper_behavior.rb +++ /dev/null @@ -1,250 +0,0 @@ -# [hyc-override] Overriding to transform date facets from EDTF to human readable strings -# frozen_string_literal: true - -module Blacklight::FacetsHelperBehavior - include Blacklight::Facet - - ## - # Check if any of the given fields have values - # - # @param [Array] fields - # @param [Hash] options - # @return [Boolean] - def has_facet_values?(fields = facet_field_names, options = {}) - facets_from_request(fields).any? { |display_facet| !display_facet.items.empty? && should_render_facet?(display_facet) } - end - - ## - # Render a collection of facet fields. - # @see #render_facet_limit - # - # @param [Array] fields - # @param [Hash] options - # @return String - def render_facet_partials(fields = facet_field_names, options = {}) - safe_join(facets_from_request(fields).map do |display_facet| - render_facet_limit(display_facet, options) - end.compact, "\n") - end - - ## - # Renders a single section for facet limit with a specified - # solr field used for faceting. Can be over-ridden for custom - # display on a per-facet basis. - # - # @param [Blacklight::Solr::Response::Facets::FacetField] display_facet - # @param [Hash] options parameters to use for rendering the facet limit partial - # @option options [String] :partial partial to render - # @option options [String] :layout partial layout to render - # @option options [Hash] :locals locals to pass to the partial - # @return [String] - def render_facet_limit(display_facet, options = {}) - return unless should_render_facet?(display_facet) - - options = options.dup - options[:partial] ||= facet_partial_name(display_facet) - options[:layout] ||= 'facet_layout' unless options.key?(:layout) - options[:locals] ||= {} - options[:locals][:field_name] ||= display_facet.name - options[:locals][:solr_field] ||= display_facet.name # deprecated - options[:locals][:facet_field] ||= facet_configuration_for_field(display_facet.name) - options[:locals][:display_facet] ||= display_facet - - render(options) - end - - ## - # Renders the list of values - # removes any elements where render_facet_item returns a nil value. This enables an application - # to filter undesireable facet items so they don't appear in the UI - def render_facet_limit_list(paginator, facet_field, wrapping_element = :li) - safe_join(paginator.items.map { |item| render_facet_item(facet_field, item) }.compact.map { |item| content_tag(wrapping_element, item) }) - end - - ## - # Renders a single facet item - def render_facet_item(facet_field, item) - if facet_in_params?(facet_field, item.value) - render_selected_facet_value(facet_field, item) - else - render_facet_value(facet_field, item) - end - end - - ## - # Determine if Blacklight should render the display_facet or not - # - # By default, only render facets with items. - # - # @param [Blacklight::Solr::Response::Facets::FacetField] display_facet - # @return [Boolean] - def should_render_facet?(display_facet) - # display when show is nil or true - facet_config = facet_configuration_for_field(display_facet.name) - display = should_render_field?(facet_config, display_facet) - # render facet if it has items in it or it is being filtered to results missing any value - display && (display_facet.items.present? || selected_missing_for_range_limit?(display_facet.name)) - end - - ## - # Determine whether a facet should be rendered as collapsed or not. - # - if the facet is 'active', don't collapse - # - if the facet is configured to collapse (the default), collapse - # - if the facet is configured not to collapse, don't collapse - # - # @param [Blacklight::Configuration::FacetField] facet_field - # @return [Boolean] - def should_collapse_facet?(facet_field) - !facet_field_in_params?(facet_field.key) && facet_field.collapse - end - - ## - # The name of the partial to use to render a facet field. - # uses the value of the "partial" field if set in the facet configuration - # otherwise uses "facet_pivot" if this facet is a pivot facet - # defaults to 'facet_limit' - # - # @return [String] - def facet_partial_name(display_facet = nil) - config = facet_configuration_for_field(display_facet.name) - name = config.try(:partial) - name ||= 'facet_pivot' if config.pivot - name ||= 'facet_limit' - end - - ## - # Standard display of a facet value in a list. Used in both _facets sidebar - # partial and catalog/facet expanded list. Will output facet value name as - # a link to add that to your restrictions, with count in parens. - # - # @param [Blacklight::Solr::Response::Facets::FacetField] facet_field - # @param [Blacklight::Solr::Response::Facets::FacetItem] item - # @param [Hash] options - # @option options [Boolean] :suppress_link display the facet, but don't link to it - # @return [String] - def render_facet_value(facet_field, item, options = {}) - path = path_for_facet(facet_field, item) - content_tag(:span, class: 'facet-label') do - link_to_unless(options[:suppress_link], facet_display_value(facet_field, item), path, class: 'facet_select') - end + render_facet_count(item.hits) - end - - ## - # Where should this facet link to? - # @param [Blacklight::Solr::Response::Facets::FacetField] facet_field - # @param [String] item - # @return [String] - def path_for_facet(facet_field, item) - facet_config = facet_configuration_for_field(facet_field) - if facet_config.url_method - send(facet_config.url_method, facet_field, item) - else - search_action_path(search_state.add_facet_params_and_redirect(facet_field, item)) - end - end - - ## - # Standard display of a SELECTED facet value (e.g. without a link and with a remove button) - # @see #render_facet_value - # @param [Blacklight::Solr::Response::Facets::FacetField] facet_field - # @param [String] item - def render_selected_facet_value(facet_field, item) - remove_href = search_action_path(search_state.remove_facet_params(facet_field, item)) - content_tag(:span, class: 'facet-label') do - content_tag(:span, facet_display_value(facet_field, item), class: 'selected') + - # remove link - link_to(remove_href, class: 'remove') do - content_tag(:span, '', class: 'glyphicon glyphicon-remove') + - content_tag(:span, '[remove]', class: 'sr-only') - end - end + render_facet_count(item.hits, classes: ['selected']) - end - - ## - # Renders a count value for facet limits. Can be over-ridden locally - # to change style. And can be called by plugins to get consistent display. - # - # @param [Integer] num number of facet results - # @param [Hash] options - # @option options [Array] an array of classes to add to count span. - # @return [String] - def render_facet_count(num, options = {}) - classes = (options[:classes] || []) << 'facet-count' - content_tag('span', t('blacklight.search.facets.count', number: number_with_delimiter(num)), class: classes) - end - - ## - # Are any facet restrictions for a field in the query parameters? - # - # @param [String] field - # @return [Boolean] - def facet_field_in_params?(field) - !facet_params(field).blank? - end - - ## - # Check if the query parameters have the given facet field with the - # given value. - # - # @param [Object] field - # @param [Object] item facet value - # @return [Boolean] - def facet_in_params?(field, item) - value = facet_value_for_facet_item(item) - - (facet_params(field) || []).include? value - end - - ## - # Get the values of the facet set in the blacklight query string - def facet_params(field) - config = facet_configuration_for_field(field) - - params[:f][config.key] if params[:f] - end - - ## - # Get the displayable version of a facet's value - # - # @param [Object] field - # @param [String] item value - # @return [String] - def facet_display_value(field, item) - facet_config = facet_configuration_for_field(field) - - value = if item.respond_to? :label - item.label - else - facet_value_for_facet_item(item) - end - - # [hyc-override] Overriding to transform date facets from EDTF to human readable strings - value = Hyc::EdtfConvert.convert_from_edtf(value) if field == 'date_issued_sim' || field == 'date_created_sim' - - if facet_config.helper_method - send facet_config.helper_method, value - elsif facet_config.query && facet_config.query[value] - facet_config.query[value][:label] - elsif facet_config.date - localization_options = facet_config.date == true ? {} : facet_config.date - - l(value.to_datetime, localization_options) - else - value - end - end - - def facet_field_id(facet_field) - "facet-#{facet_field.key.parameterize}" - end - - private - - def facet_value_for_facet_item(item) - if item.respond_to? :value - item.value - else - item - end - end -end diff --git a/app/models/concerns/blacklight/document/dublin_core.rb b/app/overrides/models/concerns/blacklight/document/dublin_core_override.rb similarity index 77% rename from app/models/concerns/blacklight/document/dublin_core.rb rename to app/overrides/models/concerns/blacklight/document/dublin_core_override.rb index 48e694821..c80f4ab17 100644 --- a/app/models/concerns/blacklight/document/dublin_core.rb +++ b/app/overrides/models/concerns/blacklight/document/dublin_core_override.rb @@ -1,24 +1,9 @@ -# [hyc-override] override file from blacklight gem # frozen_string_literal: true - -require 'builder' - -# This module provides Dublin Core export based on the document's semantic values -module Blacklight::Document::DublinCore +# [hyc-override] https://github.com/projectblacklight/blacklight/tree/v7.33.1/app/models/concerns/blacklight/document/dublin_core.rb +Blacklight::Document::DublinCore.module_eval do include HycHelper - def self.extended(document) - # Register our exportable formats - Blacklight::Document::DublinCore.register_export_formats(document) - end - - def self.register_export_formats(document) - document.will_export_as(:xml) - document.will_export_as(:dc_xml, 'text/xml') - document.will_export_as(:oai_dc_xml, 'text/xml') - end - - # added thumbnail as separate field to help with ordering + # [hyc-override] added thumbnail as separate field to help with ordering def dublin_core_field_names [:contributor, :coverage, :creator, :date, :description, :format, :identifier, :language, :publisher, :relation, :rights, :source, :subject, :title, :type, :thumbnail] @@ -80,18 +65,9 @@ def export_as_oai_dc_xml xml.target! end - alias_method :export_as_xml, :export_as_oai_dc_xml - alias_method :export_as_dc_xml, :export_as_oai_dc_xml - - # Used by ruby-oai gem to determine if a status=deleted header should be added. + # [hyc-override] Used by ruby-oai gem to determine if a status=deleted header should be added. # See OAI::Provider::Response::RecordResponse def deleted? fetch('workflow_state_name_ssim', nil)&.include?('withdrawn') end - - private - - def dublin_core_field_name?(field) - dublin_core_field_names.include? field.to_sym - end end diff --git a/lib/blacklight_oai_provider/solr_document_wrapper.rb b/lib/blacklight_oai_provider/solr_document_wrapper.rb deleted file mode 100644 index 3e04ae94d..000000000 --- a/lib/blacklight_oai_provider/solr_document_wrapper.rb +++ /dev/null @@ -1,96 +0,0 @@ -# frozen_string_literal: true -# [hyc-override] Using single item search builder when finding object by identifer -module BlacklightOaiProvider - class SolrDocumentWrapper < ::OAI::Provider::Model - attr_reader :document_model, :timestamp_field, :solr_timestamp, :limit - - def initialize(controller, options = {}) - @controller = controller - @document_model = @controller.blacklight_config.document_model - @solr_timestamp = document_model.timestamp_key - @timestamp_field = 'timestamp' # method name used by ruby-oai - @limit = options[:limit] || 15 - @set = options[:set_model] || BlacklightOaiProvider::SolrSet - - @set.controller = @controller - @set.fields = options[:set_fields] - end - - def sets - @set.all - end - - def earliest - builder = @controller.search_builder.merge(fl: solr_timestamp, sort: "#{solr_timestamp} asc", rows: 1) - response = @controller.repository.search(builder) - response.documents.first.timestamp - end - - def latest - builder = @controller.search_builder.merge(fl: solr_timestamp, sort: "#{solr_timestamp} desc", rows: 1) - response = @controller.repository.search(builder) - response.documents.first.timestamp - end - - def find(selector, options = {}) - return next_set(options[:resumption_token]) if options[:resumption_token] - - if selector == :all - response = @controller.repository.search(conditions(options)) - - return select_partial(BlacklightOaiProvider::ResumptionToken.new(options.merge(last: 0), nil, response.total)) if limit && response.total > limit - - response.documents - else - # [hyc-override] using search builder in order to allow for access controls to be applied - query = @controller.single_item_search_builder(selector).query - Rails.logger.debug("Finding a single result #{selector} #{query}") - @controller.repository.search(query).documents.first - end - end - - def select_partial(token) - records = @controller.repository.search(token_conditions(token)).documents - - raise ::OAI::ResumptionTokenException unless records - - OAI::Provider::PartialResult.new(records, token.next(token.last + limit)) - end - - def next_set(token_string) - raise ::OAI::ResumptionTokenException unless limit - - token = BlacklightOaiProvider::ResumptionToken.parse(token_string) - select_partial(token) - end - - private - - def token_conditions(token) - conditions(token.to_conditions_hash).merge(start: token.last) - end - - def conditions(options) # conditions/query derived from options - query = @controller.search_builder.merge(sort: "#{solr_timestamp} asc", rows: limit).query - - if options[:from].present? || options[:until].present? - query.append_filter_query( - "#{solr_timestamp}:[#{solr_date(options[:from])} TO #{solr_date(options[:until]).gsub('Z', '.999Z')}]" - ) - end - - query.append_filter_query(@set.from_spec(options[:set])) if options[:set].present? - query - end - - def solr_date(time) - if time.respond_to?(:xmlschema) - time.utc.xmlschema # Force UTC. - elsif time.blank? - '*' - else - time.to_s - end - end - end -end diff --git a/spec/helpers/blacklight/facets_helper_behavior_spec.rb b/spec/helpers/blacklight/facets_helper_behavior_spec.rb deleted file mode 100644 index 33f7738a7..000000000 --- a/spec/helpers/blacklight/facets_helper_behavior_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true -require 'rails_helper' - -RSpec.describe Blacklight::FacetsHelperBehavior do - # only testing overridden method - describe '#facet_display_value' do - it 'is the facet value for an ordinary facet' do - allow(helper).to receive(:facet_configuration_for_field).with('simple_field').and_return(double(query: nil, date: nil, helper_method: nil, url_method: nil)) - expect(helper.facet_display_value('simple_field', 'asdf')).to eq 'asdf' - end - - it 'extracts the configuration label for a query facet' do - allow(helper).to receive(:facet_configuration_for_field).with('query_facet').and_return(double(query: { 'query_key' => { label: 'XYZ' } }, date: nil, helper_method: nil, url_method: nil)) - expect(helper.facet_display_value('query_facet', 'query_key')).to eq 'XYZ' - end - - it 'localizes the label for date-type facets' do - allow(helper).to receive(:facet_configuration_for_field).with('date_facet').and_return(double('date' => true, :query => nil, :helper_method => nil, :url_method => nil)) - expect(helper.facet_display_value('date_facet', '2012-01-01')).to eq 'Sun, 01 Jan 2012 00:00:00 +0000' - end - - it 'humanizes the label for date_issued_sim facet' do - # we do not flag date_issued as date type in blacklight config - allow(helper).to receive(:facet_configuration_for_field).with('date_issued_sim').and_return(double('date' => false, :query => nil, :helper_method => nil, :url_method => nil)) - expect(helper.facet_display_value('date_issued_sim', '2012-01-01')).to eq 'January 1, 2012' - end - - it 'humanizes the label for date_created_sim facet' do - # we do not flag date_created as date type in blacklight config - allow(helper).to receive(:facet_configuration_for_field).with('date_created_sim').and_return(double('date' => false, :query => nil, :helper_method => nil, :url_method => nil)) - expect(helper.facet_display_value('date_created_sim', '2012-01-01')).to eq 'January 1, 2012' - end - - it 'localizes the label for date-type facets with the supplied localization options' do - allow(helper).to receive(:facet_configuration_for_field).with('date_facet').and_return(double('date' => { format: :short }, :query => nil, :helper_method => nil, :url_method => nil)) - expect(helper.facet_display_value('date_facet', '2012-01-01')).to eq '01 Jan 00:00' - end - end -end diff --git a/spec/models/concerns/blacklight/document/dublin_core_spec.rb b/spec/models/concerns/blacklight/document/dublin_core_spec.rb new file mode 100644 index 000000000..8a1a1c8fc --- /dev/null +++ b/spec/models/concerns/blacklight/document/dublin_core_spec.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true +require 'rails_helper' + +RSpec.describe 'Blacklight::Document::DublinCore' do + let(:work_solr) { work.to_solr } + let(:document) { SolrDocument.new(work_solr) } + + describe '#dublin_core_field_names' do + let(:work) { General.new } + + it 'list of fields, including added thumbnail field' do + expect(document.dublin_core_field_names).to eq [:contributor, :coverage, :creator, :date, :description, :format, :identifier, :language, :publisher, :relation, + :rights, :source, :subject, :title, :type, :thumbnail] + end + end + + describe '#deleted?' do + let(:work) { Article.new(id: '123456', title: ['654321']) } + + context 'with work in deposited state' do + it 'returns false' do + allow(document).to receive(:fetch).with('workflow_state_name_ssim', nil).and_return('deposited') + expect(document.deleted?).to be_falsey + end + end + + context 'with work in withdrawn state' do + it 'returns true' do + allow(document).to receive(:fetch).with('workflow_state_name_ssim', nil).and_return('withdrawn') + expect(document.deleted?).to be_truthy + end + end + end + + describe '#export_as_oai_dc_xml' do + context 'with creator people objects' do + let(:work) { Article.new(id: '123456', title: ['654321'], + creators_attributes: { '0' => { name: 'User4, A', + other_affiliation: 'Oregon Health & Science University', + index: 4 }, + '1' => { name: 'User1, B', + affiliation: 'Carolina Center for Genome Sciences', + index: 1 }, + '2' => { name: 'User3, C', + index: 3 }, + '3' => { name: 'User0, D', + index: 2 } } + ) + } + + it 'returns xml document with creator objects in index order, and affiliation' do + xml_doc = Nokogiri::XML(document.export_as_oai_dc_xml) + puts "Result: #{document.export_as_oai_dc_xml}" + creators = xml_doc.xpath('//dc:creator', 'dc' => 'http://purl.org/dc/elements/1.1/').map(&:text) + expect(creators).to eq ['User1, B', 'User0, D', 'User3, C', 'User4, A'] + # Affiliations are captured as dc:contributor fields for creators + affiliations = xml_doc.xpath('//dc:contributor', 'dc' => 'http://purl.org/dc/elements/1.1/').map(&:text) + expect(affiliations).to eq ['School of Medicine, Carolina Center for Genome Sciences'] + end + end + + context 'with contributor people objects' do + let(:work) { Article.new(id: '123456', title: ['654321'], + translators_attributes: { '0' => { name: 'User4, A', + other_affiliation: 'Oregon Health & Science University', + index: 2 }, + '1' => { name: 'User1, B', + affiliation: 'Eshelman School of Pharmacy, Division of Pharmaceutical Outcomes and Policy', + index: 1 } } + ) + } + + it 'returns xml document with contributors in index order' do + xml_doc = Nokogiri::XML(document.export_as_oai_dc_xml) + contributors = xml_doc.xpath('//dc:contributor', 'dc' => 'http://purl.org/dc/elements/1.1/').map(&:text) + # No affiliation is captured under dc:contributor for people objects that map to dc:contributor + expect(contributors).to eq ['User1, B', 'User4, A'] + end + end + + context 'with source values' do + let(:work) { Article.new(id: '123456', title: ['654321'], + journal_issue: '11', + journal_title: 'International Journal of Health Policy and Management', + journal_volume: '5' + + ) + } + + it 'returns xml document with journal fields formatted as dc:source' do + xml_doc = Nokogiri::XML(document.export_as_oai_dc_xml) + sources = xml_doc.xpath('//dc:source', 'dc' => 'http://purl.org/dc/elements/1.1/').map(&:text) + expect(sources).to eq ['International Journal of Health Policy and Management, 5(11)'] + end + end + + context 'with thumbnail' do + let(:work) { Article.new(id: '123456', title: ['654321']) } + before do + work_solr['thumbnail_path_ss'] = '/downloads/123456?file=thumbnail' + end + + it 'returns record, thumbnail and download urls as identifiers' do + xml_doc = Nokogiri::XML(document.export_as_oai_dc_xml) + identifiers = xml_doc.xpath('//dc:identifier', 'dc' => 'http://purl.org/dc/elements/1.1/').map(&:text) + # Contains an identifier to the resource itself in addition to the thumb/download links + expect(identifiers).to eq [ + 'http://localhost:3000/concern/articles/123456', + 'http://localhost:3000/downloads/123456?file=thumbnail', + 'http://localhost:3000/downloads/123456'] + end + end + end +end