iiif_print 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +18 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +16 -0
  4. data/.github/workflows/build-lint-test-action.yaml +4 -5
  5. data/.gitignore +5 -4
  6. data/.rubocop.yml +1 -0
  7. data/.solargraph.yml +19 -0
  8. data/Gemfile.lock +1025 -0
  9. data/README.md +102 -9
  10. data/Rakefile +6 -0
  11. data/app/actors/iiif_print/actors/cleanup_file_sets_actor_decorator.rb +24 -0
  12. data/app/actors/iiif_print/actors/file_set_actor_decorator.rb +30 -28
  13. data/app/controllers/iiif_print/split_pdfs_controller.rb +38 -0
  14. data/app/helpers/iiif_print/iiif_helper_decorator.rb +32 -0
  15. data/app/helpers/iiif_print/iiif_print_helper_behavior.rb +23 -0
  16. data/app/helpers/iiif_print_helper.rb +0 -20
  17. data/app/indexers/concerns/iiif_print/child_work_indexer.rb +27 -0
  18. data/app/indexers/concerns/iiif_print/file_set_indexer.rb +45 -17
  19. data/{lib → app/jobs}/iiif_print/jobs/application_job.rb +2 -1
  20. data/app/jobs/iiif_print/jobs/child_works_from_pdf_job.rb +153 -0
  21. data/app/jobs/iiif_print/jobs/create_relationships_job.rb +117 -0
  22. data/app/jobs/iiif_print/jobs/request_split_pdf_job.rb +31 -0
  23. data/app/listeners/iiif_print/listener.rb +31 -0
  24. data/app/models/concerns/iiif_print/set_child_flag.rb +10 -1
  25. data/app/models/concerns/iiif_print/solr/document.rb +19 -3
  26. data/app/models/iiif_print/iiif_search_decorator.rb +35 -0
  27. data/app/models/iiif_print/iiif_search_response_decorator.rb +25 -2
  28. data/app/models/iiif_print/pending_relationship.rb +3 -0
  29. data/app/presenters/iiif_print/file_set_presenter_decorator.rb +11 -0
  30. data/app/presenters/iiif_print/iiif_manifest_presenter_behavior.rb +120 -0
  31. data/app/presenters/iiif_print/iiif_manifest_presenter_factory_behavior.rb +1 -1
  32. data/app/presenters/iiif_print/work_show_presenter_decorator.rb +23 -11
  33. data/app/search_builders/concerns/iiif_print/allinson_flex_fields.rb +15 -0
  34. data/app/search_builders/concerns/iiif_print/highlight_search_params.rb +2 -1
  35. data/app/services/iiif_print/derivative_rodeo_service.rb +382 -0
  36. data/app/services/iiif_print/manifest_builder_service_behavior.rb +90 -31
  37. data/app/services/iiif_print/pluggable_derivative_service.rb +8 -10
  38. data/app/services/iiif_print/simple_schema_loader_decorator.rb +11 -0
  39. data/app/transactions/hyrax/transactions/iiif_print_container_decorator.rb +34 -0
  40. data/app/transactions/hyrax/transactions/steps/conditionally_destroy_children_from_split.rb +32 -0
  41. data/app/transactions/hyrax/transactions/steps/delete_all_file_sets_decorator.rb +35 -0
  42. data/app/views/catalog/_index_header_list_default.html.erb +13 -0
  43. data/app/views/hyrax/base/_representative_media.html.erb +4 -3
  44. data/app/views/hyrax/base/iiif_viewers/_universal_viewer.html.erb +1 -1
  45. data/app/views/hyrax/file_sets/_show_actions.html.erb +24 -0
  46. data/config/initializers/simple_schema_loader.rb +1 -0
  47. data/config/locales/iiif_print.en.yml +4 -0
  48. data/config/metadata/child_works_from_pdf_splitting.yaml +21 -0
  49. data/config/routes.rb +3 -0
  50. data/db/migrate/20181214181358_create_iiif_print_derivative_attachments.rb +8 -6
  51. data/db/migrate/20190107165909_create_iiif_print_ingest_file_relations.rb +7 -5
  52. data/db/migrate/20230109000000_create_iiif_print_pending_relationships.rb +8 -6
  53. data/db/migrate/20231110163052_add_model_details_to_iiif_print_pending_relationships.rb +7 -0
  54. data/docker-compose.yml +2 -2
  55. data/iiif_print.gemspec +11 -10
  56. data/lib/generators/iiif_print/install_generator.rb +21 -1
  57. data/lib/generators/iiif_print/templates/config/initializers/iiif_print.rb +11 -4
  58. data/lib/generators/iiif_print/templates/helpers/iiif_print_helper.rb +5 -0
  59. data/lib/iiif_print/base_derivative_service.rb +14 -2
  60. data/lib/iiif_print/blacklight_iiif_search/annotation_decorator.rb +58 -6
  61. data/lib/iiif_print/catalog_search_builder.rb +7 -3
  62. data/lib/iiif_print/configuration.rb +205 -8
  63. data/lib/iiif_print/data/fileset_helper.rb +3 -3
  64. data/lib/iiif_print/data/work_derivatives.rb +4 -4
  65. data/lib/iiif_print/engine.rb +53 -15
  66. data/lib/iiif_print/errors.rb +18 -0
  67. data/lib/iiif_print/homepage_search_builder.rb +17 -0
  68. data/lib/iiif_print/image_tool.rb +12 -8
  69. data/lib/iiif_print/jp2_derivative_service.rb +4 -1
  70. data/lib/iiif_print/lineage_service.rb +47 -13
  71. data/lib/iiif_print/metadata.rb +67 -48
  72. data/lib/iiif_print/pdf_derivative_service.rb +3 -1
  73. data/lib/iiif_print/persistence_layer/active_fedora_adapter.rb +189 -0
  74. data/lib/iiif_print/persistence_layer/valkyrie_adapter.rb +183 -0
  75. data/lib/iiif_print/persistence_layer.rb +118 -0
  76. data/lib/iiif_print/split_pdfs/base_splitter.rb +153 -0
  77. data/lib/iiif_print/split_pdfs/child_work_creation_from_pdf_service.rb +83 -37
  78. data/lib/iiif_print/split_pdfs/derivative_rodeo_splitter.rb +166 -0
  79. data/lib/iiif_print/split_pdfs/destroy_pdf_child_works_service.rb +22 -0
  80. data/lib/iiif_print/split_pdfs/pages_to_jpgs_splitter.rb +19 -0
  81. data/lib/iiif_print/split_pdfs/pages_to_pngs_splitter.rb +26 -0
  82. data/lib/iiif_print/split_pdfs/pages_to_tiffs_splitter.rb +41 -0
  83. data/lib/iiif_print/split_pdfs/pdf_image_extraction_service.rb +64 -59
  84. data/lib/iiif_print/text_extraction/hocr_reader.rb +7 -3
  85. data/lib/iiif_print/text_extraction/page_ocr.rb +5 -4
  86. data/lib/iiif_print/text_extraction_derivative_service.rb +4 -2
  87. data/lib/iiif_print/text_formats_from_alto_service.rb +3 -1
  88. data/lib/iiif_print/tiff_derivative_service.rb +3 -1
  89. data/lib/iiif_print/version.rb +1 -1
  90. data/lib/iiif_print.rb +210 -20
  91. data/lib/samvera/derivatives/configuration.rb +83 -0
  92. data/lib/samvera/derivatives/hyrax.rb +129 -0
  93. data/lib/samvera/derivatives.rb +238 -0
  94. data/tasks/copy_authorities_to_test_app.rake +11 -0
  95. data/tasks/iiif_print_dev.rake +4 -4
  96. metadata +111 -196
  97. data/app/helpers/hyrax/iiif_helper.rb +0 -22
  98. data/app/indexers/concerns/iiif_print/child_indexer.rb +0 -34
  99. data/app/views/hyrax/file_sets/_actions.html.erb +0 -45
  100. data/bin/rails +0 -13
  101. data/lib/iiif_print/jobs/child_works_from_pdf_job.rb +0 -107
  102. data/lib/iiif_print/jobs/create_relationships_job.rb +0 -78
  103. data/lib/iiif_print/split_pdfs/pages_into_images_service.rb +0 -130
  104. data/spec/.keep.txt +0 -1
  105. data/spec/factories/ability.rb +0 -6
  106. data/spec/factories/newspaper_issue.rb +0 -7
  107. data/spec/factories/newspaper_page.rb +0 -7
  108. data/spec/factories/newspaper_page_solr_document.rb +0 -12
  109. data/spec/factories/newspaper_title.rb +0 -8
  110. data/spec/factories/uploaded_pdf_file.rb +0 -9
  111. data/spec/factories/uploaded_txt_file.rb +0 -9
  112. data/spec/factories/user.rb +0 -13
  113. data/spec/fixtures/files/4.1.07.jp2 +0 -0
  114. data/spec/fixtures/files/4.1.07.tiff +0 -0
  115. data/spec/fixtures/files/README.md +0 -7
  116. data/spec/fixtures/files/alto-2-0.xsd +0 -714
  117. data/spec/fixtures/files/broken-truncated.pdf +0 -0
  118. data/spec/fixtures/files/credits.md +0 -16
  119. data/spec/fixtures/files/lowres-gray-via-ndnp-sample.tiff +0 -0
  120. data/spec/fixtures/files/minimal-1-page.pdf +0 -0
  121. data/spec/fixtures/files/minimal-2-page.pdf +0 -0
  122. data/spec/fixtures/files/minimal-alto.xml +0 -31
  123. data/spec/fixtures/files/ndnp-alto-sample.xml +0 -24
  124. data/spec/fixtures/files/ndnp-sample1-json.json +0 -1
  125. data/spec/fixtures/files/ndnp-sample1-txt.txt +0 -1
  126. data/spec/fixtures/files/ndnp-sample1.pdf +0 -0
  127. data/spec/fixtures/files/ocr_alto.xml +0 -202
  128. data/spec/fixtures/files/ocr_alto_scaled_4pts_per_px.xml +0 -202
  129. data/spec/fixtures/files/ocr_color.tiff +0 -0
  130. data/spec/fixtures/files/ocr_gray.jp2 +0 -0
  131. data/spec/fixtures/files/ocr_gray.tiff +0 -0
  132. data/spec/fixtures/files/ocr_mono.tiff +0 -0
  133. data/spec/fixtures/files/ocr_mono_text_hocr.html +0 -78
  134. data/spec/fixtures/files/page1.tiff +0 -0
  135. data/spec/fixtures/files/sample-4page-issue.pdf +0 -0
  136. data/spec/fixtures/files/sample-color-newsletter.pdf +0 -0
  137. data/spec/fixtures/files/thumbnail.jpg +0 -0
  138. data/spec/helpers/hyrax/iiif_helper_spec.rb +0 -65
  139. data/spec/helpers/iiif_print_helper_spec.rb +0 -43
  140. data/spec/iiif_print/base_derivative_service_spec.rb +0 -11
  141. data/spec/iiif_print/blacklight_iiif_search/annotation_decorator_spec.rb +0 -51
  142. data/spec/iiif_print/catalog_search_builder_spec.rb +0 -60
  143. data/spec/iiif_print/configuration_spec.rb +0 -67
  144. data/spec/iiif_print/data/work_derivatives_spec.rb +0 -245
  145. data/spec/iiif_print/data/work_file_spec.rb +0 -99
  146. data/spec/iiif_print/data/work_files_spec.rb +0 -237
  147. data/spec/iiif_print/image_tool_spec.rb +0 -109
  148. data/spec/iiif_print/jobs/child_works_from_pdf_job_spec.rb +0 -30
  149. data/spec/iiif_print/jobs/create_relationships_job_spec.rb +0 -17
  150. data/spec/iiif_print/jp2_image_metadata_spec.rb +0 -37
  151. data/spec/iiif_print/lineage_service_spec.rb +0 -13
  152. data/spec/iiif_print/metadata_spec.rb +0 -115
  153. data/spec/iiif_print/split_pdfs/pages_into_images_service_spec.rb +0 -6
  154. data/spec/iiif_print/text_extraction/alto_reader_spec.rb +0 -49
  155. data/spec/iiif_print/text_extraction/hocr_reader_spec.rb +0 -45
  156. data/spec/iiif_print/text_extraction/page_ocr_spec.rb +0 -84
  157. data/spec/iiif_print/text_extraction/render_alto_spec.rb +0 -54
  158. data/spec/iiif_print/text_extraction/word_coords_builder_spec.rb +0 -44
  159. data/spec/iiif_print_spec.rb +0 -51
  160. data/spec/misc_shared.rb +0 -111
  161. data/spec/models/iiif_print/derivative_attachment_spec.rb +0 -37
  162. data/spec/models/iiif_print/ingest_file_relation_spec.rb +0 -56
  163. data/spec/models/solr_document_spec.rb +0 -14
  164. data/spec/presenters/iiif_print/iiif_manifest_presenter_behavior_spec.rb +0 -19
  165. data/spec/presenters/iiif_print/iiif_manifest_presenter_factory_behavior_spec.rb +0 -49
  166. data/spec/services/iiif_print/jp2_derivative_service_spec.rb +0 -59
  167. data/spec/services/iiif_print/pdf_derivative_service_spec.rb +0 -66
  168. data/spec/services/iiif_print/pluggable_derivative_service_spec.rb +0 -178
  169. data/spec/services/iiif_print/text_extraction_derivative_service_spec.rb +0 -82
  170. data/spec/services/iiif_print/text_formats_from_alto_service_spec.rb +0 -127
  171. data/spec/services/iiif_print/tiff_derivative_service_spec.rb +0 -65
  172. data/spec/spec_helper.rb +0 -181
  173. data/spec/support/controller_level_helpers.rb +0 -28
  174. data/spec/support/iiif_print_models.rb +0 -127
  175. data/spec/test_app_templates/blacklight.yml +0 -9
  176. data/spec/test_app_templates/fedora.yml +0 -15
  177. data/spec/test_app_templates/lib/generators/test_app_generator.rb +0 -40
  178. data/spec/test_app_templates/redis.yml +0 -9
  179. data/spec/test_app_templates/solr/conf/schema.xml +0 -362
  180. data/spec/test_app_templates/solr/conf/solrconfig.xml +0 -322
  181. data/spec/test_app_templates/solr.yml +0 -7
@@ -0,0 +1,117 @@
1
+ # rubocop:disable Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
2
+ module IiifPrint
3
+ module Jobs
4
+ # Link newly created child works to the parent
5
+ class CreateRelationshipsJob < IiifPrint::Jobs::ApplicationJob
6
+ include Hyrax::Lockable
7
+
8
+ RETRY_MAX = 10
9
+
10
+ # @param parent_id: [<String>] parent work id
11
+ # @param parent_model: [<String>] parent model
12
+ # @param child_model: [<String>] child model
13
+ # @param retries: [<Integer>] count used during rescheduling to prevent infinite retries
14
+ def perform(parent_id:, parent_model:, child_model:, retries: 0, **)
15
+ @parent_id = parent_id
16
+ @parent_model = parent_model
17
+ @child_model = child_model
18
+ @retries = retries + 1
19
+
20
+ @number_of_successes = 0
21
+ @number_of_failures = 0
22
+ @parent_record_members_added = false
23
+ @errors = []
24
+
25
+ # Because we need our children in the correct order, we can't create any
26
+ # relationships until all child works have been created.
27
+ if completed_child_data
28
+ # add the members
29
+ add_children_to_parent
30
+ if @number_of_failures.zero? && @number_of_successes == @pending_children.count
31
+ # remove pending relationships upon valid completion
32
+ @pending_children.each(&:destroy)
33
+ elsif @number_of_failures.zero? && @number_of_successes > @pending_children.count
34
+ # remove pending relationships but raise error that too many relationships formed
35
+ @pending_children.each(&:destroy)
36
+ raise "CreateRelationshipsJob for parent id: #{@parent_id} " \
37
+ "added #{@number_of_successes} children, " \
38
+ "expected #{@pending_children.count} children."
39
+ else
40
+ # report failures & keep pending relationships
41
+ raise "CreateRelationshipsJob failed for parent id: #{@parent_id} " \
42
+ "had #{@number_of_successes} successes & #{@number_of_failures} failures, " \
43
+ "with errors: #{@errors}. Wanted #{@pending_children.count} children."
44
+ end
45
+ else
46
+ # if we aren't ready yet, reschedule the job and end this one normally
47
+ reschedule_job
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ # load @child_works and @pending children, and
54
+ # return boolean indicating whether all chilren are present
55
+ def completed_child_data
56
+ @child_works = []
57
+ found_all_children = true
58
+
59
+ # find and sequence all pending children
60
+ @pending_children = IiifPrint::PendingRelationship.where(parent_id: @parent_id).order('child_order asc')
61
+
62
+ # find child works (skip out if any haven't yet been created)
63
+ @pending_children.each do |child|
64
+ # find by title... if any aren't found, the child works are not yet ready
65
+ found_children = IiifPrint.find_by_title_for(title: child.child_title, model: @child_model)
66
+ found_all_children = false if found_children.empty?
67
+ break unless found_all_children == true
68
+ @child_works += found_children
69
+ end
70
+ # return boolean
71
+ found_all_children
72
+ end
73
+
74
+ def add_children_to_parent
75
+ parent_work = IiifPrint.find_by(id: @parent_id)
76
+ create_relationships(parent: parent_work, ordered_children: @child_works)
77
+ end
78
+
79
+ def reschedule_job
80
+ return if @retries > RETRY_MAX
81
+ CreateRelationshipsJob.set(wait: 10.minutes).perform_later(
82
+ parent_id: @parent_id,
83
+ parent_model: @parent_model,
84
+ child_model: @child_model,
85
+ retries: @retries
86
+ )
87
+ end
88
+
89
+ def create_relationships(parent:, ordered_children:)
90
+ acquire_lock_for(parent.id) do
91
+ # Not sure uncached is needed here, but kept
92
+ # for consistency with Bulkrax's relationships logic
93
+ ActiveRecord::Base.uncached do
94
+ ordered_children.each do |child|
95
+ add_to_work(child_record: child, parent_record: parent)
96
+ @number_of_successes += 1
97
+ rescue => e
98
+ @number_of_failures += 1
99
+ @errors << e
100
+ end
101
+ end
102
+
103
+ IiifPrint.save(object: parent) if @parent_record_members_added && @number_of_failures.zero?
104
+ end
105
+
106
+ # Bulkrax no longer reindexes file_sets, but IiifPrint needs both to add is_page_of_ssim for universal viewer.
107
+ # This is where child works need to be indexed (AFTER the parent save), as opposed to how Bulkrax does it.
108
+ IiifPrint.index_works(objects: ordered_children)
109
+ end
110
+
111
+ def add_to_work(child_record:, parent_record:)
112
+ @parent_record_members_added = IiifPrint.create_relationship_between(child_record: child_record, parent_record: parent_record)
113
+ end
114
+ end
115
+ end
116
+ # rubocop:enable Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
117
+ end
@@ -0,0 +1,31 @@
1
+ module IiifPrint
2
+ module Jobs
3
+ ##
4
+ # Encapsulates logic for cleanup when the PDF is destroyed after pdf splitting into child works
5
+ class RequestSplitPdfJob < IiifPrint::Jobs::ApplicationJob
6
+ ##
7
+ # @param file_set [FileSet]
8
+ # @param user [User]
9
+ # rubocop:disable Metrics/MethodLength
10
+ def perform(file_set:, user:)
11
+ return true unless file_set.pdf?
12
+
13
+ work = IiifPrint.parent_for(file_set)
14
+
15
+ # Woe is ye who changes the configuration of the model, thus removing the splitting.
16
+ raise WorkNotConfiguredToSplitFileSetError.new(work: work, file_set: file_set) unless work&.iiif_print_config&.pdf_splitter_job&.presence
17
+
18
+ # clean up any existing spawned child works of this file_set
19
+ IiifPrint::SplitPdfs::DestroyPdfChildWorksService.conditionally_destroy_spawned_children_of(
20
+ file_set: file_set,
21
+ work: work
22
+ )
23
+
24
+ location = Hyrax::WorkingDirectory.find_or_retrieve(file_set.files.first.id, file_set.id)
25
+
26
+ IiifPrint.conditionally_submit_split_for(work: work, file_set: file_set, locations: [location], user: user)
27
+ end
28
+ # rubocop:enable Metrics/MethodLength
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ ##
2
+ # @see https://github.com/samvera/hyrax/wiki/Hyrax's-Event-Bus-(Hyrax::Publisher)
3
+ # @see https://www.rubydoc.info/gems/hyrax/Hyrax/Publisher
4
+ # @see https://dry-rb.org/gems/dry-events
5
+ module IiifPrint
6
+ class Listener
7
+ ##
8
+ # Responsible for conditionally enqueuing the creation of child works from a PDF.
9
+ #
10
+ # @param event [#[]] a hash like construct with keys :user and :file_set
11
+ # @param service [#conditionally_enqueue]
12
+ #
13
+ # @see Hyrax::WorkUploadsHandler
14
+ def on_file_characterized(event, service: IiifPrint::SplitPdfs::ChildWorkCreationFromPdfService)
15
+ file_set = event[:file_set]
16
+ return false unless file_set
17
+ return false unless file_set.file_set?
18
+ return false unless file_set.original_file.pdf?
19
+
20
+ work = IiifPrint.parent_for(file_set)
21
+ # A short-circuit to avoid fetching the underlying file.
22
+ return false unless work
23
+
24
+ user = work.depositor
25
+ # TODO: Verify that this is the correct thing to be sending off for conditional enquing. That
26
+ # will require a more involved integration test.
27
+ file = file_set.original_file
28
+ service.conditionally_enqueue(file_set: file_set, work: work, file: file, user: user)
29
+ end
30
+ end
31
+ end
@@ -4,6 +4,10 @@ module RDF
4
4
  class CustomIsChildTerm < Vocabulary('http://id.loc.gov/vocabulary/identifiers/')
5
5
  property 'is_child'
6
6
  end
7
+
8
+ class FromPdfIdTerm < Vocabulary('http://id.loc.gov/vocabulary/identifiers/')
9
+ property 'split_from_pdf_id'
10
+ end
7
11
  end
8
12
 
9
13
  module IiifPrint
@@ -18,10 +22,15 @@ module IiifPrint
18
22
  multiple: false do |index|
19
23
  index.as :stored_searchable
20
24
  end
25
+ property :split_from_pdf_id,
26
+ predicate: ::RDF::FromPdfIdTerm.split_from_pdf_id,
27
+ multiple: false do |index|
28
+ index.as :stored_searchable
29
+ end
21
30
  end
22
31
 
23
32
  def set_children
24
- ordered_works.each do |child_work|
33
+ IiifPrint.object_ordered_works(self).each do |child_work|
25
34
  child_work.update(is_child: true) unless child_work.is_child
26
35
  end
27
36
  end
@@ -19,6 +19,8 @@ module IiifPrint::Solr::Document
19
19
  def self.decorate(base)
20
20
  base.prepend(self)
21
21
  base.send(:attribute, :is_child, Hyrax::SolrDocument::Metadata::Solr::String, 'is_child_bsi')
22
+ base.send(:attribute, :split_from_pdf_id, Hyrax::SolrDocument::Metadata::Solr::String, 'split_from_pdf_id_ssi')
23
+ base.send(:attribute, :digest, Hyrax::SolrDocument::Metadata::Solr::String, 'digest_ssim')
22
24
 
23
25
  # @note These properties came from the newspaper_works gem. They are configurable.
24
26
  base.class_attribute :iiif_print_solr_field_names, default: %w[alternative_title genre
@@ -31,17 +33,31 @@ module IiifPrint::Solr::Document
31
33
  base
32
34
  end
33
35
 
36
+ def digest_sha1
37
+ digest[/urn:sha1:([\w]+)/, 1]
38
+ end
39
+
34
40
  def method_missing(method_name, *args, &block)
35
41
  super unless iiif_print_solr_field_names.include? method_name.to_s
36
- self[::ActiveFedora.index_field_mapper.solr_name(method_name.to_s)]
42
+ self[IiifPrint.solr_name(method_name.to_s)]
37
43
  end
38
44
 
39
45
  def respond_to_missing?(method_name, include_private = false)
40
46
  iiif_print_solr_field_names.include?(method_name.to_s) || super
41
47
  end
42
48
 
43
- # TODO: consider configuring this field name; we use the magic field in lots of places.
49
+ # @see https://github.com/samvera/hyrax/commit/7108409c619cd2ba4ae8c836b9f3b429a7e9837b
44
50
  def file_set_ids
45
- self['file_set_ids_ssim']
51
+ # Yes, this looks a little odd. But the truth is the prior key (e.g. `file_set_ids_ssim`) was
52
+ # an alias of `member_ids_ssim`.
53
+ self['member_ids_ssim']
54
+ end
55
+
56
+ def any_highlighting?
57
+ response&.[]('highlighting')&.[](id)&.present?
58
+ end
59
+
60
+ def solr_document
61
+ self
46
62
  end
47
63
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # OVERRIDE: Blacklight IIIF Search v1.0.0
4
+ # IiifSearchDecorator module extends the functionality of the BlacklightIiifSearch::IiifSearch class
5
+ # by overriding the solr_params method to modify the search query to include the parent's metadata.
6
+ module IiifPrint
7
+ module IiifSearchDecorator
8
+ ##
9
+ # Overrides the solr_params method from BlacklightIiifSearch::IiifSearch to modify the search query.
10
+ # The method adds an additional filter to the query to include either the object_relation_field OR the
11
+ # parent document's id and removes the :f parameter from the query.
12
+ # :object_relation_field refers to the CatalogController's configuration which is typically set to
13
+ # 'is_page_of_ssim' in the host application which only searches child works by default.
14
+ #
15
+ # config.iiif_search = {
16
+ # full_text_field: 'all_text_tsimv',
17
+ # object_relation_field: 'is_page_of_ssim',
18
+ # supported_params: %w[q page],
19
+ # autocomplete_handler: 'iiif_suggest',
20
+ # suggester_name: 'iiifSuggester'
21
+ # }
22
+ #
23
+ # @return [Hash] A hash containing the modified Solr search parameters
24
+ #
25
+ def solr_params
26
+ return { q: 'nil:nil' } unless q
27
+
28
+ {
29
+ q: "#{q} AND (#{iiif_config[:object_relation_field]}:\"#{parent_document.id}\" OR id:\"#{parent_document.id}\")",
30
+ rows: rows,
31
+ page: page
32
+ }
33
+ end
34
+ end
35
+ end
@@ -4,14 +4,37 @@ module IiifPrint
4
4
  # @see https://github.com/scientist-softserv/louisville-hyku/commit/67467e5cf9fdb755f54419f17d3c24c87032d0af
5
5
  def annotation_list
6
6
  json_results = super
7
- json_results&.[]('resources')&.each do |result_hit|
7
+
8
+ # Check and process invalid hit
9
+ if json_results&.[]('resources')
10
+ remove_invalid_hit(json_results)
11
+ add_metadata_match(json_results)
12
+ end
13
+
14
+ json_results
15
+ end
16
+
17
+ def remove_invalid_hit(json_results)
18
+ resources = json_results['resources']
19
+ invalid_hit = resources.detect { |resource| resource["on"].include?(IiifPrint::BlacklightIiifSearch::AnnotationDecorator::INVALID_MATCH_TEXT) }
20
+ return unless invalid_hit
21
+
22
+ # Delete invalid hit from resources, remove first hit (which is from the invalid hit), decrement total within
23
+ resources.delete(invalid_hit)
24
+ json_results['hits'].shift
25
+ json_results['within']['total'] -= 1
26
+ end
27
+
28
+ def add_metadata_match(json_results)
29
+ json_results['resources'].each do |result_hit|
8
30
  next if result_hit['resource'].present?
31
+
32
+ # Add resource details if not present
9
33
  result_hit['resource'] = {
10
34
  "@type": "cnt:ContentAsText",
11
35
  "chars": "Metadata match, see sidebar for details"
12
36
  }
13
37
  end
14
- json_results
15
38
  end
16
39
  end
17
40
  end
@@ -3,5 +3,8 @@ module IiifPrint
3
3
  validates :parent_id, presence: true
4
4
  validates :child_title, presence: true
5
5
  validates :child_order, presence: true
6
+ validates :parent_model, presence: true
7
+ validates :child_model, presence: true
8
+ validates :file_id, presence: true
6
9
  end
7
10
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IiifPrint
4
+ module FileSetPresenterDecorator
5
+ # uses Hyku's TenantConfig to determine whether to allow PDF splitting button
6
+ def show_split_button?
7
+ return parent.try(:split_pdfs?) if parent.respond_to?(:split_pdfs?)
8
+ true
9
+ end
10
+ end
11
+ end
@@ -3,8 +3,128 @@ module IiifPrint
3
3
  module IiifManifestPresenterBehavior
4
4
  extend ActiveSupport::Concern
5
5
 
6
+ # Extending the presenter to the base url which includes the protocol.
7
+ # We need the base url to render the facet links and normalize the interface.
8
+ attr_accessor :base_url
9
+
10
+ def manifest_metadata
11
+ # ensure we are using a SolrDocument
12
+ @manifest_metadata ||= IiifPrint.manifest_metadata_from(work: model.solr_document, presenter: self)
13
+ end
14
+
6
15
  def search_service
7
16
  Rails.application.routes.url_helpers.solr_document_iiif_search_url(id, host: hostname)
8
17
  end
18
+
19
+ # OVERRIDE: Hyrax 3x, avoid nil returning to IIIF Manifest gem
20
+ # @see https://github.com/samvera/iiif_manifest/blob/c408f90eba11bef908796c7236ba6bcf8d687acc/lib/iiif_manifest/v3/manifest_builder/record_property_builder.rb#L28
21
+ ##
22
+ # @return [Array<Hash{String => String}>]
23
+ def sequence_rendering
24
+ Array(try(:rendering_ids)).map do |file_set_id|
25
+ rendering = file_set_presenters.find { |p| p.id == file_set_id }
26
+ return [] unless rendering
27
+
28
+ { '@id' => Hyrax::Engine.routes.url_helpers.download_url(rendering.id, host: hostname),
29
+ 'format' => rendering.mime_type.presence || I18n.t("hyrax.manifest.unknown_mime_text"),
30
+ 'label' => I18n.t("hyrax.manifest.download_text") + (rendering.label || '') }
31
+ end.flatten
32
+ end
33
+
34
+ # OVERRIDE: Hyrax v3.x
35
+ module DisplayImagePresenterBehavior
36
+ # Extending the presenter to the base url which includes the protocol.
37
+ # We need the base url to render the facet links and normalize the interface.
38
+ attr_accessor :base_url
39
+
40
+ # Extending this class because there is an #ability= but not #ability and this definition
41
+ # mirrors the Hyrax::IiifManifestPresenter#ability.
42
+ def ability
43
+ @ability ||= NullAbility.new
44
+ end
45
+
46
+ def display_image
47
+ return nil unless latest_file_id
48
+ return nil unless model.image?
49
+ return nil unless IiifPrint.config.default_iiif_manifest_version == 2
50
+
51
+ IIIFManifest::DisplayImage
52
+ .new(display_image_url(hostname),
53
+ format: image_format(alpha_channels),
54
+ width: width,
55
+ height: height,
56
+ iiif_endpoint: iiif_endpoint(latest_file_id, base_url: hostname))
57
+ end
58
+
59
+ # OVERRIDE: IIIF Hyrax AV v0.2 #display_content for prez 3 manifests
60
+ def display_content
61
+ return nil unless latest_file_id
62
+ return super unless model.image?
63
+
64
+ IIIFManifest::V3::DisplayContent
65
+ .new(display_image_url(hostname),
66
+ format: image_format(alpha_channels),
67
+ width: width,
68
+ height: height,
69
+ type: 'Image',
70
+ iiif_endpoint: iiif_endpoint(latest_file_id, base_url: hostname))
71
+ end
72
+
73
+ def display_image_url(base_url)
74
+ if ENV['EXTERNAL_IIIF_URL'].present?
75
+ # At the moment we are only concerned about Hyrax's default image url builder
76
+ iiif_image_url_builder(url_builder: Hyrax.config.iiif_image_url_builder)
77
+ else
78
+ super
79
+ end
80
+ end
81
+
82
+ def iiif_endpoint(file_id, base_url: request.base_url)
83
+ if ENV['EXTERNAL_IIIF_URL'].present?
84
+ IIIFManifest::IIIFEndpoint.new(
85
+ File.join(ENV['EXTERNAL_IIIF_URL'], file_id),
86
+ profile: Hyrax.config.iiif_image_compliance_level_uri
87
+ )
88
+ else
89
+ super
90
+ end
91
+ end
92
+
93
+ def hostname
94
+ @hostname || 'localhost'
95
+ end
96
+
97
+ ##
98
+ # @return [Boolean] false
99
+ def work?
100
+ false
101
+ end
102
+
103
+ private
104
+
105
+ def latest_file_id
106
+ if ENV['EXTERNAL_IIIF_URL'].present?
107
+ external_latest_file_id
108
+ else
109
+ super
110
+ end
111
+ end
112
+
113
+ def external_latest_file_id
114
+ @latest_file_id ||= digest_sha1
115
+ end
116
+
117
+ def iiif_image_url_builder(url_builder:)
118
+ args = [
119
+ latest_file_id,
120
+ ENV['EXTERNAL_IIIF_URL'],
121
+ Hyrax.config.iiif_image_size_default
122
+ ]
123
+ # In Hyrax 3, Hyrax.config.iiif_image_url_builder takes an additional argument
124
+ args << image_format(alpha_channels) if url_builder.arity == 4
125
+
126
+ url_builder.call(*args).gsub(%r{images/}, '')
127
+ end
128
+ end
9
129
  end
10
130
  end
@@ -14,7 +14,7 @@ module IiifPrint
14
14
  presenter_class.for(solr_doc)
15
15
  elsif Hyrax.config.curation_concerns.include?(solr_doc.hydra_model)
16
16
  # look up file set ids and loop through those
17
- file_set_docs = load_file_set_docs(solr_doc.file_set_ids)
17
+ file_set_docs = load_file_set_docs(solr_doc.try(:member_ids) || solr_doc.try(:[], 'member_ids_ssim'))
18
18
  file_set_docs.map { |doc| presenter_class.for(doc) } if file_set_docs.length
19
19
  end
20
20
  end.flatten.compact
@@ -2,28 +2,40 @@
2
2
 
3
3
  module IiifPrint
4
4
  module WorkShowPresenterDecorator
5
- delegate :file_set_ids, to: :solr_document
5
+ delegate :member_ids, to: :solr_document
6
+ alias file_set_ids member_ids
6
7
 
7
- # OVERRIDE Hyrax 2.9.6 to remove check for representative_presenter.image? and allow
8
- # a fallback to check for images on the child works
8
+ # OVERRIDE Hyrax 2.9.6 to remove check for representative_presenter.image?
9
9
  # @return [Boolean] render a IIIF viewer
10
10
  def iiif_viewer?
11
- parent_work_has_files? || child_work_has_files?
11
+ Hyrax.config.iiif_image_server? &&
12
+ representative_id.present? &&
13
+ representative_presenter.present? &&
14
+ members_include_viewable_image?
12
15
  end
13
16
 
14
17
  alias universal_viewer? iiif_viewer?
15
18
 
16
19
  private
17
20
 
18
- def parent_work_has_files?
19
- Hyrax.config.iiif_image_server? &&
20
- representative_id.present? &&
21
- representative_presenter.present? &&
22
- members_include_viewable_image?
21
+ # overriding Hyrax to include file sets for both work and child works (file set ids include both)
22
+ # process each id, short-circuiting the loop once one true value is found. This speeds up the test
23
+ # by not loading more member_presenters than needed.
24
+ #
25
+ # @todo Review if this is necessary for Hyrax 5.
26
+ def members_include_viewable_image?
27
+ all_member_ids = solr_document.try(:member_ids) || solr_document.try(:[], 'member_ids_ssim')
28
+ Array.wrap(all_member_ids).each do |id|
29
+ return true if file_type_and_permissions_valid?(member_presenters_for([id]).first)
30
+ end
31
+ false
23
32
  end
24
33
 
25
- def child_work_has_files?
26
- file_set_ids.present?
34
+ # This method allows for overriding to add additional file types to mix in with IiifAv
35
+ # TODO: add configuration setting for file types to loop through so an override is unneeded.
36
+ def file_type_and_permissions_valid?(presenter)
37
+ current_ability.can?(:read, presenter.id) &&
38
+ (presenter.try(:image?) || presenter.try(:solr_document).try(:image?))
27
39
  end
28
40
  end
29
41
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IiifPrint
4
+ module AllinsonFlexFields
5
+ def include_allinson_flex_fields(solr_parameters)
6
+ return unless defined?(AllinsonFlex)
7
+
8
+ query_fields = solr_parameters[:qf].split(' ') + IiifPrint.allinson_flex_fields
9
+ .each_with_object([]) do |field, arr|
10
+ arr << (field.name + '_tesim') if field.is_a?(AllinsonFlex::ProfileProperty)
11
+ end
12
+ solr_parameters[:qf] = query_fields.uniq.join(' ')
13
+ end
14
+ end
15
+ end
@@ -6,9 +6,10 @@ module IiifPrint
6
6
  def highlight_search_params(solr_parameters = {})
7
7
  return unless solr_parameters[:q] || solr_parameters[:all_fields]
8
8
  solr_parameters[:hl] = true
9
- solr_parameters[:'hl.fl'] = 'all_text_tsimv'
9
+ solr_parameters[:'hl.fl'] = '*'
10
10
  solr_parameters[:'hl.fragsize'] = 100
11
11
  solr_parameters[:'hl.snippets'] = 5
12
+ solr_parameters[:'hl.requiredFieldMatch'] = true
12
13
  end
13
14
  end
14
15
  end