hyrax 5.2.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (527) hide show
  1. checksums.yaml +4 -4
  2. data/.dassie/.env +5 -1
  3. data/.dassie/Gemfile +4 -2
  4. data/.dassie/app/assets/images/bulkrax/removed.png +0 -0
  5. data/.dassie/app/assets/javascripts/application.js +2 -0
  6. data/.dassie/app/assets/stylesheets/application.css +1 -0
  7. data/.dassie/app/controllers/catalog_controller.rb +1 -0
  8. data/.dassie/app/forms/admin_set_resource_form.rb +6 -0
  9. data/.dassie/app/forms/collection_resource_form.rb +5 -2
  10. data/.dassie/app/forms/generic_work_resource_form.rb +5 -2
  11. data/.dassie/app/forms/monograph_form.rb +5 -3
  12. data/.dassie/app/indexers/admin_set_resource_indexer.rb +5 -0
  13. data/.dassie/app/indexers/collection_resource_indexer.rb +3 -3
  14. data/.dassie/app/indexers/generic_work_resource_indexer.rb +6 -3
  15. data/.dassie/app/indexers/monograph_indexer.rb +6 -2
  16. data/.dassie/app/models/ability.rb +8 -0
  17. data/.dassie/app/models/collection_resource.rb +4 -2
  18. data/.dassie/app/models/concerns/bulkrax/has_local_processing.rb +8 -0
  19. data/.dassie/app/models/generic_work_resource.rb +5 -2
  20. data/.dassie/app/models/monograph.rb +5 -2
  21. data/.dassie/app/views/blacklight_dynamic_sitemap/sitemap/show.xml.builder +22 -0
  22. data/.dassie/bin/importer +142 -0
  23. data/.dassie/bin/web +4 -0
  24. data/.dassie/config/browse_everything_providers.yml +1 -1
  25. data/.dassie/config/bulkrax_api.yml +86 -0
  26. data/.dassie/config/initializers/bulkrax.rb +92 -0
  27. data/.dassie/config/initializers/clamav.rb +2 -0
  28. data/.dassie/config/initializers/hyrax.rb +7 -1
  29. data/.dassie/config/initializers/riiif.rb +4 -80
  30. data/.dassie/config/initializers/wings.rb +1 -1
  31. data/.dassie/config/metadata_profiles/m3_profile.yaml +929 -0
  32. data/.dassie/config/puma.rb +4 -2
  33. data/.dassie/config/routes.rb +7 -0
  34. data/.dassie/config/sidekiq.yml +17 -0
  35. data/.dassie/config/uv/uv.html +63 -81
  36. data/.dassie/db/migrate/20240606205215_create_hyrax_flexible_schemas.rb +9 -0
  37. data/.dassie/db/migrate/20240606205216_add_contexts_to_hyrax_flexible_schemas.rb +5 -0
  38. data/.dassie/db/migrate/20250826142438_create_bulkrax_importers.bulkrax.rb +19 -0
  39. data/.dassie/db/migrate/20250826142439_create_bulkrax_importer_runs.bulkrax.rb +17 -0
  40. data/.dassie/db/migrate/20250826142440_create_bulkrax_entries.bulkrax.rb +17 -0
  41. data/.dassie/db/migrate/20250826142441_add_status_to_entry.bulkrax.rb +10 -0
  42. data/.dassie/db/migrate/20250826142442_add_collections_to_importer_runs.bulkrax.rb +7 -0
  43. data/.dassie/db/migrate/20250826142443_change_collection_ids_on_entries.bulkrax.rb +6 -0
  44. data/.dassie/db/migrate/20250826142444_create_bulkrax_exporters.bulkrax.rb +20 -0
  45. data/.dassie/db/migrate/20250826142445_create_bulkrax_exporter_runs.bulkrax.rb +15 -0
  46. data/.dassie/db/migrate/20250826142446_change_importer_and_exporter_to_polymorphic.bulkrax.rb +19 -0
  47. data/.dassie/db/migrate/20250826142447_add_total_collection_records_to_importer_runs.bulkrax.rb +6 -0
  48. data/.dassie/db/migrate/20250826142448_add_children_to_importer_runs.bulkrax.rb +7 -0
  49. data/.dassie/db/migrate/20250826142449_change_total_records_to_total_work_entries.bulkrax.rb +7 -0
  50. data/.dassie/db/migrate/20250826142450_change_entry_last_error.bulkrax.rb +20 -0
  51. data/.dassie/db/migrate/20250826142451_add_validate_only_to_bulkrax_importers.bulkrax.rb +6 -0
  52. data/.dassie/db/migrate/20250826142452_add_status_to_importers.bulkrax.rb +10 -0
  53. data/.dassie/db/migrate/20250826142453_remove_foreign_key_from_bulkrax_entries.bulkrax.rb +6 -0
  54. data/.dassie/db/migrate/20250826142454_add_status_to_exporters.bulkrax.rb +8 -0
  55. data/.dassie/db/migrate/20250826142455_add_invalid_record_to_importer_run.bulkrax.rb +6 -0
  56. data/.dassie/db/migrate/20250826142456_create_bulkrax_statuses.bulkrax.rb +19 -0
  57. data/.dassie/db/migrate/20250826142457_move_to_statuses.bulkrax.rb +31 -0
  58. data/.dassie/db/migrate/20250826142458_add_date_filter_and_status_to_bulkrax_exporters.bulkrax.rb +8 -0
  59. data/.dassie/db/migrate/20250826142459_add_workflow_status_to_bulkrax_exporter.bulkrax.rb +6 -0
  60. data/.dassie/db/migrate/20250826142460_remove_unused_last_error.bulkrax.rb +8 -0
  61. data/.dassie/db/migrate/20250826142461_increase_text_sizes.bulkrax.rb +13 -0
  62. data/.dassie/db/migrate/20250826142462_change_bulkrax_statuses_error_message_column_type_to_text.bulkrax.rb +6 -0
  63. data/.dassie/db/migrate/20250826142463_rename_children_counters_to_relationships.bulkrax.rb +7 -0
  64. data/.dassie/db/migrate/20250826142464_add_file_set_counters_to_importer_runs.bulkrax.rb +8 -0
  65. data/.dassie/db/migrate/20250826142465_add_import_attempts_to_entries.bulkrax.rb +6 -0
  66. data/.dassie/db/migrate/20250826142466_add_work_counters_to_importer_runs.bulkrax.rb +7 -0
  67. data/.dassie/db/migrate/20250826142467_create_bulkrax_pending_relationships.bulkrax.rb +14 -0
  68. data/.dassie/db/migrate/20250826142468_add_order_to_bulkrax_pending_relationships.bulkrax.rb +6 -0
  69. data/.dassie/db/migrate/20250826142469_add_include_thumbnails_to_bulkrax_exporters.bulkrax.rb +6 -0
  70. data/.dassie/db/migrate/20250826142470_add_generated_metadata_to_bulkrax_exporters.bulkrax.rb +6 -0
  71. data/.dassie/db/migrate/20250826142471_rename_bulkrax_importer_run_to_importer_run.bulkrax.rb +18 -0
  72. data/.dassie/db/migrate/20250826142472_add_indices_to_bulkrax.bulkrax.rb +26 -0
  73. data/.dassie/db/migrate/20250826142473_denormalize_status_message.bulkrax.rb +8 -0
  74. data/.dassie/db/migrate/20250826142474_update_identifier_index.bulkrax.rb +7 -0
  75. data/.dassie/db/migrate/20250826142475_add_index_to_metadata_bulkrax_identifier.bulkrax.rb +19 -0
  76. data/.dassie/db/migrate/20250826142476_add_file_name_to_uploaded_files.bulkrax.rb +6 -0
  77. data/.dassie/db/migrate/20250826142477_add_error_tracking_to_pending_relationships.bulkrax.rb +6 -0
  78. data/.dassie/db/migrate/20250826142478_add_last_imported_at_to_bulkrax_importers.bulkrax.rb +6 -0
  79. data/.dassie/db/migrate/20250826142479_add_next_import_at_to_bulkrax_importers.bulkrax.rb +6 -0
  80. data/.dassie/db/migrate/20250826142480_entry_error_denormalization.bulkrax.rb +8 -0
  81. data/.dassie/db/migrate/20250826142481_faster_first_entry.bulkrax.rb +8 -0
  82. data/.dassie/db/migrate/20260430000001_create_hyrax_redirect_paths.hyrax.rb +13 -0
  83. data/.dassie/db/migrate/20260521003627_add_additional_redirect_info.hyrax.rb +32 -0
  84. data/.dassie/db/schema.rb +154 -1
  85. data/.dassie/package.json +1 -1
  86. data/.dassie/rspec_split_configuration.json +835 -0
  87. data/.dassie/solr/conf/solrconfig.xml +21 -5
  88. data/.github/{ISSUE_TEMPLATE.md → ISSUE_TEMPLATE/issue_template.md} +5 -0
  89. data/.github/workflows/lint-build-test.yml +5 -1
  90. data/.github/workflows/release-charts.yml +48 -0
  91. data/.gitignore +6 -0
  92. data/.koppie/.env +5 -1
  93. data/.koppie/Gemfile +3 -1
  94. data/.koppie/app/assets/images/bulkrax/removed.png +0 -0
  95. data/.koppie/app/assets/javascripts/application.js +2 -0
  96. data/.koppie/app/assets/stylesheets/application.css +1 -0
  97. data/.koppie/app/controllers/catalog_controller.rb +1 -1
  98. data/.koppie/app/forms/collection_resource_form.rb +5 -2
  99. data/.koppie/app/forms/file_set_form.rb +5 -0
  100. data/.koppie/app/forms/generic_work_form.rb +5 -3
  101. data/.koppie/app/forms/monograph_form.rb +5 -3
  102. data/.koppie/app/indexers/collection_resource_indexer.rb +5 -2
  103. data/.koppie/app/indexers/file_set_indexer.rb +7 -0
  104. data/.koppie/app/indexers/generic_work_indexer.rb +6 -3
  105. data/.koppie/app/indexers/monograph_indexer.rb +6 -2
  106. data/.koppie/app/models/ability.rb +8 -0
  107. data/.koppie/app/models/collection_resource.rb +4 -2
  108. data/.koppie/app/models/concerns/bulkrax/has_local_processing.rb +8 -0
  109. data/.koppie/app/models/generic_work.rb +5 -2
  110. data/.koppie/app/models/monograph.rb +5 -2
  111. data/.koppie/app/views/blacklight_dynamic_sitemap/sitemap/show.xml.builder +22 -0
  112. data/.koppie/bin/importer +142 -0
  113. data/.koppie/bin/web +4 -0
  114. data/.koppie/config/browse_everything_providers.yml +2 -0
  115. data/.koppie/config/bulkrax_api.yml +86 -0
  116. data/.koppie/config/initializers/bulkrax.rb +92 -0
  117. data/.koppie/config/initializers/clamav.rb +2 -0
  118. data/.koppie/config/initializers/hyrax.rb +11 -3
  119. data/.koppie/config/initializers/riiif.rb +4 -82
  120. data/.koppie/config/metadata_profiles/m3_profile.yaml +930 -0
  121. data/.koppie/config/routes.rb +7 -0
  122. data/.koppie/config/sidekiq.yml +2 -0
  123. data/.koppie/config/uv/uv.html +63 -81
  124. data/.koppie/db/migrate/20240606205215_create_hyrax_flexible_schemas.rb +9 -0
  125. data/.koppie/db/migrate/20240606205216_add_contexts_to_hyrax_flexible_schemas.rb +5 -0
  126. data/.koppie/db/migrate/20250826142438_create_bulkrax_importers.bulkrax.rb +19 -0
  127. data/.koppie/db/migrate/20250826142439_create_bulkrax_importer_runs.bulkrax.rb +17 -0
  128. data/.koppie/db/migrate/20250826142440_create_bulkrax_entries.bulkrax.rb +17 -0
  129. data/.koppie/db/migrate/20250826142441_add_status_to_entry.bulkrax.rb +10 -0
  130. data/.koppie/db/migrate/20250826142442_add_collections_to_importer_runs.bulkrax.rb +7 -0
  131. data/.koppie/db/migrate/20250826142443_change_collection_ids_on_entries.bulkrax.rb +6 -0
  132. data/.koppie/db/migrate/20250826142444_create_bulkrax_exporters.bulkrax.rb +20 -0
  133. data/.koppie/db/migrate/20250826142445_create_bulkrax_exporter_runs.bulkrax.rb +15 -0
  134. data/.koppie/db/migrate/20250826142446_change_importer_and_exporter_to_polymorphic.bulkrax.rb +19 -0
  135. data/.koppie/db/migrate/20250826142447_add_total_collection_records_to_importer_runs.bulkrax.rb +6 -0
  136. data/.koppie/db/migrate/20250826142448_add_children_to_importer_runs.bulkrax.rb +7 -0
  137. data/.koppie/db/migrate/20250826142449_change_total_records_to_total_work_entries.bulkrax.rb +7 -0
  138. data/.koppie/db/migrate/20250826142450_change_entry_last_error.bulkrax.rb +20 -0
  139. data/.koppie/db/migrate/20250826142451_add_validate_only_to_bulkrax_importers.bulkrax.rb +6 -0
  140. data/.koppie/db/migrate/20250826142452_add_status_to_importers.bulkrax.rb +10 -0
  141. data/.koppie/db/migrate/20250826142453_remove_foreign_key_from_bulkrax_entries.bulkrax.rb +6 -0
  142. data/.koppie/db/migrate/20250826142454_add_status_to_exporters.bulkrax.rb +8 -0
  143. data/.koppie/db/migrate/20250826142455_add_invalid_record_to_importer_run.bulkrax.rb +6 -0
  144. data/.koppie/db/migrate/20250826142456_create_bulkrax_statuses.bulkrax.rb +19 -0
  145. data/.koppie/db/migrate/20250826142457_move_to_statuses.bulkrax.rb +31 -0
  146. data/.koppie/db/migrate/20250826142458_add_date_filter_and_status_to_bulkrax_exporters.bulkrax.rb +8 -0
  147. data/.koppie/db/migrate/20250826142459_add_workflow_status_to_bulkrax_exporter.bulkrax.rb +6 -0
  148. data/.koppie/db/migrate/20250826142460_remove_unused_last_error.bulkrax.rb +8 -0
  149. data/.koppie/db/migrate/20250826142461_increase_text_sizes.bulkrax.rb +13 -0
  150. data/.koppie/db/migrate/20250826142462_change_bulkrax_statuses_error_message_column_type_to_text.bulkrax.rb +6 -0
  151. data/.koppie/db/migrate/20250826142463_rename_children_counters_to_relationships.bulkrax.rb +7 -0
  152. data/.koppie/db/migrate/20250826142464_add_file_set_counters_to_importer_runs.bulkrax.rb +8 -0
  153. data/.koppie/db/migrate/20250826142465_add_import_attempts_to_entries.bulkrax.rb +6 -0
  154. data/.koppie/db/migrate/20250826142466_add_work_counters_to_importer_runs.bulkrax.rb +7 -0
  155. data/.koppie/db/migrate/20250826142467_create_bulkrax_pending_relationships.bulkrax.rb +14 -0
  156. data/.koppie/db/migrate/20250826142468_add_order_to_bulkrax_pending_relationships.bulkrax.rb +6 -0
  157. data/.koppie/db/migrate/20250826142469_add_include_thumbnails_to_bulkrax_exporters.bulkrax.rb +6 -0
  158. data/.koppie/db/migrate/20250826142470_add_generated_metadata_to_bulkrax_exporters.bulkrax.rb +6 -0
  159. data/.koppie/db/migrate/20250826142471_rename_bulkrax_importer_run_to_importer_run.bulkrax.rb +18 -0
  160. data/.koppie/db/migrate/20250826142472_add_indices_to_bulkrax.bulkrax.rb +26 -0
  161. data/.koppie/db/migrate/20250826142473_denormalize_status_message.bulkrax.rb +8 -0
  162. data/.koppie/db/migrate/20250826142474_update_identifier_index.bulkrax.rb +7 -0
  163. data/.koppie/db/migrate/20250826142475_add_index_to_metadata_bulkrax_identifier.bulkrax.rb +19 -0
  164. data/.koppie/db/migrate/20250826142476_add_file_name_to_uploaded_files.bulkrax.rb +6 -0
  165. data/.koppie/db/migrate/20250826142477_add_error_tracking_to_pending_relationships.bulkrax.rb +6 -0
  166. data/.koppie/db/migrate/20250826142478_add_last_imported_at_to_bulkrax_importers.bulkrax.rb +6 -0
  167. data/.koppie/db/migrate/20250826142479_add_next_import_at_to_bulkrax_importers.bulkrax.rb +6 -0
  168. data/.koppie/db/migrate/20250826142480_entry_error_denormalization.bulkrax.rb +8 -0
  169. data/.koppie/db/migrate/20250826142481_faster_first_entry.bulkrax.rb +8 -0
  170. data/.koppie/db/migrate/20260430000001_create_hyrax_redirect_paths.hyrax.rb +13 -0
  171. data/.koppie/db/migrate/20260521003627_add_additional_redirect_info.hyrax.rb +32 -0
  172. data/.koppie/db/schema.rb +154 -1
  173. data/.koppie/package.json +1 -1
  174. data/.koppie/rspec_split_configuration.json +835 -0
  175. data/.koppie/solr/conf/solrconfig.xml +0 -5
  176. data/.koppie/yarn.lock +5587 -752
  177. data/.rubocop.yml +1 -0
  178. data/CONTAINERS.md +2 -0
  179. data/Dockerfile +9 -22
  180. data/Gemfile +0 -1
  181. data/Gemfile.allinson +2 -0
  182. data/README.md +1 -2
  183. data/app/actors/hyrax/actors/file_actor.rb +2 -0
  184. data/app/actors/hyrax/actors/file_set_actor.rb +1 -1
  185. data/app/assets/javascripts/hyrax/app.js.erb +143 -10
  186. data/app/assets/javascripts/hyrax/collection_select.js +68 -0
  187. data/app/assets/javascripts/hyrax/copy_permalink_button.js +28 -0
  188. data/app/assets/javascripts/hyrax/redirects.js +45 -0
  189. data/app/assets/javascripts/hyrax/relationships/control.es6 +21 -1
  190. data/app/assets/javascripts/hyrax/select_work_type.es6 +7 -2
  191. data/app/assets/javascripts/hyrax.js +3 -1
  192. data/app/assets/stylesheets/hyrax/_file-show.scss +1 -0
  193. data/app/assets/stylesheets/hyrax/_viewer.scss +8 -0
  194. data/app/assets/stylesheets/hyrax/dashboard.scss +22 -1
  195. data/app/assets/stylesheets/hyrax/sidebar.scss +98 -3
  196. data/app/authorities/qa/authorities/collections.rb +9 -9
  197. data/app/controllers/concerns/hyrax/analytics_error_handling.rb +97 -0
  198. data/app/controllers/concerns/hyrax/collections_controller_behavior.rb +3 -1
  199. data/app/controllers/concerns/hyrax/ensure_migrated_behavior.rb +50 -0
  200. data/app/controllers/concerns/hyrax/flexible_catalog_behavior.rb +206 -0
  201. data/app/controllers/concerns/hyrax/flexible_schema_behavior.rb +17 -0
  202. data/app/controllers/concerns/hyrax/redirect_to_display_url.rb +54 -0
  203. data/app/controllers/concerns/hyrax/works_controller_behavior.rb +52 -10
  204. data/app/controllers/hyrax/admin/admin_sets_controller.rb +21 -13
  205. data/app/controllers/hyrax/admin/analytics/collection_reports_controller.rb +2 -0
  206. data/app/controllers/hyrax/admin/analytics/work_reports_controller.rb +1 -0
  207. data/app/controllers/hyrax/admin/metadata_profiles_controller.rb +66 -0
  208. data/app/controllers/hyrax/batch_uploads_controller.rb +2 -0
  209. data/app/controllers/hyrax/dashboard/collections_controller.rb +27 -6
  210. data/app/controllers/hyrax/dashboard/nest_collections_controller.rb +2 -1
  211. data/app/controllers/hyrax/dashboard_controller.rb +2 -1
  212. data/app/controllers/hyrax/file_sets_controller.rb +19 -1
  213. data/app/controllers/hyrax/homepage_controller.rb +7 -0
  214. data/app/controllers/hyrax/my/works_controller.rb +2 -21
  215. data/app/controllers/hyrax/permissions_controller.rb +4 -2
  216. data/app/controllers/hyrax/redirects_controller.rb +56 -0
  217. data/app/controllers/hyrax/single_use_links_viewer_controller.rb +2 -1
  218. data/app/controllers/hyrax/transcripts_controller.rb +31 -0
  219. data/app/controllers/hyrax/uploads_controller.rb +18 -9
  220. data/app/forms/concerns/hyrax/based_near_field_behavior.rb +77 -0
  221. data/app/forms/concerns/hyrax/flexible_form_behavior.rb +37 -0
  222. data/app/forms/concerns/hyrax/redirects_field_behavior.rb +95 -0
  223. data/app/forms/concerns/hyrax/transcripts_behavior.rb +40 -0
  224. data/app/forms/hyrax/forms/admin_set_form.rb +1 -1
  225. data/app/forms/hyrax/forms/administrative_set_form.rb +3 -0
  226. data/app/forms/hyrax/forms/batch_edit_form.rb +1 -3
  227. data/app/forms/hyrax/forms/batch_upload_form.rb +14 -0
  228. data/app/forms/hyrax/forms/file_set_edit_form.rb +1 -1
  229. data/app/forms/hyrax/forms/file_set_form.rb +6 -7
  230. data/app/forms/hyrax/forms/pcdm_collection_form.rb +17 -5
  231. data/app/forms/hyrax/forms/pcdm_object_form.rb +11 -3
  232. data/app/forms/hyrax/forms/resource_batch_edit_form.rb +8 -22
  233. data/app/forms/hyrax/forms/resource_form.rb +110 -10
  234. data/app/forms/hyrax/forms.rb +1 -0
  235. data/app/helpers/hyrax/attributes_helper.rb +109 -0
  236. data/app/helpers/hyrax/blacklight_override.rb +26 -0
  237. data/app/helpers/hyrax/collections_helper.rb +2 -0
  238. data/app/helpers/hyrax/file_set_form_helper.rb +24 -0
  239. data/app/helpers/hyrax/hyrax_helper_behavior.rb +51 -2
  240. data/app/helpers/hyrax/iiif_helper.rb +11 -1
  241. data/app/helpers/hyrax/permalink_helper.rb +37 -0
  242. data/app/helpers/hyrax/redirects_tab_helper.rb +29 -0
  243. data/app/helpers/hyrax/work_form_helper.rb +9 -5
  244. data/app/helpers/hyrax/works_helper.rb +14 -0
  245. data/app/indexers/concerns/hyrax/workflow_indexer.rb +30 -0
  246. data/app/indexers/hyrax/file_set_indexer.rb +1 -0
  247. data/app/indexers/hyrax/indexers/administrative_set_indexer.rb +2 -1
  248. data/app/indexers/hyrax/indexers/file_set_indexer.rb +6 -2
  249. data/app/indexers/hyrax/indexers/pcdm_collection_indexer.rb +11 -1
  250. data/app/indexers/hyrax/indexers/pcdm_object_indexer.rb +25 -4
  251. data/app/indexers/hyrax/indexers/redirects_indexer.rb +29 -0
  252. data/app/indexers/hyrax/indexers/resource_indexer.rb +4 -0
  253. data/app/indexers/hyrax/indexers.rb +2 -0
  254. data/app/inputs/controlled_vocabulary_input.rb +7 -1
  255. data/app/jobs/content_deposit_error_event_job.rb +14 -0
  256. data/app/jobs/create_work_job.rb +2 -0
  257. data/app/jobs/migrate_files_to_valkyrie_job.rb +7 -1
  258. data/app/models/batch_upload_item.rb +4 -0
  259. data/app/models/concerns/hyrax/ability/flexible_metadata_ability.rb +10 -0
  260. data/app/models/concerns/hyrax/ability.rb +3 -1
  261. data/app/models/concerns/hyrax/collection_behavior.rb +4 -0
  262. data/app/models/concerns/hyrax/file_set/transcripts.rb +16 -0
  263. data/app/models/concerns/hyrax/file_set_behavior.rb +9 -0
  264. data/app/models/concerns/hyrax/flexibility.rb +107 -0
  265. data/app/models/concerns/hyrax/redirects_normalization.rb +38 -0
  266. data/app/models/concerns/hyrax/solr_document/characterization.rb +1 -1
  267. data/app/models/concerns/hyrax/solr_document/metadata.rb +9 -0
  268. data/app/models/concerns/hyrax/solr_document/ordered_members.rb +4 -0
  269. data/app/models/concerns/hyrax/solr_document_behavior.rb +28 -6
  270. data/app/models/concerns/hyrax/suppressible.rb +1 -1
  271. data/app/models/concerns/hyrax/valkyrie_lazy_migration.rb +1 -1
  272. data/app/models/concerns/hyrax/with_embargoes_and_leases.rb +30 -0
  273. data/app/models/concerns/hyrax/work_behavior.rb +8 -0
  274. data/app/models/hyrax/administrative_set.rb +1 -1
  275. data/app/models/hyrax/change_set.rb +3 -1
  276. data/app/models/hyrax/embargo.rb +3 -1
  277. data/app/models/hyrax/file_set.rb +4 -2
  278. data/app/models/hyrax/flexible_schema.rb +219 -0
  279. data/app/models/hyrax/lease.rb +3 -1
  280. data/app/models/hyrax/pcdm_collection.rb +4 -1
  281. data/app/models/hyrax/redirect.rb +64 -0
  282. data/app/models/hyrax/redirect_path.rb +14 -0
  283. data/app/models/hyrax/resource.rb +22 -25
  284. data/app/models/hyrax/riiif/file.rb +43 -0
  285. data/app/models/hyrax/riiif/file_resolver.rb +65 -0
  286. data/app/models/hyrax/uploaded_file.rb +9 -0
  287. data/app/models/hyrax/virus_scanner.rb +16 -3
  288. data/app/models/hyrax/work.rb +4 -1
  289. data/app/models/job_io_wrapper.rb +12 -3
  290. data/app/models/user_mailbox.rb +3 -1
  291. data/app/presenters/hyrax/admin_set_presenter.rb +11 -0
  292. data/app/presenters/hyrax/annotates_content.rb +35 -0
  293. data/app/presenters/hyrax/collection_presenter.rb +5 -1
  294. data/app/presenters/hyrax/displays_content.rb +133 -0
  295. data/app/presenters/hyrax/displays_image.rb +11 -4
  296. data/app/presenters/hyrax/displays_transcripts.rb +59 -0
  297. data/app/presenters/hyrax/file_set_presenter.rb +8 -18
  298. data/app/presenters/hyrax/google_scholar_presenter.rb +39 -0
  299. data/app/presenters/hyrax/iiif_manifest_presenter.rb +22 -4
  300. data/app/presenters/hyrax/missing_method_behavior.rb +15 -0
  301. data/app/presenters/hyrax/pcdm_member_presenter_factory.rb +2 -4
  302. data/app/presenters/hyrax/work_show_presenter.rb +56 -14
  303. data/app/renderers/hyrax/renderers/attribute_renderer.rb +6 -2
  304. data/app/renderers/hyrax/renderers/license_attribute_renderer.rb +10 -10
  305. data/app/renderers/hyrax/renderers/redirects_label_attribute_renderer.rb +25 -0
  306. data/app/renderers/hyrax/renderers/rights_statement_attribute_renderer.rb +10 -11
  307. data/app/search_builders/hyrax/dashboard/nested_collections_search_builder.rb +1 -5
  308. data/app/search_builders/hyrax/filter_by_type.rb +1 -1
  309. data/app/services/hyrax/action/create_valkyrie_work.rb +3 -2
  310. data/app/services/hyrax/authority_rendering_helper.rb +28 -0
  311. data/app/services/hyrax/authority_service.rb +107 -0
  312. data/app/services/hyrax/callbacks.rb +2 -2
  313. data/app/services/hyrax/collections/nested_collection_query_service.rb +9 -3
  314. data/app/services/hyrax/collections_service.rb +17 -0
  315. data/app/services/hyrax/custom_queries/find_by_property_value.rb +89 -0
  316. data/app/services/hyrax/edit_permissions_service.rb +4 -4
  317. data/app/services/hyrax/file_set_type_service.rb +12 -4
  318. data/app/services/hyrax/flexible_schema_validator_service.rb +153 -0
  319. data/app/services/hyrax/flexible_schema_validators/class_validator.rb +123 -0
  320. data/app/services/hyrax/flexible_schema_validators/core_metadata_validator.rb +211 -0
  321. data/app/services/hyrax/flexible_schema_validators/existing_records_validator.rb +83 -0
  322. data/app/services/hyrax/flexible_schema_validators/redirects_validator.rb +116 -0
  323. data/app/services/hyrax/flexible_schema_validators/schema_validator.rb +34 -0
  324. data/app/services/hyrax/flexible_schema_validators/sort_properties_validator.rb +54 -0
  325. data/app/services/hyrax/license_service.rb +1 -1
  326. data/app/services/hyrax/listeners/metadata_index_listener.rb +11 -0
  327. data/app/services/hyrax/m3_profile_editor.rb +32 -0
  328. data/app/services/hyrax/m3_schema_loader.rb +119 -0
  329. data/app/services/hyrax/manifest_builder_service.rb +18 -24
  330. data/app/services/hyrax/permalink_path.rb +25 -0
  331. data/app/services/hyrax/qa_select_service.rb +9 -5
  332. data/app/services/hyrax/redirect_path_normalizer.rb +58 -0
  333. data/app/services/hyrax/redirects_lookup.rb +59 -0
  334. data/app/services/hyrax/resource_permissions_visibility_propagator.rb +54 -0
  335. data/app/services/hyrax/resource_types_service.rb +3 -20
  336. data/app/services/hyrax/rights_statement_service.rb +1 -1
  337. data/app/services/hyrax/schema_loader.rb +205 -0
  338. data/app/services/hyrax/search_service.rb +1 -1
  339. data/app/services/hyrax/simple_schema_loader.rb +28 -127
  340. data/app/services/hyrax/solr_service.rb +39 -2
  341. data/app/services/hyrax/tolerant_select_service.rb +5 -3
  342. data/app/services/hyrax/valkyrie_upload.rb +2 -2
  343. data/app/services/hyrax/visibility_propagator.rb +4 -1
  344. data/app/services/hyrax/work_uploads_handler.rb +17 -9
  345. data/app/services/hyrax/workflow/workflow_factory.rb +5 -4
  346. data/app/services/migrate_resource_service.rb +7 -1
  347. data/app/uploaders/hyrax/uploaded_file_uploader.rb +2 -2
  348. data/app/utils/hyrax/data_destroyers/collection_branding_destroyer.rb +1 -1
  349. data/app/utils/hyrax/required_data_seeder.rb +1 -0
  350. data/app/utils/hyrax/required_data_seeders/collection_type_seeder.rb +1 -1
  351. data/app/utils/hyrax/required_data_seeders/flexible_profile_seeder.rb +28 -0
  352. data/app/validators/hyrax/controlled_vocabulary_validator.rb +64 -0
  353. data/app/validators/hyrax/redirect_validator.rb +128 -0
  354. data/app/views/catalog/_thumbnail_list_default.html.erb +2 -2
  355. data/app/views/collections/edit_fields/_schema_version.html.erb +1 -0
  356. data/app/views/hyrax/admin/admin_sets/_form_metadata.html.erb +24 -2
  357. data/app/views/hyrax/admin/admin_sets/_show_document_list_row.html.erb +1 -1
  358. data/app/views/hyrax/admin/admin_sets/show.html.erb +1 -1
  359. data/app/views/hyrax/admin/analytics/_analytics_error.html.erb +42 -0
  360. data/app/views/hyrax/admin/analytics/collection_reports/index.html.erb +11 -9
  361. data/app/views/hyrax/admin/analytics/work_reports/index.html.erb +60 -57
  362. data/app/views/hyrax/admin/features/index.html.erb +1 -1
  363. data/app/views/hyrax/admin/metadata_profiles/_import_modal.html.erb +20 -0
  364. data/app/views/hyrax/admin/metadata_profiles/index.html.erb +54 -0
  365. data/app/views/hyrax/base/_attribute_rows.html.erb +4 -21
  366. data/app/views/hyrax/base/_citations.html.erb +1 -0
  367. data/app/views/hyrax/base/_currently_shared.html.erb +2 -1
  368. data/app/views/hyrax/base/_file_manager_thumbnail.html.erb +1 -1
  369. data/app/views/hyrax/base/_form_member_of_collections.html.erb +6 -6
  370. data/app/views/hyrax/base/_form_redirects.html.erb +125 -0
  371. data/app/views/hyrax/base/_form_share.html.erb +7 -2
  372. data/app/views/hyrax/base/_member.html.erb +1 -1
  373. data/app/views/hyrax/base/_social_media.html.erb +1 -1
  374. data/app/views/hyrax/base/iiif_viewers/_universal_viewer.html.erb +1 -1
  375. data/app/views/hyrax/base/show.html.erb +3 -0
  376. data/app/views/hyrax/collections/_default_group.html.erb +3 -0
  377. data/app/views/hyrax/collections/_list_collections.html.erb +3 -0
  378. data/app/views/hyrax/collections/_list_works.html.erb +1 -1
  379. data/app/views/hyrax/collections/_show_document_list_row.html.erb +1 -1
  380. data/app/views/hyrax/collections/show.html.erb +4 -0
  381. data/app/views/hyrax/dashboard/_sidebar.html.erb +4 -2
  382. data/app/views/hyrax/dashboard/collections/_button_for_update_collection.html.erb +2 -1
  383. data/app/views/hyrax/dashboard/collections/_default_group.html.erb +3 -0
  384. data/app/views/hyrax/dashboard/collections/_form.html.erb +17 -0
  385. data/app/views/hyrax/dashboard/collections/_form_for_select_collection.html.erb +7 -10
  386. data/app/views/hyrax/dashboard/collections/_list_collections.html.erb +3 -0
  387. data/app/views/hyrax/dashboard/collections/_list_works.html.erb +1 -1
  388. data/app/views/hyrax/dashboard/collections/_show_document_list_row.html.erb +1 -1
  389. data/app/views/hyrax/dashboard/profiles/_edit_primary.html.erb +1 -8
  390. data/app/views/hyrax/dashboard/profiles/_trophy_edit.html.erb +1 -1
  391. data/app/views/hyrax/dashboard/profiles/show.html.erb +5 -5
  392. data/app/views/hyrax/dashboard/sidebar/_metadata.html.erb +11 -0
  393. data/app/views/hyrax/dashboard/sidebar/_repository_content.html.erb +1 -0
  394. data/app/views/hyrax/dashboard/works/_default_group.html.erb +3 -0
  395. data/app/views/hyrax/dashboard/works/_list_works.html.erb +4 -2
  396. data/app/views/hyrax/file_sets/_form.html.erb +16 -3
  397. data/app/views/hyrax/file_sets/_metadata.html.erb +35 -0
  398. data/app/views/hyrax/file_sets/_valkyrie_form.html.erb +37 -0
  399. data/app/views/hyrax/file_sets/edit.html.erb +14 -8
  400. data/app/views/hyrax/file_sets/media_display/_audio.html.erb +4 -0
  401. data/app/views/hyrax/file_sets/media_display/_image.html.erb +2 -2
  402. data/app/views/hyrax/file_sets/media_display/_video.html.erb +4 -0
  403. data/app/views/hyrax/homepage/_explore_collections.html.erb +1 -1
  404. data/app/views/hyrax/homepage/_featured_fields.html.erb +1 -1
  405. data/app/views/hyrax/homepage/_recent_document.html.erb +1 -1
  406. data/app/views/hyrax/homepage/robots.text.erb +17 -0
  407. data/app/views/hyrax/my/collections/_default_group.html.erb +3 -0
  408. data/app/views/hyrax/my/collections/_list_collections.html.erb +3 -0
  409. data/app/views/hyrax/my/collections/_modal_add_subcollection.html.erb +2 -2
  410. data/app/views/hyrax/my/collections/_modal_add_to_collection.html.erb +2 -2
  411. data/app/views/hyrax/my/works/_default_group.html.erb +3 -0
  412. data/app/views/hyrax/my/works/_list_works.html.erb +5 -2
  413. data/app/views/hyrax/shared/_copy_permalink.html.erb +10 -0
  414. data/app/views/hyrax/users/_user_info.html.erb +1 -6
  415. data/app/views/layouts/_head_tag_content.html.erb +2 -0
  416. data/app/views/layouts/hyrax/dashboard.html.erb +2 -1
  417. data/app/views/records/edit_fields/_contexts.html.erb +1 -0
  418. data/app/views/records/edit_fields/_license.html.erb +17 -5
  419. data/app/views/records/edit_fields/_resource_type.html.erb +16 -2
  420. data/app/views/records/edit_fields/_rights_statement.html.erb +17 -5
  421. data/app/views/records/edit_fields/_schema_version.html.erb +1 -0
  422. data/app/views/records/edit_fields/_transcript_ids.html.erb +18 -0
  423. data/app/views/records/show_fields/_rights.html.erb +1 -1
  424. data/app/views/shared/_citations.html.erb +46 -31
  425. data/app/views/shared/_schema_version.html.erb +38 -0
  426. data/bin/dev-entrypoint.sh +14 -4
  427. data/chart/hyrax/Chart.yaml +4 -4
  428. data/chart/hyrax/templates/ingress.yaml +3 -0
  429. data/chart/hyrax/values.yaml +10 -8
  430. data/config/features.rb +27 -0
  431. data/config/initializers/blacklight_dynamic_sitemap.rb +43 -0
  432. data/config/initializers/deprecation_patch.rb +27 -0
  433. data/config/initializers/hydra_works.rb +3 -0
  434. data/config/initializers/i18n_extensions.rb +197 -0
  435. data/config/initializers/storage_adapter_initializer.rb +15 -9
  436. data/config/locales/hyrax.de.yml +172 -54
  437. data/config/locales/hyrax.en.yml +183 -66
  438. data/config/locales/hyrax.es.yml +174 -49
  439. data/config/locales/hyrax.fr.yml +169 -45
  440. data/config/locales/hyrax.it.yml +172 -48
  441. data/config/locales/hyrax.pt-BR.yml +172 -54
  442. data/config/locales/hyrax.zh.yml +145 -21
  443. data/config/metadata/basic_metadata.yaml +66 -0
  444. data/config/metadata/batch_edit_metadata.yaml +119 -0
  445. data/config/metadata/file_set_metadata.yaml +19 -0
  446. data/config/metadata/redirects.yaml +15 -0
  447. data/config/metadata_profiles/m3_json_schema.json +487 -0
  448. data/config/metadata_profiles/m3_profile.yaml +954 -0
  449. data/config/routes.rb +9 -0
  450. data/docker-compose-allinson.yml +176 -0
  451. data/docker-compose-dassie.yml +14 -6
  452. data/docker-compose-koppie.yml +18 -10
  453. data/docker-compose-sirenia.yml +20 -10
  454. data/documentation/copy_permalink.md +29 -0
  455. data/documentation/developing-your-hyrax-based-app.md +2 -2
  456. data/documentation/file-ingest-characterization.md +763 -0
  457. data/documentation/flexible_metadata.md +113 -0
  458. data/documentation/forms/field_behaviors.md +297 -0
  459. data/documentation/redirects.md +428 -0
  460. data/hyrax.gemspec +7 -2
  461. data/lib/freyja/metadata_adapter.rb +1 -1
  462. data/lib/freyja/persister.rb +25 -1
  463. data/lib/generators/hyrax/collection_resource/collection_resource_generator.rb +2 -2
  464. data/lib/generators/hyrax/collection_resource/templates/collection.rb.erb +5 -1
  465. data/lib/generators/hyrax/collection_resource/templates/collection_form.rb.erb +5 -1
  466. data/lib/generators/hyrax/collection_resource/templates/collection_indexer.rb.erb +6 -1
  467. data/lib/generators/hyrax/iiif_viewer/USAGE +22 -0
  468. data/lib/generators/hyrax/iiif_viewer/iiif_viewer_generator.rb +49 -0
  469. data/lib/generators/hyrax/iiif_viewer/templates/_clover.html.erb +7 -0
  470. data/lib/generators/hyrax/iiif_viewer/templates/clover/clover.css +3328 -0
  471. data/lib/generators/hyrax/iiif_viewer/templates/clover/clover.html +16 -0
  472. data/lib/generators/hyrax/iiif_viewer/templates/clover/clover.js +237 -0
  473. data/lib/generators/hyrax/install_generator.rb +25 -1
  474. data/lib/generators/hyrax/riiif_generator.rb +1 -1
  475. data/lib/generators/hyrax/templates/app/views/blacklight_dynamic_sitemap/sitemap/show.xml.builder +22 -0
  476. data/lib/generators/hyrax/templates/config/clamav.rb +2 -0
  477. data/lib/generators/hyrax/templates/config/initializers/riiif.rb +4 -80
  478. data/lib/generators/hyrax/templates/db/migrate/20240606205215_create_hyrax_flexible_schemas.rb.erb +9 -0
  479. data/lib/generators/hyrax/templates/db/migrate/20240606205216_add_contexts_to_hyrax_flexible_schemas.rb.erb +5 -0
  480. data/lib/generators/hyrax/templates/db/migrate/20260430000001_create_hyrax_redirect_paths.rb.erb +22 -0
  481. data/lib/generators/hyrax/templates/db/migrate/20260521003627_add_additional_redirect_info.rb.erb +39 -0
  482. data/lib/generators/hyrax/templates/package.json +1 -1
  483. data/lib/generators/hyrax/templates/uv.html +60 -82
  484. data/lib/generators/hyrax/work/templates/indexer.rb.erb +1 -1
  485. data/lib/generators/hyrax/work/templates/model.rb.erb +5 -1
  486. data/lib/generators/hyrax/work_resource/templates/form.rb.erb +5 -2
  487. data/lib/generators/hyrax/work_resource/templates/indexer.rb.erb +6 -2
  488. data/lib/generators/hyrax/work_resource/templates/work.rb.erb +5 -2
  489. data/lib/generators/hyrax/work_resource/work_resource_generator.rb +1 -0
  490. data/lib/hyrax/active_fedora_dummy_model.rb +6 -0
  491. data/lib/hyrax/configuration.rb +197 -3
  492. data/lib/hyrax/deprecation.rb +31 -0
  493. data/lib/hyrax/engine.rb +2 -0
  494. data/lib/hyrax/form_fields.rb +17 -5
  495. data/lib/hyrax/indexer.rb +31 -2
  496. data/lib/hyrax/resource_sync/resource_list_writer.rb +3 -0
  497. data/lib/hyrax/schema.rb +15 -4
  498. data/lib/hyrax/specs/shared_specs/factories/administrative_sets.rb +12 -0
  499. data/lib/hyrax/specs/shared_specs/factories/hyrax_embargo.rb +1 -1
  500. data/lib/hyrax/specs/shared_specs/factories/hyrax_file_set.rb +13 -0
  501. data/lib/hyrax/specs/shared_specs/factories/hyrax_lease.rb +5 -1
  502. data/lib/hyrax/specs/shared_specs/factories/hyrax_work.rb +14 -0
  503. data/lib/hyrax/specs/shared_specs/simple_work.rb +41 -0
  504. data/lib/hyrax/specs/shared_specs.rb +1 -0
  505. data/lib/hyrax/transactions/collection_create.rb +2 -1
  506. data/lib/hyrax/transactions/collection_destroy.rb +1 -0
  507. data/lib/hyrax/transactions/collection_update.rb +2 -1
  508. data/lib/hyrax/transactions/container.rb +28 -0
  509. data/lib/hyrax/transactions/steps/apply_permission_template_on_update.rb +65 -0
  510. data/lib/hyrax/transactions/steps/apply_workflow_on_admin_set_change.rb +97 -0
  511. data/lib/hyrax/transactions/steps/ensure_admin_set.rb +1 -1
  512. data/lib/hyrax/transactions/steps/remove_redirect_paths.rb +35 -0
  513. data/lib/hyrax/transactions/steps/save.rb +10 -0
  514. data/lib/hyrax/transactions/steps/set_default_admin_set.rb +1 -1
  515. data/lib/hyrax/transactions/steps/sync_redirect_paths.rb +141 -0
  516. data/lib/hyrax/transactions/work_create.rb +2 -1
  517. data/lib/hyrax/transactions/work_destroy.rb +1 -0
  518. data/lib/hyrax/transactions/work_update.rb +4 -1
  519. data/lib/hyrax/version.rb +1 -1
  520. data/lib/hyrax.rb +61 -0
  521. data/lib/tasks/m3_profile_cleanup.rake +35 -0
  522. data/template.rb +4 -1
  523. metadata +313 -19
  524. data/.dassie/public/robots.txt +0 -1
  525. data/.koppie/public/robots.txt +0 -1
  526. data/app/forms/concerns/hyrax/basic_metadata_form_fields_behavior.rb +0 -39
  527. data/app/views/catalog/_thumbnail_list_collection.html.erb +0 -4
@@ -14,8 +14,10 @@ module Hyrax
14
14
  attribute :embargo_release_date, Valkyrie::Types::DateTime
15
15
  attribute :embargo_history, Valkyrie::Types::Array
16
16
 
17
+ # Fix releasing embargos on the day they are expired - this solves a 1 second bug around how
18
+ # midnights are calculated, which causes day of embargos to incorrectly set the permissions to private
17
19
  def active?
18
- (embargo_release_date.present? && Hyrax::TimeService.time_in_utc < embargo_release_date)
20
+ (embargo_release_date.present? && Date.yesterday.end_of_day < embargo_release_date)
19
21
  end
20
22
  end
21
23
  end
@@ -49,8 +49,10 @@ module Hyrax
49
49
  # @see Hyrax::CustomQueries::Navigators::ParentWorkNavigator#find_parent_work
50
50
  # @see https://wiki.duraspace.org/display/samvera/Hydra%3A%3AWorks+Shared+Modeling
51
51
  class FileSet < Hyrax::Resource
52
- include Hyrax::Schema(:core_metadata)
53
- include Hyrax::Schema(:file_set_metadata)
52
+ if Hyrax.config.file_set_include_metadata?
53
+ include Hyrax::Schema(:core_metadata)
54
+ include Hyrax::Schema(:file_set_metadata)
55
+ end
54
56
 
55
57
  def self.model_name(name_class: Hyrax::Name)
56
58
  @_model_name ||= name_class.new(self, nil, 'FileSet')
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+ # rubocop:disable Metrics/ClassLength
3
+ class Hyrax::FlexibleSchema < ApplicationRecord
4
+ serialize :profile, coder: YAML
5
+ serialize :contexts, coder: YAML
6
+
7
+ validate :validate_profile
8
+ validate :validate_property_name_conflicts
9
+
10
+ before_save :update_contexts
11
+
12
+ def warnings
13
+ @warnings ||= ActiveModel::Errors.new(self)
14
+ end
15
+
16
+ def self.current_version
17
+ order("created_at asc").last&.profile
18
+ end
19
+
20
+ def self.current_schema_id
21
+ order("created_at asc").last&.id
22
+ end
23
+
24
+ def self.create_default_schema
25
+ m3_profile_path = Hyrax::Schema.m3_schema_loader.config_paths&.first
26
+ raise ArgumentError, "No M3 profile found, check the Hyrax.config.schema_loader_config_search_paths" unless m3_profile_path
27
+ schema = Hyrax::FlexibleSchema.first
28
+ return if schema
29
+ schema = Hyrax::FlexibleSchema.new(profile: YAML.safe_load_file(m3_profile_path))
30
+ schema.save(validate: false)
31
+ schema
32
+ end
33
+
34
+ # Retrieve the properties for the model / work type
35
+ # This is a class method called by the model at class load
36
+ # meaning AdminSet is not available and we cannot get the
37
+ # contextual dynamic_schema
38
+ # Instead we use the default (contextless) dynamic_schema
39
+ # which will add all properties available for that class
40
+ # @return [Array] property#to_sym
41
+ def self.default_properties
42
+ current_version['properties'].symbolize_keys!.keys
43
+ rescue StandardError
44
+ []
45
+ end
46
+
47
+ # Retrieve the required data to use for mappings
48
+ def self.mappings_data_for(mapping = 'simple_dc_pmh')
49
+ # for OAI-PMH we need the mappings and indexing info
50
+ # for properties with the specified mapping
51
+ return {} unless current_version
52
+ current_version['properties'].each_with_object({}) do |(key, values), obj|
53
+ next unless values['mappings'] && values['mappings'][mapping]
54
+ obj[key] = {
55
+ 'indexing' => values['indexing'],
56
+ 'mappings' => { mapping => values['mappings'][mapping] }
57
+ }
58
+ end
59
+ end
60
+
61
+ def update_contexts
62
+ self.contexts = profile['contexts']
63
+ end
64
+
65
+ def title
66
+ "#{profile['profile']['responsibility_statement']} - version #{id}"
67
+ end
68
+
69
+ def attributes_for(class_name)
70
+ class_names[class_name]
71
+ end
72
+
73
+ def schema_version
74
+ profile['m3_version']
75
+ end
76
+
77
+ def context_select
78
+ contexts&.map { |k, v| [v&.[]('display_label'), k] }
79
+ end
80
+
81
+ def metadata_profile_type
82
+ profile['profile']['type']
83
+ end
84
+
85
+ def version
86
+ id
87
+ end
88
+
89
+ def profile_created_at
90
+ created_at.strftime("%b %d, %Y")
91
+ end
92
+
93
+ private
94
+
95
+ def validate_profile
96
+ validation_service = Hyrax::FlexibleSchemaValidatorService.new(profile:)
97
+ validation_service.validate!
98
+
99
+ validation_service.errors.each do |e|
100
+ errors.add(:profile, e.to_s)
101
+ end
102
+
103
+ validation_service.warnings.each do |w|
104
+ warnings.add(:profile, "Warning: #{w.to_s}")
105
+ end
106
+ end
107
+
108
+ def validate_property_name_conflicts
109
+ return unless profile&.dig('properties')
110
+
111
+ # Group properties by their resolved name
112
+ properties_by_name = profile['properties'].each_with_object({}) do |(key, config), hash|
113
+ property_name = config['name'] || key
114
+ hash[property_name] ||= []
115
+ hash[property_name] << { key: key, config: config }
116
+ end
117
+
118
+ # Check for conflicts (same name with overlapping class/context)
119
+ properties_by_name.each do |property_name, properties|
120
+ next if properties.length == 1
121
+
122
+ # Check all pairs for conflicts
123
+ properties.combination(2).each do |prop1, prop2|
124
+ if properties_conflict?(prop1[:config], prop2[:config])
125
+ errors.add(:profile, "Property name '#{property_name}' conflicts between '#{prop1[:key]}' and '#{prop2[:key]}' - they have overlapping classes and contexts")
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ def properties_conflict?(config1, config2)
132
+ classes1 = Array(config1.dig('available_on', 'class'))
133
+ classes2 = Array(config2.dig('available_on', 'class'))
134
+
135
+ contexts1 = Array(config1.dig('available_on', 'context'))
136
+ contexts2 = Array(config2.dig('available_on', 'context'))
137
+
138
+ # If no contexts specified, consider as universal context
139
+ contexts1 = [nil] if contexts1.empty?
140
+ contexts2 = [nil] if contexts2.empty?
141
+
142
+ # Conflict if there's any overlap in both classes AND contexts
143
+ class_overlap = (classes1 & classes2).any?
144
+ context_overlap = !(contexts1 & contexts2).empty?
145
+
146
+ class_overlap && context_overlap
147
+ end
148
+
149
+ def class_names
150
+ return @class_names if @class_names
151
+ @class_names = {}
152
+ profile['classes'].keys.each do |class_name|
153
+ @class_names[class_name] = {}
154
+ end
155
+ profile['properties'].each do |key, values|
156
+ property_name = values['name'] || key
157
+ values['available_on']['class'].each do |property_class|
158
+ # map some m3 items to what Hyrax expects
159
+ values = values_map(values)
160
+ @class_names[property_class][property_name] = values
161
+ end
162
+ end
163
+ @class_names
164
+ end
165
+
166
+ def values_map(values)
167
+ values['type'] = lookup_type(values['range'])
168
+ values['predicate'] = values['property_uri']
169
+ values['index_keys'] = values['indexing']
170
+ values['context'] = values.dig('available_on', 'context')
171
+ values['multiple'] = determine_multiplicity(values)
172
+
173
+ normalize_form_attributes(values)
174
+
175
+ values
176
+ end
177
+
178
+ def determine_multiplicity(values)
179
+ return values['data_type'] == 'array' if values.key?('data_type')
180
+ return values['multi_value'] if values.key?('multi_value')
181
+
182
+ if (card = values['cardinality'])
183
+ max = card['maximum']
184
+ return max.nil? || max.to_i > 1
185
+ end
186
+
187
+ false
188
+ end
189
+
190
+ def normalize_form_attributes(values)
191
+ return unless values['form']
192
+
193
+ # Rename `multi_value` key to `multiple` (legacy support)
194
+ values['form']['multiple'] = values['form'].delete('multi_value') if values['form'].key?('multi_value')
195
+
196
+ values['form']['multiple'] = values['form'].delete('data_type') == 'array' if values['form'].key?('data_type')
197
+
198
+ assign_required_flag(values)
199
+ end
200
+
201
+ def assign_required_flag(values)
202
+ return unless (card = values['cardinality']) && card['minimum']
203
+
204
+ return if values['form'].key?('required')
205
+
206
+ required_flag = card['minimum'].to_i.positive?
207
+ values['form']['required'] = required_flag
208
+ end
209
+
210
+ def lookup_type(range)
211
+ case range
212
+ when "http://www.w3.org/2001/XMLSchema#dateTime"
213
+ 'date_time'
214
+ else
215
+ range.split('#').last.underscore
216
+ end
217
+ end
218
+ end
219
+ # rubocop:enable Metrics/ClassLength
@@ -14,8 +14,10 @@ module Hyrax
14
14
  attribute :lease_expiration_date, Valkyrie::Types::DateTime
15
15
  attribute :lease_history, Valkyrie::Types::Array
16
16
 
17
+ # Fix releasing leases on the day they are expired - this solves a 1 second bug around how
18
+ # midnights are calculated, which causes day of leases to incorrectly set the permissions to private
17
19
  def active?
18
- (lease_expiration_date.present? && Hyrax::TimeService.time_in_utc < lease_expiration_date)
20
+ (lease_expiration_date.present? && Time.zone.today.end_of_day < lease_expiration_date)
19
21
  end
20
22
  end
21
23
  end
@@ -40,7 +40,10 @@ module Hyrax
40
40
  # @see Hyrax::CustomQueries::Navigators::CollectionMembers#find_members_of
41
41
  #
42
42
  class PcdmCollection < Hyrax::Resource
43
- include Hyrax::Schema(:core_metadata)
43
+ include Hyrax::Schema(:core_metadata) if Hyrax.config.collection_include_metadata?
44
+ # See documentation/redirects.md for the redirects feature.
45
+ include Hyrax::Schema(:redirects) if Hyrax.config.collection_include_metadata? && Hyrax.config.redirects_enabled?
46
+ include Hyrax::RedirectsNormalization if Hyrax.config.redirects_enabled?
44
47
 
45
48
  attribute :collection_type_gid, Valkyrie::Types::String
46
49
  attribute :member_ids, Valkyrie::Types::Array.of(Valkyrie::Types::ID).meta(ordered: true)
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ ##
5
+ # Wraps a single redirect entry for use in form views, providing `.path`
6
+ # and `.is_display_url` readers over the underlying persisted hash.
7
+ #
8
+ # Redirects are persisted as plain hashes on the parent work or
9
+ # collection. Form-render code calls `Hyrax::Redirect.wrap(entry)` to
10
+ # get a value object the view can call methods on. Other code (the
11
+ # validator, indexer, sync step) reads the persisted hash directly.
12
+ #
13
+ # @example
14
+ # Hyrax::Redirect.new(path: '/handle/12345/678', is_display_url: false)
15
+ # Hyrax::Redirect.wrap('path' => '/foo', 'is_display_url' => true)
16
+ class Redirect
17
+ attr_reader :path, :is_display_url
18
+
19
+ ##
20
+ # Accept nil values so the view can build an empty trailing row.
21
+ def initialize(path: nil, is_display_url: false)
22
+ @path = path
23
+ @is_display_url = is_display_url
24
+ end
25
+
26
+ ##
27
+ # Build a presenter from a hash. If passed a presenter, returns it
28
+ # unchanged. Returns nil for nil input. Accepts string or symbol keys.
29
+ #
30
+ # @param input [Hyrax::Redirect, Hash, nil]
31
+ # @return [Hyrax::Redirect, nil]
32
+ def self.wrap(input)
33
+ return nil if input.nil?
34
+ return input if input.is_a?(Hyrax::Redirect)
35
+ raise ArgumentError, "cannot wrap #{input.class} as Hyrax::Redirect" unless input.respond_to?(:to_h)
36
+
37
+ h = input.to_h.transform_keys(&:to_s)
38
+ new(path: h['path'], is_display_url: h.fetch('is_display_url', false))
39
+ end
40
+
41
+ ##
42
+ # @return [Hash{String => Object}] string-keyed hash matching the persisted shape.
43
+ def to_h
44
+ { 'path' => path, 'is_display_url' => is_display_url }
45
+ end
46
+
47
+ def as_json(*)
48
+ to_h
49
+ end
50
+
51
+ ##
52
+ # Equality on attribute values, so `Array#uniq` works as expected.
53
+ def ==(other)
54
+ other.is_a?(Hyrax::Redirect) &&
55
+ other.path == path &&
56
+ other.is_display_url == is_display_url
57
+ end
58
+ alias eql? ==
59
+
60
+ def hash
61
+ [path, is_display_url].hash
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ # ActiveRecord-backed redirects table for global path uniqueness.
5
+ #
6
+ # Maintained by Hyrax::Transactions::Steps::SyncRedirectPaths and
7
+ # Hyrax::Transactions::Steps::RemoveRedirectPaths; queried by
8
+ # Hyrax::RedirectsLookup. The unique index on `from_path` is the source
9
+ # of truth for "no two records share a redirect alias"; this class just
10
+ # exposes it.
11
+ class RedirectPath < ActiveRecord::Base
12
+ self.table_name = 'hyrax_redirect_paths'
13
+ end
14
+ end
@@ -30,9 +30,11 @@ module Hyrax
30
30
  # `Hyrax::VisibilityWriter` (which provide their underlying
31
31
  # implementations).
32
32
  #
33
- class Resource < Valkyrie::Resource
33
+ class Resource < Valkyrie::Resource # disable:rubocop Metrics/ClassLength
34
34
  include Hyrax::Naming
35
35
  include Hyrax::WithEvents
36
+ include Hyrax::WithEmbargoesAndLeases
37
+ include Hyrax::Suppressible
36
38
 
37
39
  attribute :alternate_ids, Valkyrie::Types::Array.of(Valkyrie::Types::ID)
38
40
  attribute :embargo_id, Valkyrie::Types::Params::ID
@@ -44,6 +46,21 @@ module Hyrax
44
46
  :read_users, :read_users=, to: :permission_manager
45
47
 
46
48
  class << self
49
+ def inherited(subclass)
50
+ super
51
+ subclass.acts_as_flexible_resource if Hyrax.config.flexible_classes.include?(subclass.name)
52
+ end
53
+
54
+ def acts_as_flexible_resource
55
+ include Hyrax::Flexibility
56
+ include Hyrax::Schema(name, schema_loader: Hyrax::Schema.m3_schema_loader)
57
+ Hyrax.config.flexible_classes << name unless Hyrax.config.flexible_classes.include?(name)
58
+ end
59
+
60
+ def flexible?
61
+ defined?(super) ? super : false
62
+ end
63
+
47
64
  ##
48
65
  # @return [String] a human readable name for the model
49
66
  def human_readable_type
@@ -105,6 +122,10 @@ module Hyrax
105
122
  end
106
123
  end
107
124
 
125
+ def flexible?
126
+ self.class.flexible?
127
+ end
128
+
108
129
  ##
109
130
  # @return [Boolean]
110
131
  def collection?
@@ -170,30 +191,6 @@ module Hyrax
170
191
  visibility_reader.read
171
192
  end
172
193
 
173
- def embargo=(value)
174
- raise TypeError "can't convert #{value.class} into Hyrax::Embargo" unless value.is_a? Hyrax::Embargo
175
-
176
- @embargo = value
177
- self.embargo_id = @embargo.id
178
- end
179
-
180
- def embargo
181
- return @embargo if @embargo
182
- @embargo = Hyrax.query_service.find_by(id: embargo_id) if embargo_id.present?
183
- end
184
-
185
- def lease=(value)
186
- raise TypeError "can't convert #{value.class} into Hyrax::Lease" unless value.is_a? Hyrax::Lease
187
-
188
- @lease = value
189
- self.lease_id = @lease.id
190
- end
191
-
192
- def lease
193
- return @lease if @lease
194
- @lease = Hyrax.query_service.find_by(id: lease_id) if lease_id.present?
195
- end
196
-
197
194
  protected
198
195
 
199
196
  def visibility_writer
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ if defined?(::Riiif::File)
3
+ module Hyrax
4
+ module Riiif
5
+ class File < ::Riiif::File
6
+ include ActiveSupport::Benchmarkable
7
+
8
+ attr_reader :id
9
+
10
+ def initialize(input_path, tempfile = nil, id:)
11
+ super(input_path, tempfile)
12
+ raise(ArgumentError, "must specify id") if id.blank?
13
+ @id = id
14
+ end
15
+
16
+ # Wrap extract in a read lock and benchmark it
17
+ def extract(transformation, image_info = nil)
18
+ ::Riiif::Image.file_resolver.file_locks[id].with_read_lock do
19
+ benchmark "Hyrax::Riiif::File extracted #{path} with #{transformation.to_params}", level: :debug do
20
+ super
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def logger
28
+ Hyrax.logger
29
+ end
30
+ end
31
+ end
32
+ end
33
+ else
34
+ module Hyrax
35
+ module Riiif
36
+ class File
37
+ def initialize(*)
38
+ raise 'Riiif not available'
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+ if defined?(Riiif::Image)
3
+ module Hyrax
4
+ module Riiif
5
+ # Adds file locking to Riiif::File
6
+ # @see Riiif::FileResolver
7
+ class FileResolver
8
+ include ActiveSupport::Benchmarkable
9
+
10
+ # @param [String] id from iiif manifest
11
+ # @return [Riiif::File]
12
+ def find(id)
13
+ path = nil
14
+ file_locks[id].with_write_lock do
15
+ path = build_path(id)
16
+ path = build_path(id, force: true) unless ::File.exist?(path) # Ensures the file is locally available
17
+ end
18
+ Hyrax::Riiif::File.new(path, id: id)
19
+ end
20
+
21
+ # tracks individual file locks
22
+ # @see Hyrax::Riiif::File
23
+ # @return [Concurrent::Map<Concurrent::ReadWriteLock>]
24
+ def file_locks
25
+ @file_locks ||= Concurrent::Map.new do |k, v|
26
+ k.compute_if_absent(v) { Concurrent::ReadWriteLock.new }
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def build_path(id, force: false)
33
+ ::Riiif::Image.cache.fetch("riiif:" + Digest::MD5.hexdigest("path:#{id}"),
34
+ expires_in: ::Riiif::Image.expires_in,
35
+ force: force) do
36
+ load_file(id)
37
+ end
38
+ end
39
+
40
+ def load_file(id)
41
+ benchmark "Hyrax::Riiif::FileResolver loaded #{id}", level: :debug do
42
+ fs_id = id.sub(/\A([^\/]*)\/.*/, '\1')
43
+ file_set = Hyrax.query_service.find_by(id: fs_id)
44
+ file_metadata = Hyrax.custom_queries.find_original_file(file_set: file_set)
45
+ file_metadata.file.disk_path.to_s # Stores a local copy in tmpdir
46
+ end
47
+ end
48
+
49
+ def logger
50
+ Hyrax.logger
51
+ end
52
+ end
53
+ end
54
+ end
55
+ else
56
+ module Hyrax
57
+ module Riiif
58
+ class FileResolver
59
+ def initialize(*)
60
+ raise 'Riiif not available'
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -14,6 +14,8 @@ module Hyrax
14
14
  dependent: :destroy
15
15
  belongs_to :user, class_name: '::User'
16
16
 
17
+ validate :virus_scan
18
+
17
19
  ##
18
20
  # Associate a {FileSet} with this uploaded file.
19
21
  #
@@ -28,5 +30,12 @@ module Hyrax
28
30
  end
29
31
  update!(file_set_uri: uri)
30
32
  end
33
+
34
+ private
35
+
36
+ def virus_scan
37
+ errors.add(:file, I18n.t('hyrax.virus_scanner.virus_detected', filename: file.path)) if
38
+ file.path && Hyrax::VirusScanner.infected?(file.path)
39
+ end
31
40
  end
32
41
  end
@@ -38,10 +38,25 @@ module Hyrax
38
38
  #
39
39
  # @return [Boolean]
40
40
  def infected?
41
- defined?(ClamAV) ? clam_av_scanner : null_scanner
41
+ if defined?(Clamby)
42
+ clamby_scanner
43
+ elsif defined?(ClamAV)
44
+ clam_av_scanner
45
+ else
46
+ null_scanner
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def clamby_scanner
53
+ scan_result = Clamby.virus?(file)
54
+ warning("A virus was found in #{file}") if scan_result
55
+ scan_result
42
56
  end
43
57
 
44
58
  def clam_av_scanner
59
+ Deprecation.warn("ClamAV support has been deprecated. Please use Clamby instead.")
45
60
  scan_result = ClamAV.instance.method(:scanfile).call(file)
46
61
  return false if scan_result.zero?
47
62
  warning "A virus was found in #{file}: #{scan_result}"
@@ -58,8 +73,6 @@ module Hyrax
58
73
  false
59
74
  end
60
75
 
61
- private
62
-
63
76
  def warning(msg)
64
77
  Hyrax.logger&.warn(msg)
65
78
  end
@@ -94,7 +94,10 @@ module Hyrax
94
94
  # @see https://wiki.lyrasis.org/display/samvera/Hydra::Works+Shared+Modeling
95
95
  # for a historical perspective.
96
96
  class Work < Hyrax::Resource
97
- include Hyrax::Schema(:core_metadata)
97
+ include Hyrax::Schema(:core_metadata) if Hyrax.config.work_default_metadata
98
+ # See documentation/redirects.md for the redirects feature.
99
+ include Hyrax::Schema(:redirects) if Hyrax.config.work_default_metadata && Hyrax.config.redirects_enabled?
100
+ include Hyrax::RedirectsNormalization if Hyrax.config.redirects_enabled?
98
101
 
99
102
  attribute :admin_set_id, Valkyrie::Types::ID
100
103
  attribute :member_ids, Valkyrie::Types::Array.of(Valkyrie::Types::ID).meta(ordered: true)
@@ -25,6 +25,8 @@ class JobIoWrapper < ApplicationRecord
25
25
  after_initialize :static_defaults
26
26
  delegate :read, to: :file
27
27
 
28
+ attr_accessor :use_valkyrie
29
+
28
30
  # Responsible for creating a JobIoWrapper from the given parameters, with a
29
31
  # focus on sniffing out attributes from the given :file.
30
32
  #
@@ -34,8 +36,8 @@ class JobIoWrapper < ApplicationRecord
34
36
  # @param [FileSet] file_set - The associated file set
35
37
  # @return [JobIoWrapper]
36
38
  # @raise ActiveRecord::RecordInvalid - if the instance is not valid
37
- def self.create_with_varied_file_handling!(user:, file:, relation:, file_set:)
38
- args = { user: user, relation: relation.to_s, file_set_id: file_set.id }
39
+ def self.create_with_varied_file_handling!(user:, file:, relation:, file_set:, use_valkyrie: nil)
40
+ args = { user: user, relation: relation.to_s, file_set_id: file_set.id, use_valkyrie: use_valkyrie }
39
41
  if file.is_a?(Hyrax::UploadedFile)
40
42
  args[:uploaded_file] = file
41
43
  args[:path] = file.uploader.path
@@ -49,6 +51,12 @@ class JobIoWrapper < ApplicationRecord
49
51
  create!(args)
50
52
  end
51
53
 
54
+ def initialize(attributes = {})
55
+ @use_valkyrie = attributes&.delete(:use_valkyrie)
56
+ @use_valkyrie = Hyrax.config.use_valkyrie? if @use_valkyrie.nil?
57
+ super(attributes)
58
+ end
59
+
52
60
  def original_name
53
61
  super || extracted_original_name
54
62
  end
@@ -63,7 +71,8 @@ class JobIoWrapper < ApplicationRecord
63
71
  nil # unable to determine
64
72
  end
65
73
 
66
- def file_set(use_valkyrie: Hyrax.config.query_index_from_valkyrie)
74
+ def file_set(use_valkyrie: nil)
75
+ use_valkyrie ||= @use_valkyrie
67
76
  return FileSet.find(file_set_id) unless use_valkyrie
68
77
  Hyrax.query_service.find_by(id: Valkyrie::ID.new(file_set_id))
69
78
  end
@@ -49,7 +49,9 @@ class UserMailbox
49
49
  end
50
50
 
51
51
  def preferred_locale
52
- user.preferred_locale || I18n.default_locale
52
+ return I18n.default_locale unless I18n.locale_available?(user.preferred_locale)
53
+
54
+ user.preferred_locale
53
55
  end
54
56
 
55
57
  def empty_trash(user)
@@ -1,6 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
  module Hyrax
3
3
  class AdminSetPresenter < CollectionPresenter
4
+ delegate :contexts, to: :solr_document
5
+
6
+ def self.terms
7
+ base = super
8
+ if Hyrax.config.flexible? && Hyrax.config.admin_set_class.new.respond_to?(:contexts)
9
+ base | [:contexts]
10
+ else
11
+ base
12
+ end
13
+ end
14
+
4
15
  ##
5
16
  # @return [Boolean] true if there are items
6
17
  def any_items?