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
@@ -36,6 +36,8 @@ module Hyrax
36
36
  require 'hyrax/transactions/steps/add_to_parent'
37
37
  require 'hyrax/transactions/steps/apply_collection_type_permissions'
38
38
  require 'hyrax/transactions/steps/apply_permission_template'
39
+ require 'hyrax/transactions/steps/apply_permission_template_on_update'
40
+ require 'hyrax/transactions/steps/apply_workflow_on_admin_set_change'
39
41
  require 'hyrax/transactions/steps/change_depositor'
40
42
  require 'hyrax/transactions/steps/check_for_default_admin_set'
41
43
  require 'hyrax/transactions/steps/check_for_empty_admin_set'
@@ -49,6 +51,7 @@ module Hyrax
49
51
  require 'hyrax/transactions/steps/set_collection_type_gid'
50
52
  require 'hyrax/transactions/steps/remove_file_set_from_work'
51
53
  require 'hyrax/transactions/steps/remove_from_membership'
54
+ require 'hyrax/transactions/steps/remove_redirect_paths'
52
55
  require 'hyrax/transactions/steps/save'
53
56
  require 'hyrax/transactions/steps/save_access_control'
54
57
  require 'hyrax/transactions/steps/save_collection_banner'
@@ -58,6 +61,7 @@ module Hyrax
58
61
  require 'hyrax/transactions/steps/set_uploaded_date_unless_present'
59
62
  require 'hyrax/transactions/steps/set_user_as_creator'
60
63
  require 'hyrax/transactions/steps/set_user_as_depositor'
64
+ require 'hyrax/transactions/steps/sync_redirect_paths'
61
65
  require 'hyrax/transactions/steps/update_work_members'
62
66
  require 'hyrax/transactions/steps/validate'
63
67
 
@@ -245,6 +249,14 @@ module Hyrax
245
249
  ops.register 'save_collection_logo' do
246
250
  Steps::SaveCollectionLogo.new
247
251
  end
252
+
253
+ ops.register 'sync_redirect_paths' do
254
+ Steps::SyncRedirectPaths.new
255
+ end
256
+
257
+ ops.register 'remove_redirect_paths' do
258
+ Steps::RemoveRedirectPaths.new
259
+ end
248
260
  end
249
261
 
250
262
  namespace 'work_resource' do |ops| # Hyrax::Work resource
@@ -260,6 +272,14 @@ module Hyrax
260
272
  Steps::ApplyPermissionTemplate.new
261
273
  end
262
274
 
275
+ ops.register 'apply_permission_template_on_update' do
276
+ Steps::ApplyPermissionTemplateOnUpdate.new
277
+ end
278
+
279
+ ops.register 'apply_workflow_on_admin_set_change' do
280
+ Steps::ApplyWorkflowOnAdminSetChange.new
281
+ end
282
+
263
283
  ops.register 'change_depositor' do
264
284
  Steps::ChangeDepositor.new
265
285
  end
@@ -287,6 +307,14 @@ module Hyrax
287
307
  ops.register 'update_work_members' do
288
308
  Steps::UpdateWorkMembers.new
289
309
  end
310
+
311
+ ops.register 'sync_redirect_paths' do
312
+ Steps::SyncRedirectPaths.new
313
+ end
314
+
315
+ ops.register 'remove_redirect_paths' do
316
+ Steps::RemoveRedirectPaths.new
317
+ end
290
318
  end
291
319
  # rubocop:enable Metrics/BlockLength
292
320
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module Transactions
4
+ module Steps
5
+ ##
6
+ # A `dry-transaction` step that applies a permission template to a work
7
+ # when its admin set has changed during an update.
8
+ #
9
+ # Removes grants from the old admin set's permission template that are not
10
+ # present in the new template, then applies the new template's grants.
11
+ # Manually added permissions and the depositor's access are preserved.
12
+ #
13
+ # @since 5.1.0
14
+ class ApplyPermissionTemplateOnUpdate
15
+ include Dry::Monads[:result]
16
+
17
+ ##
18
+ # @param [Hyrax::Work] object
19
+ #
20
+ # @return [Dry::Monads::Result]
21
+ def call(object)
22
+ return Success(object) unless object.respond_to?(:previous_admin_set_id)
23
+
24
+ old_template = Hyrax::PermissionTemplate.find_by(source_id: object.previous_admin_set_id)
25
+ new_template = Hyrax::PermissionTemplate.find_by(source_id: object.admin_set_id)
26
+
27
+ remove_old_grants(object, old_template, new_template) if old_template
28
+
29
+ if new_template
30
+ Hyrax::PermissionTemplateApplicator.apply(new_template).to(model: object)
31
+ else
32
+ Hyrax.logger.warn("At update time, #{object} doesn't have a " \
33
+ "PermissionTemplate for new AdministrativeSet " \
34
+ "#{object.admin_set_id}. Continuing without " \
35
+ "applying permissions.")
36
+ end
37
+
38
+ Success(object)
39
+ end
40
+
41
+ private
42
+
43
+ def remove_old_grants(object, old_template, new_template)
44
+ new_agents = agents_from_template(new_template)
45
+
46
+ object.edit_groups = object.edit_groups.to_a - (old_template.agent_ids_for(agent_type: 'group', access: 'manage') - new_agents[:manage_groups])
47
+ object.edit_users = object.edit_users.to_a - (old_template.agent_ids_for(agent_type: 'user', access: 'manage') - new_agents[:manage_users])
48
+ object.read_groups = object.read_groups.to_a - (old_template.agent_ids_for(agent_type: 'group', access: 'view') - new_agents[:view_groups])
49
+ object.read_users = object.read_users.to_a - (old_template.agent_ids_for(agent_type: 'user', access: 'view') - new_agents[:view_users])
50
+ end
51
+
52
+ def agents_from_template(template)
53
+ return { manage_groups: [], manage_users: [], view_groups: [], view_users: [] } if template.nil?
54
+
55
+ {
56
+ manage_groups: template.agent_ids_for(agent_type: 'group', access: 'manage').to_a,
57
+ manage_users: template.agent_ids_for(agent_type: 'user', access: 'manage').to_a,
58
+ view_groups: template.agent_ids_for(agent_type: 'group', access: 'view').to_a,
59
+ view_users: template.agent_ids_for(agent_type: 'user', access: 'view').to_a
60
+ }
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module Transactions
4
+ module Steps
5
+ ##
6
+ # A `dry-transaction` step that moves a work's `Sipity::Entity` to the
7
+ # new admin set's active workflow when the admin set has changed during
8
+ # an update and both the old and new admin sets use workflow management.
9
+ #
10
+ # The entity's `workflow_state` is preserved by matching on state name:
11
+ # the new workflow's state with the same name is used when one exists,
12
+ # and the new workflow's initial state is used as a fallback (with a
13
+ # warning) when it does not.
14
+ #
15
+ # The depositor's existing `EntitySpecificResponsibility` is re-created
16
+ # against the new workflow's `depositing` role so it is not orphaned to
17
+ # a `WorkflowRole` from the prior workflow.
18
+ #
19
+ # No-ops when:
20
+ # * the admin set has not changed,
21
+ # * the new admin set has no active workflow (the entity is left in
22
+ # place, pointing at the prior workflow), or
23
+ # * the work has no `Sipity::Entity` yet (the prior admin set was not
24
+ # workflow-managed).
25
+ class ApplyWorkflowOnAdminSetChange
26
+ include Dry::Monads[:result]
27
+
28
+ ##
29
+ # @param [Hyrax::Work] object
30
+ #
31
+ # @return [Dry::Monads::Result]
32
+ def call(object)
33
+ return Success(object) unless object.respond_to?(:previous_admin_set_id)
34
+
35
+ entity = Sipity::Entity.find_by(proxy_for_global_id: Hyrax::GlobalID(object).to_s)
36
+ return Success(object) if entity.nil?
37
+
38
+ new_workflow = active_workflow_for(object.admin_set_id)
39
+ return Success(object) if new_workflow.nil?
40
+
41
+ move_entity_to_workflow(entity: entity, new_workflow: new_workflow, object: object)
42
+ reassign_depositor_responsibility(entity: entity, new_workflow: new_workflow, object: object)
43
+
44
+ Success(object)
45
+ end
46
+
47
+ private
48
+
49
+ def active_workflow_for(admin_set_id)
50
+ Sipity::Workflow.find_active_workflow_for(admin_set_id: admin_set_id)
51
+ rescue Sipity::Workflow::NoActiveWorkflowError
52
+ nil
53
+ end
54
+
55
+ def move_entity_to_workflow(entity:, new_workflow:, object:)
56
+ target_state = matching_state_on(new_workflow, entity.workflow_state_name) ||
57
+ fallback_initial_state(new_workflow, entity, object)
58
+ entity.update!(workflow: new_workflow, workflow_state: target_state)
59
+ end
60
+
61
+ def matching_state_on(workflow, state_name)
62
+ return nil if state_name.blank?
63
+ workflow.workflow_states.find_by(name: state_name)
64
+ end
65
+
66
+ def fallback_initial_state(new_workflow, entity, object)
67
+ Hyrax.logger.warn("Workflow state #{entity.workflow_state_name.inspect} from prior workflow " \
68
+ "does not exist on the new active workflow for AdministrativeSet " \
69
+ "#{object.admin_set_id}. Falling back to the initial state for work " \
70
+ "#{object.id}.")
71
+ new_workflow.initial_workflow_state
72
+ end
73
+
74
+ def reassign_depositor_responsibility(entity:, new_workflow:, object:)
75
+ depositor = ::User.find_by_user_key(object.depositor)
76
+ return if depositor.nil?
77
+
78
+ remove_stale_depositor_responsibility(entity: entity, new_workflow: new_workflow, depositor: depositor)
79
+
80
+ Hyrax::Workflow::PermissionGenerator.call(agents: depositor,
81
+ entity: entity,
82
+ roles: Hyrax::RoleRegistry::DEPOSITING,
83
+ workflow: new_workflow)
84
+ end
85
+
86
+ def remove_stale_depositor_responsibility(entity:, new_workflow:, depositor:)
87
+ depositor_agent = Sipity::Agent(depositor)
88
+ entity.entity_specific_responsibilities
89
+ .joins(:workflow_role)
90
+ .where(agent_id: depositor_agent.id)
91
+ .where.not(sipity_workflow_roles: { workflow_id: new_workflow.id })
92
+ .destroy_all
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -15,7 +15,7 @@ module Hyrax
15
15
  # @return [Dry::Monads::Result] `Failure` if there is no `AdminSet` for
16
16
  # the input; `Success(input)`, otherwise.
17
17
  def call(obj)
18
- obj.admin_set_id ? Success(obj) : Failure(:no_admin_set_id)
18
+ obj.admin_set_id.to_s.present? ? Success(obj) : Failure(:no_admin_set_id)
19
19
  end
20
20
  end
21
21
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ require 'dry/monads'
3
+
4
+ module Hyrax
5
+ module Transactions
6
+ module Steps
7
+ # A `dry-transaction` step that removes a resource's rows from the
8
+ # `hyrax_redirect_paths` redirects table. Runs as part of the destroy
9
+ # transactions so deleted resources don't leave dangling claims on
10
+ # redirect paths.
11
+ #
12
+ # See documentation/redirects.md.
13
+ class RemoveRedirectPaths
14
+ include Dry::Monads[:result]
15
+
16
+ # @param [Valkyrie::Resource] resource
17
+ # @return [Dry::Monads::Result]
18
+ def call(resource)
19
+ return Success(resource) unless removable?(resource)
20
+ Hyrax::RedirectPath.where(resource_id: resource.id.to_s).delete_all
21
+ Success(resource)
22
+ rescue ActiveRecord::StatementInvalid => e
23
+ Hyrax.logger.error("[redirects] remove_redirect_paths failed: #{e.message}")
24
+ Failure([:redirect_path_remove_error, e.message])
25
+ end
26
+
27
+ private
28
+
29
+ def removable?(resource)
30
+ Hyrax.config.redirects_enabled? && resource.respond_to?(:id) && resource.id.present?
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -35,6 +35,7 @@ module Hyrax
35
35
  valid_future_date?(change_set.lease, 'lease_expiration_date') if change_set.respond_to?(:lease) && change_set.lease
36
36
  valid_future_date?(change_set.embargo, 'embargo_release_date') if change_set.respond_to?(:embargo) && change_set.embargo
37
37
  new_collections = changed_collection_membership(change_set)
38
+ old_admin_set_id = changed_admin_set_id(change_set)
38
39
 
39
40
  unsaved = change_set.sync
40
41
  save_lease_or_embargo(unsaved)
@@ -50,6 +51,8 @@ module Hyrax
50
51
 
51
52
  user ||= ::User.find_by_user_key(saved.depositor)
52
53
 
54
+ saved.define_singleton_method(:previous_admin_set_id) { old_admin_set_id } if old_admin_set_id
55
+
53
56
  publish_changes(resource: saved, user: user, new: unsaved.new_record, new_collections: new_collections)
54
57
  Success(saved)
55
58
  end
@@ -83,6 +86,13 @@ module Hyrax
83
86
  change_set.member_of_collection_ids - change_set.model.member_of_collection_ids
84
87
  end
85
88
 
89
+ def changed_admin_set_id(change_set)
90
+ return nil unless change_set.respond_to?(:admin_set_id) &&
91
+ change_set.changed?(:admin_set_id)
92
+
93
+ change_set.model.admin_set_id
94
+ end
95
+
86
96
  def publish_changes(resource:, user:, new: false, new_collections: [])
87
97
  if resource.collection?
88
98
  @publisher.publish('collection.metadata.updated', collection: resource, user: user)
@@ -16,7 +16,7 @@ module Hyrax
16
16
  #
17
17
  # @return [Dry::Monads::Result]
18
18
  def call(obj)
19
- obj.admin_set_id ||= Hyrax::EnsureWellFormedAdminSetService.call
19
+ obj.admin_set_id = Hyrax::EnsureWellFormedAdminSetService.call if obj.admin_set_id.to_s.blank?
20
20
 
21
21
  Success(obj)
22
22
  end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+ require 'dry/monads'
3
+
4
+ module Hyrax
5
+ module Transactions
6
+ module Steps
7
+ # A `dry-transaction` step that mirrors a saved resource's `redirects`
8
+ # entries into the `hyrax_redirect_paths` redirects table. The unique
9
+ # index on `from_path` enforces global uniqueness at the DB level — if
10
+ # a concurrent save already claimed a path, the insert raises
11
+ # ActiveRecord::RecordNotUnique and this step returns Failure, which
12
+ # short-circuits the enclosing transaction.
13
+ #
14
+ # Every row carries the resource's canonical UUID URL in
15
+ # `permalink_path`. Column semantics:
16
+ #
17
+ # - `from_path` — the URL the visitor entered (an alias or the UUID
18
+ # URL itself).
19
+ # - `to_path` — the URL the address bar should display after the
20
+ # resolver routes the request. For the display row, equals its own
21
+ # `from_path` (the visitor stays at the display URL). For
22
+ # non-display rows, points at the display row's `from_path` (the
23
+ # visitor lands at the user-facing display URL). When no entry is
24
+ # marked, every alias's `to_path` is the UUID URL.
25
+ # - `permalink_path` — the resource's canonical UUID URL. Constant
26
+ # per resource across all rows.
27
+ #
28
+ # When a display URL is set, the sync step also writes an extra row
29
+ # with `from_path = permalink_path` so visitors hitting the bare
30
+ # UUID URL are routed to the display alias.
31
+ #
32
+ # No-op when the redirects feature is off (config or Flipflop) or when
33
+ # the resource doesn't carry the redirects attribute.
34
+ #
35
+ # See documentation/redirects.md.
36
+ class SyncRedirectPaths
37
+ include Dry::Monads[:result]
38
+
39
+ # @param [Valkyrie::Resource] object the saved resource (must have an id)
40
+ # @return [Dry::Monads::Result]
41
+ def call(object)
42
+ return Success(object) unless syncable?(object)
43
+ replace_rows(object, build_rows(object))
44
+ Success(object)
45
+ rescue ActiveRecord::RecordNotUnique => e
46
+ Failure([:redirect_path_collision, e.message])
47
+ rescue ActiveRecord::StatementInvalid => e
48
+ Hyrax.logger.error("[redirects] sync_redirect_paths failed: #{e.message}")
49
+ Failure([:redirect_path_sync_error, e.message])
50
+ end
51
+
52
+ private
53
+
54
+ def syncable?(object)
55
+ return false unless Hyrax.config.redirects_active?
56
+ object.respond_to?(:redirects) && object.respond_to?(:id) && object.id.present?
57
+ end
58
+
59
+ def build_rows(object)
60
+ permalink = Hyrax::PermalinkPath.call(object)
61
+ entries = entries_for(object)
62
+ display_path = entries.find { |e| e[:is_display_url] }&.dig(:from_path)
63
+ resource_id = object.id.to_s
64
+ now = Time.current
65
+ alias_rows = entries.map { |entry| build_alias_row(entry, resource_id, permalink, display_path, now) }
66
+ alias_rows << build_permalink_row(resource_id, permalink, display_path, now) if display_path
67
+ alias_rows
68
+ end
69
+
70
+ # Valkyrie's JSONValueMapper symbolizes hash keys on read; accept either.
71
+ # Paths are normalized at write time by Hyrax::RedirectsNormalization.
72
+ def entries_for(object)
73
+ seen = Set.new
74
+ Array(object.redirects).each_with_object([]) do |entry, acc|
75
+ path = entry['path'] || entry[:path]
76
+ next if path.blank? || seen.include?(path)
77
+ seen << path
78
+ acc << { from_path: path, is_display_url: display_url_flag(entry) }
79
+ end
80
+ end
81
+
82
+ # Boolean-cast the stored value so importer/console writes that leave a
83
+ # string like "false" or "0" in the JSONB hash don't end up as truthy.
84
+ # Returns false when the key is absent.
85
+ def display_url_flag(entry)
86
+ raw = if entry.key?('is_display_url')
87
+ entry['is_display_url']
88
+ elsif entry.key?(:is_display_url)
89
+ entry[:is_display_url]
90
+ end
91
+ ActiveModel::Type::Boolean.new.cast(raw) || false
92
+ end
93
+
94
+ def build_alias_row(entry, resource_id, permalink, display_path, now)
95
+ to_path = if entry[:is_display_url]
96
+ entry[:from_path]
97
+ elsif display_path
98
+ display_path
99
+ else
100
+ permalink
101
+ end
102
+ { from_path: entry[:from_path],
103
+ to_path: to_path,
104
+ permalink_path: permalink,
105
+ resource_id: resource_id,
106
+ is_display_url: entry[:is_display_url],
107
+ created_at: now, updated_at: now }
108
+ end
109
+
110
+ def build_permalink_row(resource_id, permalink, display_path, now)
111
+ { from_path: permalink,
112
+ to_path: display_path,
113
+ permalink_path: permalink,
114
+ resource_id: resource_id,
115
+ is_display_url: false,
116
+ created_at: now, updated_at: now }
117
+ end
118
+
119
+ # Replaces the resource's rows when the set differs from what's
120
+ # persisted. Skips the rewrite when the desired and existing rows
121
+ # match (the common case on an update that didn't touch the
122
+ # redirects attribute), to preserve `created_at` and avoid an
123
+ # unnecessary DB transaction.
124
+ def replace_rows(object, rows)
125
+ desired = rows.map { |r| [r[:from_path], r[:to_path], r[:is_display_url]] }.sort
126
+
127
+ Hyrax::RedirectPath.transaction do
128
+ existing = Hyrax::RedirectPath.where(resource_id: object.id.to_s)
129
+ .pluck(:from_path, :to_path, :is_display_url)
130
+ return if desired == existing.sort
131
+
132
+ Hyrax::RedirectPath.where(resource_id: object.id.to_s).delete_all
133
+ # rubocop:disable Rails/SkipsModelValidations -- the DB unique index on `from_path` is the validation we rely on; bulk insert is intentional
134
+ Hyrax::RedirectPath.insert_all!(rows) if rows.any?
135
+ # rubocop:enable Rails/SkipsModelValidations
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -16,7 +16,8 @@ module Hyrax
16
16
  'work_resource.save_acl',
17
17
  'work_resource.add_file_sets',
18
18
  'work_resource.change_depositor',
19
- 'work_resource.add_to_parent'].freeze
19
+ 'work_resource.add_to_parent',
20
+ 'work_resource.sync_redirect_paths'].freeze
20
21
 
21
22
  ##
22
23
  # @see Hyrax::Transactions::Transaction
@@ -10,6 +10,7 @@ module Hyrax
10
10
  class WorkDestroy < Transaction
11
11
  DEFAULT_STEPS = ['work_resource.delete_all_file_sets',
12
12
  'work_resource.delete_acl',
13
+ 'work_resource.remove_redirect_paths',
13
14
  'work_resource.delete'].freeze
14
15
 
15
16
  ##
@@ -5,9 +5,12 @@ module Hyrax
5
5
  # @since 3.4.0
6
6
  class WorkUpdate < Transaction
7
7
  DEFAULT_STEPS = ['change_set.apply',
8
+ 'work_resource.apply_permission_template_on_update',
9
+ 'work_resource.apply_workflow_on_admin_set_change',
8
10
  'work_resource.save_acl',
9
11
  'work_resource.add_file_sets',
10
- 'work_resource.update_work_members'].freeze
12
+ 'work_resource.update_work_members',
13
+ 'work_resource.sync_redirect_paths'].freeze
11
14
 
12
15
  ##
13
16
  # @see Hyrax::Transactions::Transaction
data/lib/hyrax/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Hyrax
3
- VERSION = '5.2.0'
3
+ VERSION = '5.3.0'
4
4
  end
data/lib/hyrax.rb CHANGED
@@ -17,6 +17,7 @@ require 'browse-everything'
17
17
  require 'hydra/works'
18
18
  require 'hyrax/engine'
19
19
  require 'hyrax/version'
20
+ require 'hyrax/deprecation'
20
21
  require 'hyrax/inflections'
21
22
  require 'hyrax/name'
22
23
  require 'hyrax/valkyrie_can_can_adapter'
@@ -48,6 +49,8 @@ module Hyrax
48
49
  autoload :Zotero
49
50
  autoload :Listeners
50
51
  autoload :Workflow
52
+ autoload :SchemaLoader
53
+ autoload :M3SchemaLoader
51
54
  autoload :SimpleSchemaLoader
52
55
  autoload :VirusScanner
53
56
  autoload :DerivativeBucketedStorage
@@ -146,4 +149,62 @@ module Hyrax
146
149
  def self.custom_queries
147
150
  query_service.custom_queries
148
151
  end
152
+
153
+ ##
154
+ # Returns the schema for a resource class, optionally scoped by admin set contexts.
155
+ #
156
+ # When +admin_set_id+ is present and flexible schema is enabled, the admin set's
157
+ # contexts are used so flexible resources get a context-aware schema. If the
158
+ # admin set is not found, falls back to the base schema without raising.
159
+ #
160
+ # @param klass [Class] a Valkyrie resource class (e.g. Hyrax::Work)
161
+ # @param admin_set_id [String, nil] optional admin set id to resolve contexts from
162
+ # @return [Dry::Types::Schema, Array] the schema (keys/types or enumerable of keys)
163
+ def self.schema_for(klass:, admin_set_id: nil)
164
+ contexts = if admin_set_id.blank? || !config.flexible?
165
+ []
166
+ else
167
+ schema_contexts_for(admin_set_id)
168
+ end
169
+ resolve_schema(klass, contexts)
170
+ rescue Valkyrie::Persistence::ObjectNotFoundError
171
+ fallback_schema(klass)
172
+ end
173
+
174
+ ##
175
+ # @api private
176
+ #
177
+ # @param admin_set_id [String]
178
+ # @return [Array<String>] context identifiers from the admin set, or []
179
+ def self.schema_contexts_for(admin_set_id)
180
+ return [] if admin_set_id.blank?
181
+ admin_set = query_service.find_by(id: admin_set_id)
182
+ admin_set.respond_to?(:contexts) ? Array(admin_set.contexts) : []
183
+ end
184
+ private_class_method :schema_contexts_for
185
+
186
+ ##
187
+ # @api private
188
+ #
189
+ # @param klass [Class]
190
+ # @param contexts [Array<String>]
191
+ # @return [Dry::Types::Schema, Array] the resolved schema for the class and contexts
192
+ def self.resolve_schema(klass, contexts)
193
+ if contexts.present? && klass.respond_to?(:flexible?) && klass.flexible?
194
+ klass.new(contexts: contexts).singleton_class.schema || klass.schema
195
+ else
196
+ klass.new.singleton_class.schema || klass.schema
197
+ end
198
+ end
199
+ private_class_method :resolve_schema
200
+
201
+ ##
202
+ # @api private
203
+ #
204
+ # @param klass [Class]
205
+ # @return [Dry::Types::Schema, Array] the base schema when admin set lookup fails
206
+ def self.fallback_schema(klass)
207
+ klass.new.singleton_class.schema || klass.schema
208
+ end
209
+ private_class_method :fallback_schema
149
210
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'pathname'
5
+
6
+ namespace :hyrax do
7
+ namespace :metadata do
8
+ desc "Clean up display_label attributes in M3 profile YAML file"
9
+ task :update_labels, [:profile_path] => :environment do |_task, args|
10
+ # Force i18n to load
11
+ I18n.t('blacklight.search.fields.show.title_tesim')
12
+
13
+ profile_editor = Hyrax::M3ProfileEditor.new(Rails.root.join(args[:profile_path]) || Rails.root.join('config', 'metadata_profiles', 'm3_profile.yaml'))
14
+
15
+ profile_editor.profile_data['properties'].each do |property_name, property_data|
16
+ default_label = case property_data['display_label']
17
+ when String
18
+ property_data['display_label']
19
+ when Hash
20
+ property_data['display_label']['default']
21
+ else
22
+ property_name.humanize
23
+ end
24
+ existing_hash = property_data['view']&.fetch('label', nil)
25
+ existing_hash = nil if !existing_hash.is_a?(Hash) || existing_hash.keys.size <= 1
26
+ existing_hash ||= property_data['display_label'] if property_data['display_label'].is_a?(Hash) && property_data['display_label'].keys.size > 1
27
+
28
+ profile_editor.profile_data['properties'][property_name]['display_label'] = existing_hash.presence || {}
29
+ profile_editor.profile_data['properties'][property_name]['display_label']['default'] = profile_editor.find_i18n(default_label)
30
+ profile_editor.profile_data['properties'][property_name]['view'].delete('label') if profile_editor.profile_data['properties'][property_name]['view'].present?
31
+ end
32
+ profile_editor.save
33
+ end
34
+ end
35
+ end
data/template.rb CHANGED
@@ -4,6 +4,9 @@ insert_into_file 'config/application.rb', after: /config\.load_defaults [0-9.]+$
4
4
  "\n config.add_autoload_paths_to_load_path = true"
5
5
  end
6
6
 
7
- gem 'hyrax', '5.2.0'
7
+ # In order to test app generation with local code, un-comment the line below and comment out the other gem definition
8
+ # Then run hyrax generation with the flag `-m /path/to/my/local/code/template.rb`
9
+ # gem 'hyrax', path: __dir__
10
+ gem 'hyrax', '5.3.0'
8
11
  run 'bundle install'
9
12
  generate 'hyrax:install', '-f'