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
@@ -2,6 +2,28 @@ $drawer-small: 40px;
2
2
  $drawer-large: 260px;
3
3
  $gutter-width: $grid-gutter-width/2;
4
4
 
5
+ // Backdrop overlay for mobile sidebar - automatically shows when sidebar is maximized
6
+ .sidebar-backdrop {
7
+ display: none;
8
+ position: fixed;
9
+ top: 0;
10
+ left: 0;
11
+ right: 0;
12
+ bottom: 0;
13
+ background-color: rgba(0, 0, 0, 0.5);
14
+ z-index: 1000;
15
+ opacity: 0;
16
+ transition: opacity 0.3s ease;
17
+
18
+ @media screen and (max-width: 991px) {
19
+ // Automatically show when sidebar is maximized preventing flash on page load
20
+ .sidebar.maximized.js-initialized ~ & {
21
+ display: block;
22
+ opacity: 1;
23
+ }
24
+ }
25
+ }
26
+
5
27
  .main-content {
6
28
  padding-left: $drawer-small + $gutter-width;
7
29
  padding-right: $gutter-width;
@@ -9,10 +31,29 @@ $gutter-width: $grid-gutter-width/2;
9
31
  background-color: $admin-content-background-color;
10
32
  position: absolute;
11
33
  width: 100%;
34
+ transition: opacity 0.3s ease;
12
35
 
13
36
  &.maximized {
14
37
  padding-left: $drawer-large + $gutter-width;
15
38
  }
39
+
40
+ // On mobile, sidebar overlays content instead of pushing it
41
+ @media screen and (max-width: 991px) {
42
+ padding-left: $drawer-small + $gutter-width;
43
+ // Keep padding the same even when sidebar is maximized
44
+ // Only grey out after JS has initialized preventing flash on page load
45
+ &.maximized:not(.js-initialized) {
46
+ padding-left: $drawer-small + $gutter-width;
47
+ opacity: 1;
48
+ pointer-events: auto;
49
+ }
50
+ &.maximized.js-initialized {
51
+ padding-left: $drawer-small + $gutter-width;
52
+ // Grey out and disable content when sidebar is open
53
+ opacity: 0.5;
54
+ pointer-events: none;
55
+ }
56
+ }
16
57
  }
17
58
 
18
59
  // .sidebar is a class on the div
@@ -25,6 +66,40 @@ $gutter-width: $grid-gutter-width/2;
25
66
  width: $drawer-small;
26
67
  z-index: 1000;
27
68
 
69
+ // On mobile sidebar overlays content when maximized
70
+ @media screen and (max-width: 991px) {
71
+ width: $drawer-small;
72
+
73
+ // Force sidebar to start collapsed on mobile preventing flash of open sidebar on page load
74
+ &:not(.js-initialized).maximized {
75
+ width: $drawer-small !important;
76
+
77
+ // Hide maximized content initially on mobile
78
+ .sidebar-action-text,
79
+ a span + span,
80
+ h5 {
81
+ display: none !important;
82
+ }
83
+
84
+ .h5,
85
+ .profile,
86
+ .collapse-toggle::after {
87
+ opacity: 0 !important;
88
+ }
89
+
90
+ .sidebar-toggle {
91
+ transform: none !important;
92
+ }
93
+ }
94
+
95
+ &.maximized.js-initialized {
96
+ position: fixed;
97
+ z-index: 1001;
98
+ box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
99
+ width: $drawer-large;
100
+ }
101
+ }
102
+
28
103
  &.maximized {
29
104
  width: $drawer-large;
30
105
 
@@ -83,12 +158,32 @@ $gutter-width: $grid-gutter-width/2;
83
158
  cursor: pointer;
84
159
  background-color: $body-background-color;
85
160
  border-radius: 50%;
86
- line-height: 18px;
161
+ border: none;
162
+ padding: 0;
163
+ line-height: 24px;
87
164
  position: absolute;
88
- right: -10px;
165
+ right: -12px;
89
166
  text-align: center;
90
- width: 18px;
167
+ width: 24px;
168
+ height: 24px;
169
+ min-width: 24px;
170
+ min-height: 24px;
91
171
  z-index: 1001;
172
+ display: flex;
173
+ align-items: center;
174
+ justify-content: center;
175
+
176
+ // Focus styles for keyboard navigation
177
+ &:focus {
178
+ outline: 2px solid #005fcc;
179
+ outline-offset: 2px;
180
+ box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #005fcc;
181
+ }
182
+
183
+ // Centered icon
184
+ .fa {
185
+ display: block;
186
+ }
92
187
  }
93
188
 
94
189
  a {
@@ -7,10 +7,7 @@ module Qa::Authorities
7
7
  def search(_q, controller)
8
8
  # The Hyrax::CollectionSearchBuilder expects a current_user
9
9
  return [] unless controller.current_user
10
- response, = search_response(controller)
11
- docs = response.documents
12
-
13
- docs.map do |doc|
10
+ search_documents(controller).map do |doc|
14
11
  { id: doc.id, label: doc.title, value: doc.id }
15
12
  end
16
13
  end
@@ -27,13 +24,16 @@ module Qa::Authorities
27
24
  )
28
25
  end
29
26
 
30
- def search_response(controller)
27
+ def search_documents(controller)
31
28
  access = controller.params[:access] || 'read'
29
+ builder = search_service(controller).search_builder
30
+ .with(q: controller.params[:q])
31
+ .with_access(access)
32
32
 
33
- search_service(controller).search_results do |builder|
34
- builder.with({ q: controller.params[:q] })
35
- .with_access(access)
36
- .rows(100)
33
+ Hyrax::SolrService.fetch_all do |rows, start|
34
+ controller.blacklight_config.repository.search(
35
+ builder.query.merge(rows: rows, start: start, fl: 'id,title_tesim,has_model_ssim')
36
+ )
37
37
  end
38
38
  end
39
39
  end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+ require 'google/cloud/errors'
3
+
4
+ module Hyrax
5
+ module AnalyticsErrorHandling
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ rescue_from Google::Cloud::PermissionDeniedError, with: :handle_analytics_permission_error
10
+ rescue_from Google::Cloud::Error, with: :handle_analytics_general_error
11
+ rescue_from StandardError, with: :handle_analytics_general_error
12
+ end
13
+
14
+ private
15
+
16
+ def handle_analytics_permission_error(exception)
17
+ Rails.logger.error "Analytics permission error: #{exception.message}"
18
+ @analytics_error = {
19
+ type: 'permission',
20
+ title: I18n.t('hyrax.admin.analytics.errors.permission.title'),
21
+ message: I18n.t('hyrax.admin.analytics.errors.permission.message'),
22
+ details: exception.message,
23
+ troubleshooting_steps: Array.wrap(I18n.t('hyrax.admin.analytics.errors.permission.troubleshooting_steps')),
24
+ documentation_url: I18n.t('hyrax.admin.analytics.errors.permission.documentation_url')
25
+ }
26
+ set_empty_analytics_data
27
+ render_analytics_with_error
28
+ end
29
+
30
+ def handle_analytics_general_error(exception)
31
+ raise exception unless analytics_related_error?(exception)
32
+
33
+ Rails.logger.error "Analytics error: #{exception.message}"
34
+
35
+ error_type = determine_error_type(exception)
36
+
37
+ @analytics_error = {
38
+ type: error_type,
39
+ title: I18n.t("hyrax.admin.analytics.errors.#{error_type}.title"),
40
+ message: I18n.t("hyrax.admin.analytics.errors.#{error_type}.message"),
41
+ details: exception.message,
42
+ troubleshooting_steps: Array.wrap(I18n.t("hyrax.admin.analytics.errors.#{error_type}.troubleshooting_steps")),
43
+ documentation_url: I18n.t("hyrax.admin.analytics.errors.#{error_type}.documentation_url")
44
+ }
45
+ set_empty_analytics_data
46
+ render_analytics_with_error
47
+ end
48
+
49
+ def determine_error_type(exception)
50
+ case exception.message
51
+ when /property.*not found/i, /invalid property/i
52
+ 'invalid_property'
53
+ when /authentication/i, /credentials/i, /unauthorized/i
54
+ 'authentication'
55
+ when /quota/i, /rate limit/i
56
+ 'quota_exceeded'
57
+ else
58
+ 'general'
59
+ end
60
+ end
61
+
62
+ def analytics_related_error?(exception)
63
+ return true if exception.is_a?(Google::Cloud::Error)
64
+ exception.message.include?('analytics') ||
65
+ exception.message.include?('Google') ||
66
+ exception.message.include?('property') ||
67
+ exception.backtrace&.any? { |line| line.include?('analytics') || line.include?('google') }
68
+ end
69
+
70
+ def set_empty_analytics_data
71
+ # Set empty data structures to prevent view errors
72
+ empty_data = { pageviews: [], work_page_views: [], downloads: [], all_top_collections: [],
73
+ top_collections: [], top_downloads: [], top_collection_pages: [], accessible_works: [],
74
+ accessible_file_sets: [], works_count: 0, top_works: [], top_file_set_downloads: [],
75
+ uniques: [], files: [] }
76
+
77
+ empty_data.each { |key, value| instance_variable_set("@#{key}", value) if instance_variable_get("@#{key}").nil? }
78
+ end
79
+
80
+ def render_analytics_with_error
81
+ respond_to do |format|
82
+ format.html { render :index }
83
+ format.csv { render plain: "Analytics data unavailable due to configuration error", status: :service_unavailable }
84
+ end
85
+ end
86
+
87
+ def safe_analytics_call(method_name, *args)
88
+ Hyrax::Analytics.send(method_name, *args)
89
+ rescue Google::Cloud::PermissionDeniedError, Google::Cloud::Error => e
90
+ Rails.logger.warn "Analytics call failed: #{method_name} - #{e.message}"
91
+ []
92
+ rescue StandardError => e
93
+ Rails.logger.warn "Unexpected analytics error: #{method_name} - #{e.message}"
94
+ []
95
+ end
96
+ end
97
+ end
@@ -3,7 +3,9 @@ module Hyrax
3
3
  module CollectionsControllerBehavior
4
4
  extend ActiveSupport::Concern
5
5
  include Blacklight::AccessControls::Catalog
6
- include Blacklight::Base
6
+ include Blacklight::Configurable
7
+ include Blacklight::SearchContext
8
+ include Hyrax::RedirectToDisplayUrl
7
9
 
8
10
  included do
9
11
  # include the display_trophy_link view helper method
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ ##
5
+ # A shared module for controllers to ensure that a resource has been fully
6
+ # migrated from a Wings-backed ActiveFedora object to a native Valkyrie resource
7
+ # before an action (like `edit`) is performed.
8
+ #
9
+ # This is a temporary solution to handle just-in-time migrations for legacy
10
+ # objects when `Hyrax.config.flexible?` is true.
11
+ #
12
+ # @example
13
+ # class MyController < ApplicationController
14
+ # include Hyrax::EnsureMigratedBehavior
15
+ #
16
+ # before_action do
17
+ # @resource = find_my_resource
18
+ # @resource = ensure_migrated(resource: @resource, form: build_form, transaction_key: 'my.transaction')
19
+ # end
20
+ # end
21
+ module EnsureMigratedBehavior
22
+ extend ActiveSupport::Concern
23
+
24
+ private
25
+
26
+ ##
27
+ # @param resource [Valkyrie::Resource] the resource to check
28
+ # @param transaction_key [String] the key for the update transaction
29
+ #
30
+ # @return [Valkyrie::Resource] the migrated resource if migration was needed,
31
+ # otherwise the original resource.
32
+ def ensure_migrated(resource:, transaction_key:)
33
+ return resource unless resource.flexible? && wings_backed?(resource)
34
+
35
+ # Create a minimal form object to run the transaction.
36
+ # We can't use the controller's form because it may fail to build
37
+ # for a Wings-backed object, creating a circular dependency.
38
+ form = Hyrax::Forms::ResourceForm.for(resource: resource)
39
+ result = transactions[transaction_key].call(form)
40
+
41
+ raise "Valkyrie lazy migration failed for #{resource.class} #{resource.id}: #{result.failure}" unless result.success?
42
+
43
+ result.value!
44
+ end
45
+
46
+ def wings_backed?(resource)
47
+ resource.respond_to?(:wings?) && resource.wings?
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ module FlexibleCatalogBehavior
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def load_flexible_schema
9
+ previous_profile, current_profile = Hyrax::FlexibleSchema.order("created_at asc").last(2).map(&:profile)
10
+ return if previous_profile.blank? && current_profile.blank?
11
+
12
+ current_profile = previous_profile if current_profile.nil?
13
+ remove_old_properties!(previous_profile['properties'], current_profile['properties'].keys) if current_profile != previous_profile
14
+ properties_hash = current_profile['properties']
15
+ properties_hash.each do |itemprop, prop|
16
+ label = display_label_for(itemprop, prop)
17
+
18
+ view_options = prop['view']
19
+ indexing = prop['indexing']
20
+ next if indexing.nil?
21
+
22
+ # prevents all restricted fields from being added to blacklight config
23
+ # to prevent them from being exposed in catalog search results.
24
+ # They remain available on show pages, based on visibility.
25
+ if restricted_field?(indexing)
26
+ remove_from_blacklight_config!(itemprop, indexing)
27
+ next
28
+ end
29
+
30
+ # `view.search_results: false` in the m3 profile hides this property from catalog
31
+ # search-result columns. Only gates the dynamic add_index_field path below;
32
+ # properties already declared in CatalogController are left untouched, and
33
+ # the `qf` (query-field relevance) list is unaffected. Facet registration below
34
+ # is intentionally not gated — a hidden property can still be facetable.
35
+ if catalog_indexable?(view_options) && stored_searchable?(indexing, itemprop)
36
+ index_args = { itemprop:, label: }
37
+
38
+ if facetable?(indexing, itemprop)
39
+ index_args[:link_to_facet] = "#{itemprop}_sim"
40
+ end
41
+
42
+ name = blacklight_config.index_fields.keys.detect { |key| key.start_with?(itemprop) }
43
+ name ||= "#{itemprop}_tesim"
44
+
45
+ # for properties that DO exist in the CatalogController
46
+ if blacklight_config.index_fields[name].present?
47
+ if label
48
+ blacklight_config.index_fields[name].label = label
49
+ blacklight_config.index_fields[name].custom_label = true
50
+ end
51
+ blacklight_config.index_fields[name].itemprop = itemprop
52
+
53
+ blacklight_config.index_fields[name].link_to_facet = index_args[:link_to_facet]
54
+
55
+ if require_view_helper_method?(view_options)
56
+ # add or update the helper method so linked fields will render correctly in the index view
57
+ blacklight_config.index_fields[name].helper_method = view_option_for_helper_method(view_options)
58
+ # the helper method for index_field_link needs the field name
59
+ blacklight_config.index_fields[name].field_name = itemprop
60
+ end
61
+ else
62
+ # for properties that DO NOT exist in the catalog controller
63
+ if require_view_helper_method?(view_options)
64
+ # add the view helper method to the arguments hash when creating a property
65
+ index_args[:helper_method] = view_option_for_helper_method(view_options)
66
+ # the helper method for index_field_link needs the field name
67
+ index_args[:field_name] = itemprop
68
+ end
69
+ # if a property in the metadata profile doesn't exist in the CatalogController, add it
70
+ blacklight_config.add_index_field(name, index_args)
71
+
72
+ # all index fields get this property so an admin can hide a property from the catalog search results
73
+ # by adding the name of the property via admin dashboard > Settings > Accounts > Hidden index fields
74
+ # NOTE: it is likely this will be handled by the metadata profile in the future
75
+ blacklight_config.index_fields[name].if = :render_optionally?
76
+ end
77
+
78
+ qf = blacklight_config.search_fields['all_fields'].solr_parameters[:qf]
79
+ unless qf.include?(name)
80
+ qf << " #{name}"
81
+ end
82
+ end
83
+
84
+ if facetable?(indexing, itemprop)
85
+ name = "#{itemprop}_sim"
86
+ unless blacklight_config.facet_fields[name].present?
87
+ blacklight_config.add_facet_field(name, label: label)
88
+ end
89
+ else
90
+ # if the property does not have facetable in the indexing section of the metadata profile, remove the facet field from the blacklight config
91
+ name = "#{itemprop}_sim"
92
+ blacklight_config.facet_fields.delete(name)
93
+ end
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ # Returns true if the view options require a helper method to render the linked field correctly in the index view
100
+ # @param view_options [Hash] the view options ex: {"render_as"=>"linked", "html_dl"=>true}
101
+ # @return [Boolean] to determine if the view_option_for_helper_method should be called
102
+ def require_view_helper_method?(view_options)
103
+ view_options.present? && %w[external_link linked rights_statement].include?(view_options.dig('render_as'))
104
+ end
105
+
106
+ # Returns the helper method that will render the linked field correctly in the index view
107
+ # @param view_options [Hash] the view options ex: {"render_as"=>"linked", "html_dl"=>true}
108
+ # @return [Symbol] helper method from Hyrax::HyraxHelperBehavior
109
+ def view_option_for_helper_method(view_options)
110
+ render_as = view_options.dig('render_as')
111
+ return :iconify_auto_link if render_as == 'external_link'
112
+ return :index_field_link if render_as == 'linked'
113
+ return :rights_statement_links if render_as == 'rights_statement'
114
+ end
115
+
116
+ def display_label_for(field_name, config)
117
+ display_label = config.fetch('display_label', {})&.with_indifferent_access || {}
118
+ display_label = { default: display_label } if display_label.is_a?(String)
119
+ display_label[:default] = field_name.to_s.humanize if display_label[:default].blank?
120
+ # Return a lambda so locale and translation are resolved at render time,
121
+ # not at initialize time (before before_action :set_locale runs).
122
+ lambda { |*|
123
+ label = display_label[I18n.locale] || display_label[:default]
124
+ I18n.t(label, default: label)
125
+ }
126
+ end
127
+
128
+ def stored_searchable?(indexing, itemprop)
129
+ indexing.include?('stored_searchable') || indexing.include?("#{itemprop}_tesim")
130
+ end
131
+
132
+ # True when the property declares `admin_only` or `editor_only` in its
133
+ # indexing array. Restricted fields are never exposed through the
134
+ # Blacklight catalog (no index column, no facet, no free-text match);
135
+ # visibility is enforced on show pages by the `field_visible?` view
136
+ # helper.
137
+ def restricted_field?(indexing)
138
+ indexing.include?("admin_only") || indexing.include?("editor_only")
139
+ end
140
+
141
+ # Returns false only when the m3 profile sets `view.search_results: false`
142
+ # for the property; absent or `true` keeps existing catalog-display behavior.
143
+ def catalog_indexable?(view_options)
144
+ return true unless view_options.is_a?(Hash)
145
+ view_options['search_results'] != false
146
+ end
147
+
148
+ def facetable?(indexing, itemprop)
149
+ indexing.include?('facetable')
150
+ end
151
+
152
+ def remove_old_properties!(previous_profile_properties, current_property_keys)
153
+ props = previous_profile_properties.keys - current_property_keys
154
+ props.each do |prop|
155
+ indexing = previous_profile_properties.dig(prop, 'indexing')
156
+ remove_from_blacklight_config!(prop, indexing)
157
+ end
158
+ end
159
+
160
+ # Evict every Blacklight registration for `itemprop`. Collects the set of
161
+ # Solr field names to remove from three sources:
162
+ # - "<itemprop>_tesim" — the default Solr field name used as the
163
+ # index field for this property,
164
+ # - "<itemprop>_sim" — the default Solr field name used as the
165
+ # facet field for this property,
166
+ # - any additional Solr-field names explicitly declared in `indexing:`
167
+ # (filtering out the directive flags `stored_searchable`, `facetable`,
168
+ # `admin_only`, and `editor_only`).
169
+ # Then removes those exact names from `facet_fields`, `index_fields`, and
170
+ # the all_fields qf. Exact-name matching avoids prefix collisions where
171
+ # e.g. `title` would otherwise match `title_alternative_*`.
172
+ INDEXING_DIRECTIVES = %w[stored_searchable facetable admin_only editor_only].freeze
173
+
174
+ def remove_from_blacklight_config!(itemprop, indexing = nil)
175
+ names = solr_field_names_for(itemprop, indexing)
176
+ names.each do |name|
177
+ blacklight_config.facet_fields.delete(name)
178
+ blacklight_config.index_fields.delete(name)
179
+ end
180
+
181
+ qf = blacklight_config.search_fields['all_fields']&.solr_parameters&.dig(:qf)
182
+ return if qf.nil?
183
+ names.each do |name|
184
+ qf.slice!(" #{name}")
185
+ qf.slice!(name)
186
+ end
187
+ end
188
+
189
+ def solr_field_names_for(itemprop, indexing)
190
+ default_fields = ["#{itemprop}_tesim", "#{itemprop}_sim"]
191
+ declared_fields = (indexing || []) - INDEXING_DIRECTIVES
192
+ (default_fields + declared_fields).uniq
193
+ end
194
+ end
195
+
196
+ def initialize
197
+ self.class.load_flexible_schema
198
+ super
199
+ end
200
+
201
+ # Hook to allow optional rendering at the app level
202
+ def render_optionally?
203
+ true
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ module FlexibleSchemaBehavior
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ before_action :set_latest_schema_version, only: [:new, :edit]
9
+ end
10
+
11
+ private
12
+
13
+ def set_latest_schema_version
14
+ @latest_schema_version = Hyrax::FlexibleSchema.current_schema_id.to_f
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ # Controller concern that sends visitors who land on a path with a
5
+ # redirect-table row pointing elsewhere to that other path. Included on
6
+ # work and collection show controllers via the curation-concerns
7
+ # behavior modules.
8
+ #
9
+ # Two request shapes hit this concern:
10
+ #
11
+ # - Direct UUID URL visits (`/concern/generic_works/<uuid>`) — Rails
12
+ # routes natively to the show action. If the resource has a display
13
+ # alias, the row keyed on the UUID URL has `to_path` pointing at the
14
+ # alias, and the before_action 301s the visitor there.
15
+ # - In-process dispatches from `Hyrax::RedirectsController` — the
16
+ # resolver finds a display-URL row and re-dispatches the request
17
+ # through `recognize_path` + `controller.dispatch`. To avoid the
18
+ # before_action firing again on the same logical request, the
19
+ # resolver sets `request.env['hyrax.redirects.dispatched'] = true`
20
+ # before dispatching; this concern checks the same env key and
21
+ # no-ops when set.
22
+ #
23
+ # See documentation/redirects.md.
24
+ module RedirectToDisplayUrl
25
+ extend ActiveSupport::Concern
26
+
27
+ included do
28
+ before_action :redirect_to_display_url_if_needed, only: :show
29
+ end
30
+
31
+ private
32
+
33
+ def redirect_to_display_url_if_needed
34
+ return if request.env['hyrax.redirects.dispatched']
35
+ return unless Hyrax.config.redirects_active?
36
+ row = Hyrax::RedirectsLookup.find_row(request.path)
37
+ return if row.blank?
38
+ return if row.to_path == request.path
39
+ redirect_to with_locale(row.to_path), status: :moved_permanently
40
+ end
41
+
42
+ # Carry the visitor's requested locale (set by Hyrax::Controller#set_locale
43
+ # from `params[:locale]`) across the 301 so the destination page renders in
44
+ # the same language. Appended as `?locale=<value>` so this works whether the
45
+ # host app uses path-prefix or query-string locales. The locale value is
46
+ # URL-encoded via Rack::Utils.build_query so attacker-controlled chars in
47
+ # params[:locale] can't inject extra query params into the Location header.
48
+ def with_locale(path)
49
+ return path if params[:locale].blank?
50
+ separator = path.include?('?') ? '&' : '?'
51
+ "#{path}#{separator}#{Rack::Utils.build_query(locale: params[:locale])}"
52
+ end
53
+ end
54
+ end