geoblacklight 5.3.0 → 5.4.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/app/components/geoblacklight/document/sidebar_component.html.erb +3 -0
  3. data/app/components/geoblacklight/document/sidebar_component.rb +7 -0
  4. data/app/components/geoblacklight/download_links_component.html.erb +3 -0
  5. data/app/components/geoblacklight/download_links_component.rb +1 -1
  6. data/app/components/geoblacklight/item_map_viewer_component.rb +2 -2
  7. data/app/components/geoblacklight/location_leaflet_map_component.rb +2 -2
  8. data/app/components/geoblacklight/relations_component.html.erb +1 -1
  9. data/app/components/geoblacklight/static_map_component.rb +2 -2
  10. data/app/controllers/download_controller.rb +6 -0
  11. data/app/controllers/relation_controller.rb +1 -1
  12. data/app/helpers/geoblacklight_helper.rb +31 -3
  13. data/app/javascript/geoblacklight/controllers/leaflet_viewer_controller.js +18 -3
  14. data/app/javascript/geoblacklight/controllers/openlayers_viewer_controller.js +33 -2
  15. data/app/javascript/geoblacklight/leaflet/basemaps.js +0 -32
  16. data/app/javascript/geoblacklight/leaflet/inspection.js +5 -4
  17. data/app/javascript/geoblacklight/openlayers/basemaps.js +0 -20
  18. data/app/javascript/geoblacklight/openlayers/layers.js +1 -1
  19. data/app/models/concerns/geoblacklight/solr_document.rb +19 -2
  20. data/app/views/catalog/_metadata.html.erb +3 -0
  21. data/geoblacklight.gemspec +2 -2
  22. data/lib/generators/geoblacklight/templates/catalog_controller.rb +4 -5
  23. data/lib/generators/geoblacklight/templates/settings.yml +19 -0
  24. data/lib/geoblacklight/deprecated_configuration.rb +665 -0
  25. data/lib/geoblacklight/download.rb +3 -0
  26. data/lib/geoblacklight/engine.rb +13 -0
  27. data/lib/geoblacklight/references.rb +6 -1
  28. data/lib/geoblacklight/relation/ancestors.rb +3 -0
  29. data/lib/geoblacklight/relation/descendants.rb +3 -0
  30. data/lib/geoblacklight/relation/relation_response.rb +4 -1
  31. data/lib/geoblacklight/version.rb +1 -1
  32. data/lib/geoblacklight.rb +16 -0
  33. data/lib/tasks/geoblacklight.rake +9 -0
  34. data/package.json +1 -1
  35. data/solr/conf/solrconfig.xml +10 -10
  36. data/spec/features/configurable_basemap_spec.rb +68 -0
  37. data/spec/features/relations_spec.rb +2 -3
  38. data/spec/lib/geoblacklight/deprecated_configuration_spec.rb +680 -0
  39. data/spec/solr_config_spec.rb +41 -0
  40. data/spec/spec_helper.rb +24 -1
  41. metadata +12 -9
@@ -0,0 +1,665 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "pathname"
5
+ require "yaml"
6
+
7
+ module Geoblacklight
8
+ ##
9
+ # Boot time checks for application configuration that GeoBlacklight 6 removes.
10
+ #
11
+ # These cases cannot be caught by deprecating a method, because nothing in
12
+ # GeoBlacklight calls them:
13
+ #
14
+ # * An overridden template keeps working on 5.x and is simply never rendered
15
+ # again on 6, so the customization disappears silently.
16
+ # * A file the install generator wrote into the application — its routes, its
17
+ # CatalogController, its layout — names a constant that 6 deletes, so the
18
+ # application, not GeoBlacklight, is what fails to boot.
19
+ # * A removed setting is only ever read by JavaScript, or by code that 6
20
+ # deletes outright.
21
+ #
22
+ # So we look at the application's own files and settings once, at boot. Where
23
+ # several problems live in the same file they are reported as a single warning:
24
+ # a stock 5.x application trips all of them at once and fixes them in one pass,
25
+ # and one line per problem is what makes a maintainer silence the deprecator.
26
+ module DeprecatedConfiguration
27
+ ##
28
+ # Templates GeoBlacklight 6 no longer ships, mapped to their replacement. An
29
+ # application that has copied one of these into app/views will keep the file
30
+ # after upgrading, but GeoBlacklight 6 will not render it.
31
+ TEMPLATES = {
32
+ "catalog/_metadata" => "use Geoblacklight::MetadataComponent instead",
33
+ "catalog/_relations_container" => "use Geoblacklight::Document::RelationsContainerComponent instead",
34
+ "relation/index" => "the controller is renamed, so the template moves to app/views/relations/index"
35
+ }.freeze
36
+
37
+ ##
38
+ # Settings keys GeoBlacklight 6 stops acting on, mapped to what happens instead.
39
+ # None of these does it read at all, so the line can come out, and nothing
40
+ # raises to say the customization stopped mattering.
41
+ SETTINGS = {
42
+ "DOWNLOAD_FORMATS" =>
43
+ "GeoBlacklight 6 removes the generated download subsystem and never reads it. Note the 4.x " \
44
+ "upgrade added this key because GeoBlacklight 5 required it",
45
+ "HELP_TEXT" =>
46
+ "GeoBlacklight 6 removes the viewer help text popovers along with ViewerHelpTextComponent and " \
47
+ "no longer reads the key at all",
48
+ "ICON_MAPPING" =>
49
+ "GeoBlacklight 6 drops the lookup from its geoblacklight_icon helper and removes the " \
50
+ "institution icon components this maps onto, so a provider named here renders as an empty span",
51
+ "LEAFLET" =>
52
+ "GeoBlacklight 6 replaces the leaflet and openlayers viewers with <ogm-viewer> and reads none " \
53
+ "of this section — including the BASEMAPS key 5.3 added for CARTO API keys, which becomes " \
54
+ "light_basemap_url and dark_basemap_url on Geoblacklight.configuration, each the URL of a " \
55
+ "MapLibre style document",
56
+ "SIDEBAR_STATIC_MAP" =>
57
+ "GeoBlacklight 6's StaticMapComponent decides for itself, from whether the record is " \
58
+ "previewable and georeferenced, and never reads this list of viewer protocols",
59
+ "TIMEOUT_DOWNLOAD" =>
60
+ "GeoBlacklight 6 removes the generated download subsystem and never reads it"
61
+ }.freeze
62
+
63
+ ##
64
+ # Settings whose default GeoBlacklight 6 changes. The installer only writes
65
+ # config/settings.yml once, so an application upgrading from 5.x carries its old
66
+ # value forward silently. We warn only when the value is still the 5.x default,
67
+ # so an application that has deliberately chosen a value is left alone.
68
+ CHANGED_SETTINGS = {}.freeze
69
+
70
+ ##
71
+ # Individual setting values that stop matching anything in GeoBlacklight 6.
72
+ STALE_SETTING_VALUES = {}.freeze
73
+
74
+ ##
75
+ # Settings that do not exist in 5.x but that GeoBlacklight 6 requires.
76
+ REQUIRED_SETTINGS = {}.freeze
77
+
78
+ ##
79
+ # Translation keys GeoBlacklight 6 stops looking up, keyed by prefix so that a
80
+ # whole family is reported as one line rather than 33. An application that
81
+ # translated or reworded any of these keeps the translation in its own locale
82
+ # file, where GeoBlacklight 6 never reads it again, so the customization
83
+ # disappears with no error.
84
+ LOCALE_KEYS = {
85
+ "blacklight.icon." =>
86
+ "GeoBlacklight 6 restructures its icons and ships no labels under blacklight.icon",
87
+ "geoblacklight.download." =>
88
+ "GeoBlacklight 6 keeps this namespace but renames its keys — download_link is now " \
89
+ "original_link, beside downloads, generated_link, iiif_image_link and iiif_manifest_link — " \
90
+ "and drops the rest with the generated download subsystem",
91
+ "geoblacklight.help_text.viewer_protocol." =>
92
+ "GeoBlacklight 6 removes the viewer help text popovers along with ViewerHelpTextComponent",
93
+ "geoblacklight.metadata.toggle_summary" =>
94
+ "GeoBlacklight 6 renders metadata through Geoblacklight::MetadataComponent",
95
+ "geoblacklight.relations.browse_all" =>
96
+ "GeoBlacklight 6 looks up geoblacklight.relations.browse.<field family> instead, so " \
97
+ "browse.member_of rather than browse.member_of_ancestors"
98
+ }.freeze
99
+
100
+ ##
101
+ # Relationship labels that GeoBlacklight 6 turns from a plain string into a
102
+ # pluralized one:/other: pair. I18n does not raise when a String is looked up
103
+ # with a count, so an application that reworded one keeps a label that never
104
+ # pluralizes.
105
+ PLURALIZED_LOCALE_KEYS = %w[
106
+ member_of_ancestors member_of_descendants part_of_ancestors part_of_descendants
107
+ relation_ancestors relation_descendants replaces_ancestors replaces_descendants
108
+ source_ancestors source_descendants version_of_ancestors version_of_descendants
109
+ ].map { |key| "geoblacklight.relations.#{key}" }.freeze
110
+
111
+ ##
112
+ # The attributes GeoBlacklight 6 declares on a RELATIONSHIPS_SHOWN entry. Its
113
+ # Geoblacklight::Configuration::RelationshipConfig is an ActiveModel, so an
114
+ # attribute that is neither declared here nor tolerated as one of
115
+ # RELATIONSHIP_ATTRIBUTES_REMOVED raises Geoblacklight::Exceptions::InvalidSettings
116
+ # while the configuration is built — before anything can rescue it.
117
+ RELATIONSHIP_ATTRIBUTES = %w[field inverse label query_type].freeze
118
+
119
+ ##
120
+ # Attributes GeoBlacklight 6 still accepts on a RELATIONSHIPS_SHOWN entry but does
121
+ # nothing with, mapped to what replaced them. It names these deliberately — every
122
+ # config/settings.yml generated since 4.0 sets icon — so they warn once per boot
123
+ # instead of raising, which is why they are worth reporting separately from an
124
+ # attribute that does raise.
125
+ RELATIONSHIP_ATTRIBUTES_REMOVED = {
126
+ "icon" => "GeoBlacklight 6 labels related records with a resource class badge instead"
127
+ }.freeze
128
+
129
+ ##
130
+ # Helpers the 5.x install generator names with `helper_method:` in the
131
+ # application's own CatalogController, and that GeoBlacklight 6 removes.
132
+ REMOVED_HELPER_METHODS = {
133
+ "snippit" => "GeoBlacklight 6 truncates index field values itself",
134
+ "render_value_as_truncate_abstract" =>
135
+ "use `component: Geoblacklight::MetadataDescriptionMarkdownComponent` instead"
136
+ }.freeze
137
+
138
+ ##
139
+ # Paths into the @geoblacklight/frontend package that the 5.x install generator
140
+ # wrote into the application's own stylesheets and entrypoints. GeoBlacklight 6
141
+ # ships plain CSS rather than Sass, so these resolve to nothing and the
142
+ # application's asset build fails rather than degrading quietly.
143
+ ASSET_REFERENCES = {
144
+ "@geoblacklight/frontend/app/assets/stylesheets/geoblacklight/geoblacklight" =>
145
+ "GeoBlacklight 6 ships plain CSS instead of Sass; import " \
146
+ "@geoblacklight/frontend/app/assets/stylesheets/geoblacklight.css instead",
147
+ # Matches both the Vite form, which keeps the package prefix, and the importmap
148
+ # form, which the 5.x generator rewrites to a bare path.
149
+ "images/blacklight/logo.svg" =>
150
+ "GeoBlacklight 6 moves the logo to images/geoblacklight/logo.svg, with " \
151
+ "images/geoblacklight/logo-dark.svg for dark mode"
152
+ }.freeze
153
+
154
+ ##
155
+ # Things the 5.x installed layout does that GeoBlacklight 6 no longer supports.
156
+ # Only the Vite generator copies base.html.erb into the application, but apps on
157
+ # either pipeline copy and adapt it, so match on content rather than on the path.
158
+ LAYOUT_REFERENCES = {
159
+ "shared/header_navbar" =>
160
+ "Blacklight 9 stops shipping the shared/_header_navbar partial this renders; move the masthead " \
161
+ "to blacklight_config.header_component, which is where GeoBlacklight 6 configures it",
162
+ "vite_client_tag" =>
163
+ "GeoBlacklight 6 drops the vite_rails dependency and ships no Vite entrypoints; move the layout to " \
164
+ "the stylesheet and javascript tags your asset pipeline provides"
165
+ }.freeze
166
+
167
+ ##
168
+ # Where an application keeps its layouts.
169
+ LAYOUT_GLOBS = ["app/views/layouts/**/*.{erb,haml}"].freeze
170
+
171
+ ##
172
+ # Where an application keeps the asset files the install generator wrote or edited.
173
+ ASSET_GLOBS = [
174
+ "app/assets/stylesheets/**/*.{css,scss}",
175
+ "app/javascript/**/*.{js,css,scss}"
176
+ ].freeze
177
+
178
+ ##
179
+ # GeoBlacklight names an application will only mention if it has subclassed,
180
+ # rendered or configured them itself, and that GeoBlacklight 6 removes or moves.
181
+ # Every one of these raises NameError or NoMethodError rather than degrading, so
182
+ # they are worth finding before the upgrade rather than after it.
183
+ #
184
+ # Geoblacklight::IconFacetItemComponent is deliberately absent: every generated
185
+ # CatalogController names it, and #catalog_controller_problems already reports it
186
+ # with the facets it is attached to.
187
+ REMOVED_CONSTANTS = {
188
+ "Geoblacklight::AccordionComponent" => "it is removed without replacement",
189
+ "Geoblacklight::AttributeTableComponent" =>
190
+ "the inspect table is drawn inside the <ogm-viewer> element in GeoBlacklight 6",
191
+ "Geoblacklight::DownloadLinksComponent" => "use Geoblacklight::Document::DownloadLinksComponent",
192
+ "Geoblacklight::IndexMapInspectComponent" =>
193
+ "index map inspection happens inside <ogm-viewer> in GeoBlacklight 6",
194
+ "Geoblacklight::IndexMapLegendComponent" =>
195
+ "the legend is emitted by <ogm-viewer> in GeoBlacklight 6",
196
+ "Geoblacklight::LocationLeafletMapComponent" =>
197
+ "use Geoblacklight::OverviewMapComponent for many records, or " \
198
+ "Geoblacklight::LocatorMapComponent for one",
199
+ "Geoblacklight::RelationsComponent" => "use Geoblacklight::Relations::RelationsComponent",
200
+ "Geoblacklight::ViewerHelpTextComponent" => "it is removed without replacement",
201
+ "Geoblacklight::Relation::" => "the namespace is renamed to Geoblacklight::Relations::",
202
+ "Geoblacklight::CsvDownload" => "GeoBlacklight 6 removes the generated download subsystem",
203
+ "Geoblacklight::GeojsonDownload" => "GeoBlacklight 6 removes the generated download subsystem",
204
+ "Geoblacklight::GeotiffDownload" => "GeoBlacklight 6 removes the generated download subsystem",
205
+ "Geoblacklight::KmzDownload" => "GeoBlacklight 6 removes the generated download subsystem",
206
+ "Geoblacklight::ShapefileDownload" => "GeoBlacklight 6 removes the generated download subsystem",
207
+ "RelationController" => "it is renamed to RelationsController",
208
+ "index_fields_display" =>
209
+ "use Geoblacklight::SearchResultComponent#description, which renders the description as Markdown"
210
+ }.freeze
211
+
212
+ ##
213
+ # Where an application keeps code that could name one of those.
214
+ CODE_GLOBS = [
215
+ "app/**/*.{rb,erb}",
216
+ "config/initializers/*.rb",
217
+ "lib/**/*.rb"
218
+ ].freeze
219
+
220
+ ##
221
+ # Constants the 5.x install generator wrote into the application's own
222
+ # config/routes.rb and that GeoBlacklight 6 deletes. The application's routes
223
+ # file is not something GeoBlacklight rewrites on upgrade, so these raise
224
+ # NameError while Rails draws the routes — before any of our other checks run.
225
+ ROUTE_CONSTANTS = {
226
+ "Geoblacklight::Routes::Downloadable" =>
227
+ "GeoBlacklight 6 removes the generated download subsystem, so delete the `concern " \
228
+ ":gbl_downloadable`, the `namespace :download` block and `resources :download, only: [:show]`"
229
+ }.freeze
230
+
231
+ ##
232
+ # Version floors GeoBlacklight 6 raises, as {library => requirement}. Unlike
233
+ # everything else here these are not a choice the application made, and the
234
+ # failure does not look like a GeoBlacklight problem: bundler simply refuses to
235
+ # resolve, or Blacklight renders facets the stylesheets no longer match.
236
+ GEM_REQUIREMENTS = {
237
+ "Blacklight" => {
238
+ minimum: "9.1",
239
+ because: "GeoBlacklight 6 calls blacklight_config.dark_mode_support and resolves per-field " \
240
+ "layout components, both of which Blacklight added in 9.1, and its geosearch facet and " \
241
+ "styling are written against Blacklight 9's accordion facets"
242
+ },
243
+ "Rails" => {
244
+ minimum: "8.0",
245
+ because: "GeoBlacklight 6 drops support for earlier versions"
246
+ }
247
+ }.freeze
248
+
249
+ ##
250
+ # Warn about everything we can see, once per boot.
251
+ # @param root [Pathname] the application root to inspect
252
+ def self.warn!(root = Rails.root)
253
+ if root
254
+ warn_about_templates(root)
255
+ warn_about_locale_keys(root)
256
+ warn_about_routes(root)
257
+ warn_about_asset_references(root)
258
+ warn_about_frontend_package(root)
259
+ warn_about_removed_constants(root)
260
+ warn_about_layouts(root)
261
+ end
262
+ warn_about_settings_file
263
+ warn_about_catalog_controller
264
+ warn_about_gem_requirements
265
+ end
266
+
267
+ ##
268
+ # @param root [Pathname] the application root to inspect
269
+ def self.warn_about_templates(root)
270
+ TEMPLATES.each do |template, advice|
271
+ Dir.glob(File.join(root, "app", "views", "#{template}.*")).sort.each do |override|
272
+ Geoblacklight.deprecation.warn(
273
+ "#{relative_to(override, root)} overrides #{template}, which is removed in " \
274
+ "GeoBlacklight 6; #{advice}"
275
+ )
276
+ end
277
+ end
278
+ end
279
+
280
+ ##
281
+ # @param root [Pathname] the application root to inspect
282
+ def self.warn_about_locale_keys(root)
283
+ Dir.glob(File.join(root, "config", "locales", "**", "*.{yml,yaml}")).sort.each do |path|
284
+ defined_keys = locale_keys(path)
285
+ next if defined_keys.empty?
286
+
287
+ problems = LOCALE_KEYS.filter_map do |prefix, advice|
288
+ matched = defined_keys.select { |key| key.start_with?(prefix) }
289
+ next if matched.empty?
290
+
291
+ subject = (matched.size > 1) ? "the #{matched.size} #{prefix}* keys" : matched.first
292
+ "stop translating #{subject}, because #{advice}"
293
+ end
294
+
295
+ pluralized = defined_keys & PLURALIZED_LOCALE_KEYS
296
+ if pluralized.any?
297
+ problems << "give the #{pluralized.size} geoblacklight.relations.* #{"label".pluralize(pluralized.size)} " \
298
+ "a one:/other: pair, because GeoBlacklight 6 looks them up with a count and a plain string " \
299
+ "silently never pluralizes"
300
+ end
301
+
302
+ next if problems.empty?
303
+
304
+ Geoblacklight.deprecation.warn(
305
+ "#{relative_to(path, root)} needs these changes before GeoBlacklight 6: #{problems.join("; ")}"
306
+ )
307
+ end
308
+ end
309
+
310
+ ##
311
+ # @param root [Pathname] the application root to inspect
312
+ def self.warn_about_routes(root)
313
+ path = File.join(root, "config", "routes.rb")
314
+ return unless File.exist?(path)
315
+
316
+ contents = File.read(path)
317
+ problems = ROUTE_CONSTANTS.filter_map { |constant, advice| advice if contents.include?(constant) }
318
+ return if problems.empty?
319
+
320
+ Geoblacklight.deprecation.warn(
321
+ "#{relative_to(path, root)} needs these changes before GeoBlacklight 6: #{problems.join("; ")}"
322
+ )
323
+ rescue SystemCallError
324
+ # An unreadable routes file is somebody else's problem; a boot time diagnostic
325
+ # must never be the reason an application fails to start.
326
+ nil
327
+ end
328
+
329
+ ##
330
+ # The install generator pins @geoblacklight/frontend to the gem's own version, so
331
+ # `yarn upgrade` keeps the 5.x package: every import then resolves to GeoBlacklight
332
+ # 5 assets against GeoBlacklight 6 markup, with nothing raising.
333
+ # @param root [Pathname] the application root to inspect
334
+ def self.warn_about_frontend_package(root)
335
+ path = File.join(root, "package.json")
336
+ return unless File.exist?(path)
337
+
338
+ pinned = JSON.parse(File.read(path)).dig("dependencies", "@geoblacklight/frontend")
339
+ return if pinned.nil? || pinned.to_s.delete("^0-9.").to_f >= 6
340
+
341
+ Geoblacklight.deprecation.warn(
342
+ "#{relative_to(path, root)} pins @geoblacklight/frontend to #{pinned}; GeoBlacklight 6 needs the " \
343
+ "matching 6.x package, and `yarn upgrade` will not cross the major on its own"
344
+ )
345
+ rescue JSON::ParserError, SystemCallError
346
+ # A package.json we cannot read is not ours to complain about.
347
+ nil
348
+ end
349
+
350
+ ##
351
+ # One warning per layout, listing everything in it GeoBlacklight 6 drops.
352
+ # @param root [Pathname] the application root to inspect
353
+ def self.warn_about_layouts(root)
354
+ LAYOUT_GLOBS.flat_map { |glob| Dir.glob(File.join(root, glob)) }.uniq.sort.each do |path|
355
+ problems = dead_layout_references(path)
356
+ next if problems.empty?
357
+
358
+ Geoblacklight.deprecation.warn(
359
+ "#{relative_to(path, root)} needs these changes before GeoBlacklight 6: #{problems.join("; ")}"
360
+ )
361
+ end
362
+ end
363
+
364
+ ##
365
+ # @param path [String]
366
+ # @return [Array<String>]
367
+ def self.dead_layout_references(path)
368
+ contents = File.read(path)
369
+ LAYOUT_REFERENCES.filter_map do |reference, advice|
370
+ "stop using #{reference}, because #{advice}" if contents.include?(reference)
371
+ end
372
+ rescue SystemCallError
373
+ # An unreadable layout is somebody else's problem; a boot time diagnostic must
374
+ # never be the reason an application fails to start.
375
+ []
376
+ end
377
+
378
+ ##
379
+ # One warning per file, listing every removed GeoBlacklight name it mentions.
380
+ # @param root [Pathname] the application root to inspect
381
+ def self.warn_about_removed_constants(root)
382
+ CODE_GLOBS.flat_map { |glob| Dir.glob(File.join(root, glob)) }.uniq.sort.each do |path|
383
+ problems = removed_constants_in(path)
384
+ next if problems.empty?
385
+
386
+ Geoblacklight.deprecation.warn(
387
+ "#{relative_to(path, root)} needs these changes before GeoBlacklight 6: #{problems.join("; ")}"
388
+ )
389
+ end
390
+ end
391
+
392
+ ##
393
+ # @param path [String]
394
+ # @return [Array<String>]
395
+ def self.removed_constants_in(path)
396
+ contents = File.read(path)
397
+ REMOVED_CONSTANTS.filter_map do |name, advice|
398
+ "stop referring to #{name}, because #{advice}" if contents.include?(name)
399
+ end
400
+ rescue SystemCallError
401
+ # An unreadable file is somebody else's problem; a boot time diagnostic must
402
+ # never be the reason an application fails to start.
403
+ []
404
+ end
405
+
406
+ ##
407
+ # One warning per asset file, listing every dead reference in it.
408
+ # @param root [Pathname] the application root to inspect
409
+ def self.warn_about_asset_references(root)
410
+ ASSET_GLOBS.flat_map { |glob| Dir.glob(File.join(root, glob)) }.uniq.sort.each do |path|
411
+ problems = dead_asset_references(path)
412
+ next if problems.empty?
413
+
414
+ Geoblacklight.deprecation.warn(
415
+ "#{relative_to(path, root)} needs these changes before GeoBlacklight 6: #{problems.join("; ")}"
416
+ )
417
+ end
418
+ end
419
+
420
+ ##
421
+ # @param path [String]
422
+ # @return [Array<String>]
423
+ def self.dead_asset_references(path)
424
+ contents = File.read(path)
425
+ ASSET_REFERENCES.filter_map do |reference, advice|
426
+ "stop importing #{reference}, because #{advice}" if contents.include?(reference)
427
+ end
428
+ rescue SystemCallError
429
+ # An unreadable asset file is somebody else's problem; a boot time diagnostic
430
+ # must never be the reason an application fails to start.
431
+ []
432
+ end
433
+
434
+ ##
435
+ # Everything the application's own CatalogController needs before GeoBlacklight 6,
436
+ # reported as a single to-do list.
437
+ def self.warn_about_catalog_controller
438
+ return unless defined?(::CatalogController)
439
+
440
+ problems = catalog_controller_problems(::CatalogController)
441
+ return if problems.empty?
442
+
443
+ Geoblacklight.deprecation.warn(
444
+ "app/controllers/catalog_controller.rb needs these changes before GeoBlacklight 6: " +
445
+ problems.join("; ")
446
+ )
447
+ rescue
448
+ # A boot time diagnostic must never be the reason an application fails to start.
449
+ nil
450
+ end
451
+
452
+ ##
453
+ # @param controller [Class]
454
+ # @return [Array<String>]
455
+ def self.catalog_controller_problems(controller)
456
+ config = controller.blacklight_config
457
+ problems = []
458
+
459
+ icon_facets = config.facet_fields.select do |_key, field|
460
+ field.item_component.to_s == "Geoblacklight::IconFacetItemComponent"
461
+ end
462
+ if icon_facets.any?
463
+ problems << "stop passing `item_component: Geoblacklight::IconFacetItemComponent` to " \
464
+ "#{icon_facets.keys.join(", ")}, because GeoBlacklight 6 removes that class and the reference " \
465
+ "will not resolve"
466
+ end
467
+
468
+ # index_fields and show_fields are keyed by Solr field, so the same key can
469
+ # appear in both with different helpers; check them separately.
470
+ {"index" => config.index_fields, "show" => config.show_fields}.each do |kind, fields|
471
+ fields.each do |key, field|
472
+ advice = REMOVED_HELPER_METHODS[field.helper_method.to_s]
473
+ next unless advice
474
+ problems << "stop passing `helper_method: :#{field.helper_method}` to the #{key} #{kind} field, " \
475
+ "because GeoBlacklight 6 removes that helper; #{advice}"
476
+ end
477
+ end
478
+
479
+ basemap = config.basemap_provider
480
+ if basemap.present? && basemap.to_s != "positron"
481
+ problems << "translate `config.basemap_provider = #{basemap.to_s.inspect}` into the " \
482
+ "light_basemap_url and dark_basemap_url that GeoBlacklight 6 reads from " \
483
+ "Geoblacklight.configuration, which take the URL of a MapLibre style document rather than " \
484
+ "a preset name; left here it reaches nothing and <ogm-viewer> draws its own basemap"
485
+ end
486
+
487
+ problems
488
+ end
489
+
490
+ ##
491
+ # Warn when the application is on a version of Blacklight or Rails that
492
+ # GeoBlacklight 6 will not run against. One warning per library, because they
493
+ # are fixed in different lines of the Gemfile and each is its own upgrade.
494
+ # @param versions [Hash{String=>String,nil}]
495
+ def self.warn_about_gem_requirements(versions = current_gem_versions)
496
+ gem_requirement_problems(versions).each do |problem|
497
+ Geoblacklight.deprecation.warn(problem)
498
+ end
499
+ end
500
+
501
+ ##
502
+ # @return [Hash{String=>String,nil}]
503
+ def self.current_gem_versions
504
+ {
505
+ "Blacklight" => (::Blacklight::VERSION if defined?(::Blacklight::VERSION)),
506
+ "Rails" => (::Rails::VERSION::STRING if defined?(::Rails::VERSION::STRING))
507
+ }
508
+ end
509
+
510
+ ##
511
+ # @param versions [Hash{String=>String,nil}]
512
+ # @return [Array<String>]
513
+ def self.gem_requirement_problems(versions)
514
+ GEM_REQUIREMENTS.filter_map do |library, requirement|
515
+ current = versions[library]
516
+ # An unreadable version is not evidence of a problem, so say nothing.
517
+ next unless current && Gem::Version.correct?(current)
518
+ next if Gem::Version.new(current) >= Gem::Version.new(requirement[:minimum])
519
+
520
+ "#{library} #{current} is too old for GeoBlacklight 6, which requires #{library} " \
521
+ "#{requirement[:minimum]} or later, because #{requirement[:because]}"
522
+ end
523
+ end
524
+
525
+ ##
526
+ # Everything the application's own config/settings.yml needs before
527
+ # GeoBlacklight 6, as a single to-do list. A generated 5.x settings file trips
528
+ # several of these at once and they are all fixed in the one file.
529
+ def self.warn_about_settings_file
530
+ return unless defined?(::Settings)
531
+
532
+ problems = settings_problems
533
+ return if problems.empty?
534
+
535
+ Geoblacklight.deprecation.warn(
536
+ "config/settings.yml needs these changes before GeoBlacklight 6: " + problems.join("; ")
537
+ )
538
+ end
539
+
540
+ ##
541
+ # @return [Array<String>]
542
+ def self.settings_problems
543
+ problems = relationships_shown_problems
544
+
545
+ removed = SETTINGS.select { |setting, _| setting_present?(setting) }
546
+ removed.each { |setting, advice| problems << "remove Settings.#{setting}, because #{advice}" }
547
+
548
+ CHANGED_SETTINGS.each do |setting, change|
549
+ next unless setting_value(setting) == change[:from]
550
+ problems << "Settings.#{setting} is still the GeoBlacklight 5 default #{change[:from].inspect}; " \
551
+ "GeoBlacklight 6 uses #{change[:to].inspect} because #{change[:because]}"
552
+ end
553
+
554
+ REQUIRED_SETTINGS.each do |setting, advice|
555
+ next unless setting_value(setting).nil?
556
+ problems << "set Settings.#{setting}, because #{advice}"
557
+ end
558
+
559
+ STALE_SETTING_VALUES.each do |setting, stale|
560
+ next unless Array(setting_value(setting)).include?(stale[:value])
561
+ problems << "Settings.#{setting} lists #{stale[:value].inspect}, which never matches in " \
562
+ "GeoBlacklight 6; #{stale[:because]}"
563
+ end
564
+
565
+ problems.concat(uppercase_convention_problems)
566
+ problems
567
+ end
568
+
569
+ ##
570
+ # GeoBlacklight 6 resolves settings case-insensitively and prefers lowercase; an
571
+ # uppercase key still works but makes 6 emit a deprecation for every one of them.
572
+ # Renaming is not safe on its own, because the generated CatalogController and
573
+ # SolrDocument read Settings.UPPERCASE directly.
574
+ # @return [Array<String>]
575
+ def self.uppercase_convention_problems
576
+ uppercase = ::Settings.to_h.keys.map(&:to_s).select { |key| key.match?(/[A-Z]/) }
577
+ return [] if uppercase.empty?
578
+
579
+ ["rename the #{uppercase.size} uppercase top level keys to lowercase, because GeoBlacklight 6 " \
580
+ "resolves settings case-insensitively and warns once per uppercase key it has to fall back to; " \
581
+ "convert the Settings.UPPERCASE reads in app/controllers/catalog_controller.rb and " \
582
+ "app/models/solr_document.rb in the same pass, or the application will not boot"]
583
+ end
584
+
585
+ ##
586
+ # @return [Array<String>]
587
+ def self.relationships_shown_problems
588
+ entries = ::Settings.RELATIONSHIPS_SHOWN
589
+ return [] unless entries.respond_to?(:to_h)
590
+ entries = entries.to_h
591
+ return [] if entries.empty?
592
+
593
+ problems = []
594
+
595
+ attributes = entries.each_value.flat_map { |entry|
596
+ entry.respond_to?(:to_h) ? entry.to_h.keys.map(&:to_s) : []
597
+ }.uniq
598
+
599
+ RELATIONSHIP_ATTRIBUTES_REMOVED.slice(*attributes).each do |attribute, advice|
600
+ problems << "delete the #{attribute} attribute from RELATIONSHIPS_SHOWN, because #{advice} " \
601
+ "and only warns, once a boot, that the key is dead"
602
+ end
603
+
604
+ unrecognized = attributes - RELATIONSHIP_ATTRIBUTES - RELATIONSHIP_ATTRIBUTES_REMOVED.keys
605
+ if unrecognized.any?
606
+ problems << "remove the #{unrecognized.sort.join(", ")} " \
607
+ "#{"attribute".pluralize(unrecognized.size)} from RELATIONSHIPS_SHOWN, which GeoBlacklight 6 " \
608
+ "does not recognize — its RelationshipConfig accepts only " \
609
+ "#{RELATIONSHIP_ATTRIBUTES.join(", ")}, so building the configuration raises " \
610
+ "Geoblacklight::Exceptions::InvalidSettings on boot and again on every search"
611
+ end
612
+
613
+ problems
614
+ end
615
+
616
+ ##
617
+ # The dotted keys a locale file defines, with the leading locale dropped so that a
618
+ # translation into any locale matches.
619
+ # @param path [String]
620
+ # @return [Array<String>]
621
+ def self.locale_keys(path)
622
+ loaded = YAML.load_file(path, aliases: true)
623
+ return [] unless loaded.is_a?(Hash)
624
+
625
+ loaded.each_value.flat_map { |tree| tree.is_a?(Hash) ? dotted_keys(tree) : [] }
626
+ rescue
627
+ # A locale file we cannot parse is not ours to complain about.
628
+ []
629
+ end
630
+
631
+ ##
632
+ # @param tree [Hash]
633
+ # @return [Array<String>]
634
+ def self.dotted_keys(tree, prefix = [])
635
+ tree.flat_map do |key, value|
636
+ path = prefix + [key.to_s]
637
+ value.is_a?(Hash) ? dotted_keys(value, path) : [path.join(".")]
638
+ end
639
+ end
640
+ private_class_method :dotted_keys
641
+
642
+ ##
643
+ # Walk a dotted settings path without raising when an ancestor is missing.
644
+ # @param setting [String] e.g. "LEAFLET.VIEWERS"
645
+ # @return [Object, nil] the value, or nil if any step of the path is missing
646
+ def self.setting_value(setting)
647
+ setting.split(".").reduce(::Settings) do |node, key|
648
+ return nil unless node.respond_to?(key)
649
+ node.public_send(key)
650
+ end
651
+ end
652
+
653
+ ##
654
+ # @param setting [String]
655
+ # @return [Boolean]
656
+ def self.setting_present?(setting)
657
+ setting_value(setting).present?
658
+ end
659
+
660
+ def self.relative_to(path, root)
661
+ Pathname.new(path).relative_path_from(Pathname.new(root)).to_s
662
+ end
663
+ private_class_method :relative_to
664
+ end
665
+ end
@@ -3,6 +3,9 @@
3
3
  module Geoblacklight
4
4
  class Download
5
5
  def initialize(document, options = {})
6
+ Geoblacklight.deprecation.warn(
7
+ "#{self.class} is deprecated; GeoBlacklight 6 removes the generated download subsystem: downloads are direct links only, rendered by Geoblacklight::Document::DownloadLinksComponent"
8
+ )
6
9
  @document = document
7
10
  @options = options
8
11
  end