blacklight 7.25.1 → 7.25.2
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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +1 -1
- data/.rubocop.yml +1 -0
- data/VERSION +1 -1
- data/app/components/blacklight/search_bar_component.rb +5 -1
- data/app/controllers/concerns/blacklight/bookmarks.rb +6 -3
- data/app/helpers/blacklight/blacklight_helper_behavior.rb +12 -1
- data/app/helpers/blacklight/catalog_helper_behavior.rb +1 -0
- data/app/models/blacklight/icon.rb +7 -1
- data/app/services/blacklight/search_service.rb +4 -0
- data/app/views/catalog/_previous_next_doc.html.erb +1 -0
- data/app/views/catalog/_search_form.html.erb +1 -1
- data/app/views/catalog/_show_main_content.html.erb +1 -1
- data/app/views/catalog/show.html.erb +1 -1
- data/blacklight.gemspec +1 -0
- data/lib/blacklight/configuration.rb +8 -1
- data/lib/blacklight/parameters.rb +97 -1
- data/lib/blacklight/search_builder.rb +3 -2
- data/lib/blacklight/search_state/filter_field.rb +12 -6
- data/lib/blacklight/search_state.rb +10 -64
- data/spec/controllers/bookmarks_controller_spec.rb +2 -1
- data/spec/helpers/blacklight/render_constraints_helper_behavior_spec.rb +1 -1
- data/spec/lib/blacklight/parameters_spec.rb +87 -0
- data/spec/lib/blacklight/search_state/filter_field_spec.rb +1 -14
- data/spec/lib/blacklight/search_state_spec.rb +1 -14
- data/spec/models/blacklight/search_builder_spec.rb +10 -1
- data/spec/models/blacklight/solr/search_builder_spec.rb +3 -1
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8c6c1570bf376880fa59c30420730762bf4694b4681856c9b367a6ecd2bb4bb7
|
|
4
|
+
data.tar.gz: 459f8098798a27f3474f2036143adc65d80955f16136cda54a2a244f86223003
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2fee85026a8eff0f283ea7f4d8936248872cbe0e3e87793ecfc13d479f0233723db03fbc01d418f5e242899a909c115b104fd6b9a680a4d21c5132cfabf2d030
|
|
7
|
+
data.tar.gz: e0a50dbea2c14447fb1f43b77e1f36f0b93974bfba525af0a9f677252fa19f064a4efd37ef913cd917e4d0d063df3c1600523e8d98be94c15ad083a75c4d1944
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -43,7 +43,7 @@ jobs:
|
|
|
43
43
|
- name: Run tests
|
|
44
44
|
run: bundle exec rake ci
|
|
45
45
|
env:
|
|
46
|
-
ENGINE_CART_RAILS_OPTIONS: '--skip-git --skip-listen --skip-spring --skip-keeps --skip-action-cable --skip-coffee --skip-test'
|
|
46
|
+
ENGINE_CART_RAILS_OPTIONS: '-a propshaft --skip-git --skip-listen --skip-spring --skip-keeps --skip-action-cable --skip-coffee --skip-test'
|
|
47
47
|
test_bootstrap5:
|
|
48
48
|
runs-on: ubuntu-latest
|
|
49
49
|
strategy:
|
data/.rubocop.yml
CHANGED
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.25.
|
|
1
|
+
7.25.2
|
|
@@ -10,7 +10,7 @@ module Blacklight
|
|
|
10
10
|
# rubocop:disable Metrics/ParameterLists
|
|
11
11
|
def initialize(
|
|
12
12
|
url:, params:,
|
|
13
|
-
advanced_search_url: nil,
|
|
13
|
+
advanced_search_url: nil, presenter: nil,
|
|
14
14
|
classes: ['search-query-form'], prefix: nil,
|
|
15
15
|
method: 'GET', q: nil, query_param: :q,
|
|
16
16
|
search_field: nil, search_fields: nil, autocomplete_path: nil,
|
|
@@ -29,6 +29,10 @@ module Blacklight
|
|
|
29
29
|
@autofocus = autofocus
|
|
30
30
|
@search_fields = search_fields
|
|
31
31
|
@i18n = i18n
|
|
32
|
+
return if presenter.nil?
|
|
33
|
+
|
|
34
|
+
Deprecation.warn(self, 'SearchBarComponent no longer uses a SearchBarPresenter, the presenter: param will be removed in 8.0. ' \
|
|
35
|
+
'Set advanced_search.enabled, autocomplete_enabled, and enable_search_bar_autofocus on BlacklightConfiguration')
|
|
32
36
|
end
|
|
33
37
|
# rubocop:enable Metrics/ParameterLists
|
|
34
38
|
|
|
@@ -75,12 +75,15 @@ module Blacklight::Bookmarks
|
|
|
75
75
|
|
|
76
76
|
current_or_guest_user.save! unless current_or_guest_user.persisted?
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
bookmarks_to_add = @bookmarks.reject { |bookmark| current_or_guest_user.bookmarks.where(bookmark).exists? }
|
|
79
|
+
success = ActiveRecord::Base.transaction do
|
|
80
|
+
current_or_guest_user.bookmarks.create!(bookmarks_to_add)
|
|
81
|
+
rescue ActiveRecord::RecordInvalid
|
|
82
|
+
false
|
|
80
83
|
end
|
|
81
84
|
|
|
82
85
|
if request.xhr?
|
|
83
|
-
success ? render(json: { bookmarks: { count: current_or_guest_user.bookmarks.count } }) : render(
|
|
86
|
+
success ? render(json: { bookmarks: { count: current_or_guest_user.bookmarks.count } }) : render(json: current_or_guest_user.errors.full_messages, status: "500")
|
|
84
87
|
else
|
|
85
88
|
if @bookmarks.any? && success
|
|
86
89
|
flash[:notice] = I18n.t('blacklight.bookmarks.add.success', count: @bookmarks.length)
|
|
@@ -79,7 +79,18 @@ module Blacklight::BlacklightHelperBehavior
|
|
|
79
79
|
# Render the search navbar
|
|
80
80
|
# @return [String]
|
|
81
81
|
def render_search_bar
|
|
82
|
-
|
|
82
|
+
if search_bar_presenter_class == Blacklight::SearchBarPresenter && partial_from_blacklight?(Blacklight::SearchBarPresenter.partial)
|
|
83
|
+
component_class = blacklight_config&.view_config(document_index_view_type)&.search_bar_component || Blacklight::SearchBarComponent
|
|
84
|
+
component_class.new(
|
|
85
|
+
url: search_action_url,
|
|
86
|
+
advanced_search_url: search_action_url(action: 'advanced_search'),
|
|
87
|
+
params: search_state.params_for_search.except(:qt),
|
|
88
|
+
search_fields: Deprecation.silence(Blacklight::ConfigurationHelperBehavior) { search_fields },
|
|
89
|
+
autocomplete_path: search_action_path(action: :suggest)
|
|
90
|
+
)
|
|
91
|
+
else
|
|
92
|
+
search_bar_presenter.render
|
|
93
|
+
end
|
|
83
94
|
end
|
|
84
95
|
deprecation_deprecate render_search_bar: "Call `render Blacklight::SearchBarComponent.new' instead"
|
|
85
96
|
|
|
@@ -181,6 +181,7 @@ module Blacklight::CatalogHelperBehavior
|
|
|
181
181
|
def render_document_main_content_partial(_document = @document)
|
|
182
182
|
render partial: 'show_main_content'
|
|
183
183
|
end
|
|
184
|
+
deprecation_deprecate render_document_main_content_partial: "Use \"render 'show_main_content'\" instead"
|
|
184
185
|
|
|
185
186
|
##
|
|
186
187
|
# Should we display the sort and per page widget?
|
|
@@ -55,7 +55,10 @@ module Blacklight
|
|
|
55
55
|
def file_source
|
|
56
56
|
raise Blacklight::Exceptions::IconNotFound, "Could not find #{path}" if file.blank?
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
# Handle both Sprockets::Asset and Propshaft::Asset
|
|
59
|
+
data = file.respond_to?(:source) ? file.source : file.path.read
|
|
60
|
+
|
|
61
|
+
data.force_encoding('UTF-8')
|
|
59
62
|
end
|
|
60
63
|
|
|
61
64
|
def ng_xml
|
|
@@ -68,7 +71,10 @@ module Blacklight
|
|
|
68
71
|
[icon_name, additional_options[:label_context]].compact.join('_')
|
|
69
72
|
end
|
|
70
73
|
|
|
74
|
+
# @return [Sprockets::Asset,Propshaft::Asset]
|
|
71
75
|
def file
|
|
76
|
+
return Rails.application.assets.load_path.find(path) if defined? Propshaft
|
|
77
|
+
|
|
72
78
|
# Rails.application.assets is `nil` in production mode (where compile assets is enabled).
|
|
73
79
|
# This workaround is based off of this comment: https://github.com/fphilipe/premailer-rails/issues/145#issuecomment-225992564
|
|
74
80
|
(Rails.application.assets || ::Sprockets::Railtie.build_environment(Rails.application)).find_asset(path)
|
|
@@ -17,6 +17,10 @@ module Blacklight
|
|
|
17
17
|
search_builder_class.new(self)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def search_state_class
|
|
21
|
+
@search_state.class
|
|
22
|
+
end
|
|
23
|
+
|
|
20
24
|
# a solr query method
|
|
21
25
|
# @yield [search_builder] optional block yields configured SearchBuilder, caller can modify or create new SearchBuilder to be used. Block should return SearchBuilder to be used.
|
|
22
26
|
# @return [Blacklight::Solr::Response] the solr response object
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= warn "#{
|
|
1
|
+
<%= warn "#{__FILE__} is a deprecated partial." %>
|
|
2
2
|
<%= render((blacklight_config&.view_config(document_index_view_type)&.search_bar_component || Blacklight::SearchBarComponent).new(
|
|
3
3
|
url: search_action_url,
|
|
4
4
|
advanced_search_url: search_action_url(action: 'advanced_search'),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= render
|
|
1
|
+
<%= render(Blacklight::SearchContextComponent.new(search_context: @search_context, search_session: search_session)) if search_session['document_id'] == @document.id %>
|
|
2
2
|
|
|
3
3
|
<% @page_title = t('blacklight.search.show.title', document_title: Deprecation.silence(Blacklight::BlacklightHelperBehavior) { document_show_html_title }, application_name: application_name).html_safe %>
|
|
4
4
|
<% content_for(:head) { render_link_rel_alternates } %>
|
data/blacklight.gemspec
CHANGED
|
@@ -33,6 +33,7 @@ Gem::Specification.new do |s|
|
|
|
33
33
|
s.add_dependency "i18n", '>= 1.7.0' # added named parameters
|
|
34
34
|
s.add_dependency "ostruct", '>= 0.3.2'
|
|
35
35
|
s.add_dependency "view_component", '~> 2.43'
|
|
36
|
+
s.add_dependency 'hashdiff'
|
|
36
37
|
|
|
37
38
|
s.add_development_dependency "rsolr", ">= 1.0.6", "< 3" # Library for interacting with rSolr.
|
|
38
39
|
s.add_development_dependency "rspec-rails", "~> 5.0"
|
|
@@ -296,7 +296,7 @@ module Blacklight
|
|
|
296
296
|
property :enable_search_bar_autofocus, default: false
|
|
297
297
|
|
|
298
298
|
BASIC_SEARCH_PARAMETERS = [:q, :qt, :page, :per_page, :search_field, :sort, :controller, :action, :'facet.page', :'facet.prefix', :'facet.sort', :rows, :format].freeze
|
|
299
|
-
ADVANCED_SEARCH_PARAMETERS = [:
|
|
299
|
+
ADVANCED_SEARCH_PARAMETERS = [{ clause: {} }, :op].freeze
|
|
300
300
|
# List the request parameters that compose the SearchState.
|
|
301
301
|
# If you use a plugin that adds to the search state, then you can add the parameters
|
|
302
302
|
# by modifiying this field.
|
|
@@ -305,6 +305,13 @@ module Blacklight
|
|
|
305
305
|
# @return [Array<Symbol>]
|
|
306
306
|
property :search_state_fields, default: BASIC_SEARCH_PARAMETERS + ADVANCED_SEARCH_PARAMETERS
|
|
307
307
|
|
|
308
|
+
# Have SearchState filter out unknown request parameters
|
|
309
|
+
#
|
|
310
|
+
# @!attribute filter_search_state_fields
|
|
311
|
+
# @since v8.0.0
|
|
312
|
+
# @return [Boolean]
|
|
313
|
+
property :filter_search_state_fields, default: false
|
|
314
|
+
|
|
308
315
|
##
|
|
309
316
|
# Create collections of solr field configurations.
|
|
310
317
|
# This will create array-like accessor methods for
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'hashdiff'
|
|
4
|
+
|
|
2
5
|
module Blacklight
|
|
3
|
-
|
|
6
|
+
class Parameters
|
|
7
|
+
extend Deprecation
|
|
8
|
+
|
|
4
9
|
##
|
|
5
10
|
# Sanitize the search parameters by removing unnecessary parameters
|
|
6
11
|
# from the provided parameters.
|
|
@@ -9,5 +14,96 @@ module Blacklight
|
|
|
9
14
|
params.reject { |_k, v| v.nil? }
|
|
10
15
|
.except(:action, :controller, :id, :commit, :utf8)
|
|
11
16
|
end
|
|
17
|
+
|
|
18
|
+
# rubocop:disable Naming/MethodParameterName
|
|
19
|
+
# Merge two Rails strong_params-style permissions into a single list of permitted parameters,
|
|
20
|
+
# deep-merging complex values as needed.
|
|
21
|
+
# @param [Array<Symbol, Hash>] a
|
|
22
|
+
# @param [Array<Symbol, Hash>] b
|
|
23
|
+
# @return [Array<Symbol, Hash>]
|
|
24
|
+
def self.deep_merge_permitted_params(a, b)
|
|
25
|
+
a = [a] if a.is_a? Hash
|
|
26
|
+
b = [b] if b.is_a? Hash
|
|
27
|
+
|
|
28
|
+
complex_params_from_a, scalar_params_from_a = a.flatten.uniq.partition { |x| x.is_a? Hash }
|
|
29
|
+
complex_params_from_a = complex_params_from_a.inject({}) { |tmp, h| _deep_merge_permitted_param_hashes(h, tmp) }
|
|
30
|
+
complex_params_from_b, scalar_params_from_b = b.flatten.uniq.partition { |x| x.is_a? Hash }
|
|
31
|
+
complex_params_from_b = complex_params_from_b.inject({}) { |tmp, h| _deep_merge_permitted_param_hashes(h, tmp) }
|
|
32
|
+
|
|
33
|
+
(scalar_params_from_a + scalar_params_from_b + [_deep_merge_permitted_param_hashes(complex_params_from_a, complex_params_from_b)]).reject(&:blank?).uniq
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private_class_method def self._deep_merge_permitted_param_hashes(h1, h2)
|
|
37
|
+
h1.merge(h2) do |_key, old_value, new_value|
|
|
38
|
+
if (old_value.is_a?(Hash) && old_value.empty?) || (new_value.is_a?(Hash) && new_value.empty?)
|
|
39
|
+
{}
|
|
40
|
+
elsif old_value.is_a?(Hash) && new_value.is_a?(Hash)
|
|
41
|
+
_deep_merge_permitted_param_hashes(old_value, new_value)
|
|
42
|
+
elsif old_value.is_a?(Array) || new_value.is_a?(Array)
|
|
43
|
+
deep_merge_permitted_params(old_value, new_value)
|
|
44
|
+
else
|
|
45
|
+
new_value
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
# rubocop:enable Naming/MethodParameterName
|
|
50
|
+
|
|
51
|
+
attr_reader :params, :search_state
|
|
52
|
+
|
|
53
|
+
delegate :blacklight_config, :filter_fields, to: :search_state
|
|
54
|
+
|
|
55
|
+
def initialize(params, search_state)
|
|
56
|
+
@params = params.is_a?(Hash) ? params.with_indifferent_access : params
|
|
57
|
+
@search_state = search_state
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @param [Hash] params with unknown structure (not declared in the blacklight config or filters) stripped out
|
|
61
|
+
def permit_search_params
|
|
62
|
+
# if the parameters were generated internally, we can (probably) trust that they're fine
|
|
63
|
+
return params unless params.is_a?(ActionController::Parameters)
|
|
64
|
+
|
|
65
|
+
# if the parameters were permitted already, we should be able to trust them
|
|
66
|
+
return params if params.permitted?
|
|
67
|
+
|
|
68
|
+
permitted_params = filter_fields.inject(blacklight_config.search_state_fields) do |allowlist, filter|
|
|
69
|
+
Blacklight::Parameters.deep_merge_permitted_params(allowlist, filter.permitted_params)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
deep_unmangle_params!(params, permitted_params)
|
|
73
|
+
|
|
74
|
+
if blacklight_config.filter_search_state_fields
|
|
75
|
+
params.permit(*permitted_params)
|
|
76
|
+
else
|
|
77
|
+
warn_about_deprecated_parameter_handling(params, permitted_params)
|
|
78
|
+
params.deep_dup.permit!
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def warn_about_deprecated_parameter_handling(params, permitted_params)
|
|
85
|
+
diff = Hashdiff.diff(params.to_unsafe_h, params.permit(*permitted_params).to_h)
|
|
86
|
+
return if diff.empty?
|
|
87
|
+
|
|
88
|
+
Deprecation.warn(Blacklight::Parameters, "Blacklight 8 will filter out non-search parameter, including: #{diff.map { |_op, key, *| key }.to_sentence}")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Facebook's crawler turns array query parameters into a hash with numeric keys. Once we know
|
|
92
|
+
# the expected parameter structure, we can unmangle those parameters to match our expected values.
|
|
93
|
+
def deep_unmangle_params!(params, permitted_params)
|
|
94
|
+
permitted_params.select { |p| p.is_a?(Hash) }.each do |permission|
|
|
95
|
+
permission.each do |key, permitted_value|
|
|
96
|
+
if params[key].is_a?(ActionController::Parameters) && permitted_value.is_a?(Hash)
|
|
97
|
+
deep_unmangle_params!(params[key], [permitted_value])
|
|
98
|
+
elsif permitted_value.is_a?(Array) && permitted_value.empty?
|
|
99
|
+
if params[key].is_a?(ActionController::Parameters) && params[key]&.keys&.all? { |k| k.to_s =~ /\A\d+\z/ }
|
|
100
|
+
params[key] = params[key].values
|
|
101
|
+
elsif params[key].is_a?(String)
|
|
102
|
+
params[key] = Array(params[key])
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
12
108
|
end
|
|
13
109
|
end
|
|
@@ -27,7 +27,8 @@ module Blacklight
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
@blacklight_params = {}
|
|
30
|
-
|
|
30
|
+
search_state_class = @scope&.search_state_class || Blacklight::SearchState
|
|
31
|
+
@search_state = search_state_class.new(@blacklight_params, @scope&.blacklight_config, @scope)
|
|
31
32
|
@additional_filters = {}
|
|
32
33
|
@merged_params = {}
|
|
33
34
|
@reverse_merged_params = {}
|
|
@@ -48,7 +49,7 @@ module Blacklight
|
|
|
48
49
|
Deprecation.warn(Blacklight::SearchBuilder, "SearchBuilder#where must be called with a hash, received #{conditions.inspect}.") unless conditions.is_a? Hash
|
|
49
50
|
params_will_change!
|
|
50
51
|
@search_state = @search_state.reset(@search_state.params.merge(q: conditions))
|
|
51
|
-
@blacklight_params = @search_state.params
|
|
52
|
+
@blacklight_params = @search_state.params
|
|
52
53
|
@additional_filters = conditions
|
|
53
54
|
self
|
|
54
55
|
end
|
|
@@ -159,12 +159,18 @@ module Blacklight
|
|
|
159
159
|
end
|
|
160
160
|
end
|
|
161
161
|
|
|
162
|
-
def
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
162
|
+
def permitted_params
|
|
163
|
+
if config.pivot
|
|
164
|
+
{
|
|
165
|
+
filters_key => config.pivot.each_with_object({}) { |key, filter| filter.merge(key => [], "-#{key}" => []) },
|
|
166
|
+
inclusive_filters_key => config.pivot.each_with_object({}) { |key, filter| filter.merge(key => []) }
|
|
167
|
+
}
|
|
168
|
+
else
|
|
169
|
+
{
|
|
170
|
+
filters_key => { config.key => [], "-#{config.key}" => [] },
|
|
171
|
+
inclusive_filters_key => { config.key => [] }
|
|
172
|
+
}
|
|
173
|
+
end
|
|
168
174
|
end
|
|
169
175
|
|
|
170
176
|
private
|
|
@@ -19,71 +19,17 @@ module Blacklight
|
|
|
19
19
|
|
|
20
20
|
delegate :facet_configuration_for_field, to: :blacklight_config
|
|
21
21
|
|
|
22
|
-
def self.modifiable_params(params)
|
|
23
|
-
if params.respond_to?(:to_unsafe_h)
|
|
24
|
-
# This is the typical (not-ActionView::TestCase) code path.
|
|
25
|
-
params = params.to_unsafe_h
|
|
26
|
-
# In Rails 5 to_unsafe_h returns a HashWithIndifferentAccess, in Rails 4 it returns Hash
|
|
27
|
-
params = params.with_indifferent_access if params.instance_of? Hash
|
|
28
|
-
elsif params.is_a? Hash
|
|
29
|
-
# This is an ActionView::TestCase workaround for Rails 4.2.
|
|
30
|
-
params = params.dup.with_indifferent_access
|
|
31
|
-
else
|
|
32
|
-
params = params.dup.to_h.with_indifferent_access
|
|
33
|
-
end
|
|
34
|
-
params
|
|
35
|
-
end
|
|
36
|
-
|
|
37
22
|
# @param [ActionController::Parameters] params
|
|
38
23
|
# @param [Blacklight::Config] blacklight_config
|
|
39
24
|
# @param [ApplicationController] controller used for the routing helpers
|
|
40
25
|
def initialize(params, blacklight_config, controller = nil)
|
|
41
26
|
@blacklight_config = blacklight_config
|
|
42
27
|
@controller = controller
|
|
43
|
-
@params =
|
|
44
|
-
normalize_params! if needs_normalization?
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def needs_normalization?
|
|
48
|
-
return false if params.blank?
|
|
49
|
-
return true if (params.keys.map(&:to_s) - permitted_fields.map(&:to_s)).present?
|
|
50
|
-
|
|
51
|
-
!!filters.detect { |filter| filter.values.detect { |value| filter.needs_normalization?(value) } }
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def normalize_params!
|
|
55
|
-
@params = normalize_params
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def normalize_params
|
|
59
|
-
return params unless needs_normalization?
|
|
60
|
-
|
|
61
|
-
base_params = params.slice(*blacklight_config.search_state_fields)
|
|
62
|
-
normal_state = blacklight_config.facet_fields.each_value.inject(reset(base_params)) do |working_state, filter_key|
|
|
63
|
-
f = filter(filter_key)
|
|
64
|
-
next working_state unless f.any?
|
|
65
|
-
|
|
66
|
-
filter_values = f.values(except: [:inclusive_filters]).inject([]) do |memo, filter_value|
|
|
67
|
-
# flatten arrays that had been mangled into integer-indexed hashes
|
|
68
|
-
memo.concat([f.normalize(filter_value)].flatten)
|
|
69
|
-
end
|
|
70
|
-
filter_values = f.values(except: [:filters, :missing]).inject(filter_values) do |memo, filter_value|
|
|
71
|
-
memo << f.normalize(filter_value)
|
|
72
|
-
end
|
|
73
|
-
filter_values.inject(working_state) do |memo, filter_value|
|
|
74
|
-
memo.filter(filter_key).add(filter_value)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
normal_state.params
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def permitted_fields
|
|
81
|
-
filter_keys = filter_fields.inject(Set.new) { |memo, filter| memo.merge [filter.filters_key, filter.inclusive_filters_key] }
|
|
82
|
-
blacklight_config.search_state_fields + filter_keys.subtract([nil, '']).to_a
|
|
28
|
+
@params = Blacklight::Parameters.new(params, self).permit_search_params.to_h.with_indifferent_access
|
|
83
29
|
end
|
|
84
30
|
|
|
85
31
|
def to_hash
|
|
86
|
-
|
|
32
|
+
params.deep_dup
|
|
87
33
|
end
|
|
88
34
|
alias to_h to_hash
|
|
89
35
|
|
|
@@ -132,7 +78,7 @@ module Blacklight
|
|
|
132
78
|
|
|
133
79
|
# @return [Blacklight::SearchState]
|
|
134
80
|
def reset(params = nil)
|
|
135
|
-
self.class.new(params ||
|
|
81
|
+
self.class.new(params || {}, blacklight_config, controller)
|
|
136
82
|
end
|
|
137
83
|
|
|
138
84
|
# @return [Blacklight::SearchState]
|
|
@@ -162,6 +108,10 @@ module Blacklight
|
|
|
162
108
|
p
|
|
163
109
|
end
|
|
164
110
|
|
|
111
|
+
def filter_fields
|
|
112
|
+
blacklight_config.facet_fields.each_value.map { |value| filter(value) }
|
|
113
|
+
end
|
|
114
|
+
|
|
165
115
|
def filters
|
|
166
116
|
@filters ||= filter_fields.select(&:any?)
|
|
167
117
|
end
|
|
@@ -192,7 +142,7 @@ module Blacklight
|
|
|
192
142
|
# catalog/index with their new facet choice.
|
|
193
143
|
def add_facet_params_and_redirect(field, item)
|
|
194
144
|
new_params = Deprecation.silence(self.class) do
|
|
195
|
-
add_facet_params(field, item)
|
|
145
|
+
add_facet_params(field, item).to_h.with_indifferent_access
|
|
196
146
|
end
|
|
197
147
|
|
|
198
148
|
# Delete any request params from facet-specific action, needed
|
|
@@ -229,7 +179,7 @@ module Blacklight
|
|
|
229
179
|
# @yield [params] The merged parameters hash before being sanitized
|
|
230
180
|
def params_for_search(params_to_merge = {})
|
|
231
181
|
# params hash we'll return
|
|
232
|
-
my_params =
|
|
182
|
+
my_params = to_h.merge(self.class.new(params_to_merge, blacklight_config, controller))
|
|
233
183
|
|
|
234
184
|
if block_given?
|
|
235
185
|
yield my_params
|
|
@@ -297,11 +247,7 @@ module Blacklight
|
|
|
297
247
|
# and need to be reset when e.g. constraints change
|
|
298
248
|
# @return [ActionController::Parameters]
|
|
299
249
|
def reset_search_params
|
|
300
|
-
Parameters.sanitize(
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
def filter_fields
|
|
304
|
-
blacklight_config.facet_fields.each_value.map { |value| filter(value) }
|
|
250
|
+
Parameters.sanitize(to_h).except(:page, :counter)
|
|
305
251
|
end
|
|
306
252
|
end
|
|
307
253
|
end
|
|
@@ -24,7 +24,8 @@ RSpec.describe BookmarksController do
|
|
|
24
24
|
allow(@controller).to receive_message_chain(:current_or_guest_user, :existing_bookmark_for).and_return(false)
|
|
25
25
|
allow(@controller).to receive_message_chain(:current_or_guest_user, :persisted?).and_return(true)
|
|
26
26
|
allow(@controller).to receive_message_chain(:current_or_guest_user, :bookmarks, :where, :exists?).and_return(false)
|
|
27
|
-
allow(@controller).to receive_message_chain(:current_or_guest_user, :bookmarks, :create).
|
|
27
|
+
allow(@controller).to receive_message_chain(:current_or_guest_user, :bookmarks, :create!).and_raise(ActiveRecord::RecordInvalid)
|
|
28
|
+
allow(@controller).to receive_message_chain(:current_or_guest_user, :errors, :full_messages).and_return([1])
|
|
28
29
|
put :update, xhr: true, params: { id: 'iamabooboo', format: :js }
|
|
29
30
|
expect(response.code).to eq "500"
|
|
30
31
|
end
|
|
@@ -27,7 +27,7 @@ RSpec.describe Blacklight::RenderConstraintsHelperBehavior do
|
|
|
27
27
|
let(:params) { ActionController::Parameters.new(q: 'foobar', f: { type: 'journal' }) }
|
|
28
28
|
|
|
29
29
|
it "has a link relative to the current url" do
|
|
30
|
-
expect(subject).to have_link 'Remove constraint', href: '/catalog?f%5Btype%5D=journal'
|
|
30
|
+
expect(subject).to have_link 'Remove constraint', href: '/catalog?f%5Btype%5D%5B%5D=journal'
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -21,4 +21,91 @@ RSpec.describe Blacklight::Parameters do
|
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
describe '.deep_merge_permitted_params' do
|
|
26
|
+
it 'merges scalar values' do
|
|
27
|
+
expect(described_class.deep_merge_permitted_params([:a], [:b])).to eq [:a, :b]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'appends complex values' do
|
|
31
|
+
expect(described_class.deep_merge_permitted_params([:a], { b: [] })).to eq [:a, { b: [] }]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'merges lists of scalar values' do
|
|
35
|
+
expect(described_class.deep_merge_permitted_params({ f: [:a, :b] }, { f: [:b, :c] })).to eq [{ f: [:a, :b, :c] }]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'merges complex value data structures' do
|
|
39
|
+
expect(described_class.deep_merge_permitted_params([{ f: { field1: [] } }], { f: { field2: [] } })).to eq [{ f: { field1: [], field2: [] } }]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'takes the most permissive value' do
|
|
43
|
+
expect(described_class.deep_merge_permitted_params([{ f: {} }], { f: { field2: [] } })).to eq [{ f: {} }]
|
|
44
|
+
expect(described_class.deep_merge_permitted_params([{ f: {} }], { f: [:some_value] })).to eq [{ f: {} }]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe '#permit_search_params' do
|
|
49
|
+
subject(:params) { described_class.new(query_params, search_state) }
|
|
50
|
+
|
|
51
|
+
let(:query_params) { ActionController::Parameters.new(a: 1, b: 2, c: []) }
|
|
52
|
+
let(:search_state) { Blacklight::SearchState.new(query_params, blacklight_config) }
|
|
53
|
+
let(:blacklight_config) { Blacklight::Configuration.new }
|
|
54
|
+
|
|
55
|
+
context 'with facebooks badly mangled query parameters' do
|
|
56
|
+
let(:query_params) do
|
|
57
|
+
ActionController::Parameters.new(
|
|
58
|
+
f: { field: { '0': 'first', '1': 'second' } },
|
|
59
|
+
f_inclusive: { field: { '0': 'first', '1': 'second' } }
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
before do
|
|
64
|
+
blacklight_config.add_facet_field 'field'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'normalizes the facets to the expected format' do
|
|
68
|
+
expect(params.permit_search_params.to_h.with_indifferent_access).to include f: { field: %w[first second] }, f_inclusive: { field: %w[first second] }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context 'with filter_search_state_fields set to false' do
|
|
73
|
+
let(:blacklight_config) { Blacklight::Configuration.new(filter_search_state_fields: false) }
|
|
74
|
+
|
|
75
|
+
it 'allows all params, but warns about the behavior' do
|
|
76
|
+
allow(Deprecation).to receive(:warn)
|
|
77
|
+
expect(params.permit_search_params.to_h.with_indifferent_access).to include(a: 1, b: 2, c: [])
|
|
78
|
+
|
|
79
|
+
expect(Deprecation).to have_received(:warn).with(described_class, /including: a, b, and c/).at_least(:once)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context 'with filter_search_state_fields set to true' do
|
|
84
|
+
let(:blacklight_config) { Blacklight::Configuration.new(filter_search_state_fields: true) }
|
|
85
|
+
|
|
86
|
+
it 'rejects unknown params' do
|
|
87
|
+
expect(params.permit_search_params.to_h).to be_empty
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
context 'with some search parameters' do
|
|
91
|
+
let(:query_params) { ActionController::Parameters.new(q: 'abc', page: 5, f: { facet_field: %w[a b], unknown_field: ['a'] }) }
|
|
92
|
+
|
|
93
|
+
before do
|
|
94
|
+
blacklight_config.add_facet_field 'facet_field'
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'allows scalar params' do
|
|
98
|
+
expect(params.permit_search_params.to_h.with_indifferent_access).to include(q: 'abc', page: 5)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'allows facet params' do
|
|
102
|
+
expect(params.permit_search_params.to_h.with_indifferent_access).to include(f: { facet_field: %w[a b] })
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'removes unknown facet fields parameters' do
|
|
106
|
+
expect(params.permit_search_params.to_h.with_indifferent_access[:f]).not_to include(:unknown_field)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
24
111
|
end
|
|
@@ -6,6 +6,7 @@ RSpec.describe Blacklight::SearchState::FilterField do
|
|
|
6
6
|
let(:params) { { f: { some_field: %w[1 2], another_field: ['3'] } } }
|
|
7
7
|
let(:blacklight_config) do
|
|
8
8
|
Blacklight::Configuration.new.configure do |config|
|
|
9
|
+
config.add_facet_field 'new_field'
|
|
9
10
|
config.add_facet_field 'another_field', single: true
|
|
10
11
|
simple_facet_fields.each { |simple_facet_field| config.add_facet_field simple_facet_field }
|
|
11
12
|
config.search_state_fields = config.search_state_fields + additional_search_fields
|
|
@@ -23,14 +24,6 @@ RSpec.describe Blacklight::SearchState::FilterField do
|
|
|
23
24
|
expect(new_state.filter('some_field').values).to eq %w[1 2 4]
|
|
24
25
|
end
|
|
25
26
|
|
|
26
|
-
it 'creates new parameter as needed' do
|
|
27
|
-
filter = search_state.filter('unknown_field')
|
|
28
|
-
new_state = filter.add('4')
|
|
29
|
-
|
|
30
|
-
expect(new_state.filter('unknown_field').values).to eq %w[4]
|
|
31
|
-
expect(new_state.params[:f]).to include(:unknown_field)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
27
|
context 'without any parameters in the url' do
|
|
35
28
|
let(:params) { {} }
|
|
36
29
|
|
|
@@ -205,10 +198,4 @@ RSpec.describe Blacklight::SearchState::FilterField do
|
|
|
205
198
|
expect(search_state.filter('some_field').include?(OpenStruct.new(value: '1'))).to eq true
|
|
206
199
|
end
|
|
207
200
|
end
|
|
208
|
-
|
|
209
|
-
describe '#needs_normalization?' do
|
|
210
|
-
it 'returns false for Blacklight::SearchState::FilterField::MISSING' do
|
|
211
|
-
expect(search_state.filter('some_field').needs_normalization?(Blacklight::SearchState::FilterField::MISSING)).to be false
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
201
|
end
|
|
@@ -54,19 +54,6 @@ RSpec.describe Blacklight::SearchState do
|
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
context 'with facebooks badly mangled query parameters' do
|
|
58
|
-
let(:simple_facet_fields) { [:field] }
|
|
59
|
-
let(:params) do
|
|
60
|
-
{ f: { field: { '0': 'first', '1': 'second' } },
|
|
61
|
-
f_inclusive: { field: { '0': 'first', '1': 'second' } } }
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
it 'normalizes the facets to the expected format' do
|
|
65
|
-
expect(search_state.to_h).to include f: { field: %w[first second] }
|
|
66
|
-
expect(search_state.to_h).to include f_inclusive: { field: %w[first second] }
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
57
|
context 'deleting item from to_h' do
|
|
71
58
|
let(:additional_search_fields) { [:q_1] }
|
|
72
59
|
let(:params) { { q: 'foo', q_1: 'bar' } }
|
|
@@ -81,7 +68,7 @@ RSpec.describe Blacklight::SearchState do
|
|
|
81
68
|
end
|
|
82
69
|
|
|
83
70
|
context 'deleting deep item from to_h' do
|
|
84
|
-
let(:additional_search_fields) { [:
|
|
71
|
+
let(:additional_search_fields) { [{ foo: {} }] }
|
|
85
72
|
let(:params) { { foo: { bar: [] } } }
|
|
86
73
|
|
|
87
74
|
it 'does not mutate search_state to deep mutate search_state.to_h' do
|
|
@@ -5,7 +5,7 @@ RSpec.describe Blacklight::SearchBuilder, api: true do
|
|
|
5
5
|
|
|
6
6
|
let(:processor_chain) { [] }
|
|
7
7
|
let(:blacklight_config) { Blacklight::Configuration.new }
|
|
8
|
-
let(:scope) { double blacklight_config: blacklight_config }
|
|
8
|
+
let(:scope) { double blacklight_config: blacklight_config, search_state_class: nil }
|
|
9
9
|
|
|
10
10
|
context "with default processor chain" do
|
|
11
11
|
subject { described_class.new scope }
|
|
@@ -15,6 +15,15 @@ RSpec.describe Blacklight::SearchBuilder, api: true do
|
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
context "with scope search_state_class" do
|
|
19
|
+
let(:state_class) { Class.new(Blacklight::SearchState) }
|
|
20
|
+
let(:scope) { double blacklight_config: blacklight_config, search_state_class: state_class }
|
|
21
|
+
|
|
22
|
+
it "uses the class-level default_processor_chain" do
|
|
23
|
+
expect(subject.search_state).to be_a state_class
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
18
27
|
describe "#with" do
|
|
19
28
|
it "sets the blacklight params" do
|
|
20
29
|
params = {}
|
|
@@ -216,7 +216,9 @@ RSpec.describe Blacklight::Solr::SearchBuilderBehavior, api: true do
|
|
|
216
216
|
expect(subject["spellcheck.q"]).to be_blank
|
|
217
217
|
|
|
218
218
|
single_facet.each_value do |value|
|
|
219
|
-
|
|
219
|
+
Array(value).each do |v|
|
|
220
|
+
expect(subject[:fq]).to include("{!term f=#{single_facet.keys[0]}}#{v}")
|
|
221
|
+
end
|
|
220
222
|
end
|
|
221
223
|
end
|
|
222
224
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: blacklight
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.25.
|
|
4
|
+
version: 7.25.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jonathan Rochkind
|
|
@@ -17,7 +17,7 @@ authors:
|
|
|
17
17
|
autorequire:
|
|
18
18
|
bindir: exe
|
|
19
19
|
cert_chain: []
|
|
20
|
-
date: 2022-05-
|
|
20
|
+
date: 2022-05-19 00:00:00.000000000 Z
|
|
21
21
|
dependencies:
|
|
22
22
|
- !ruby/object:Gem::Dependency
|
|
23
23
|
name: rails
|
|
@@ -137,6 +137,20 @@ dependencies:
|
|
|
137
137
|
- - "~>"
|
|
138
138
|
- !ruby/object:Gem::Version
|
|
139
139
|
version: '2.43'
|
|
140
|
+
- !ruby/object:Gem::Dependency
|
|
141
|
+
name: hashdiff
|
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
|
143
|
+
requirements:
|
|
144
|
+
- - ">="
|
|
145
|
+
- !ruby/object:Gem::Version
|
|
146
|
+
version: '0'
|
|
147
|
+
type: :runtime
|
|
148
|
+
prerelease: false
|
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
150
|
+
requirements:
|
|
151
|
+
- - ">="
|
|
152
|
+
- !ruby/object:Gem::Version
|
|
153
|
+
version: '0'
|
|
140
154
|
- !ruby/object:Gem::Dependency
|
|
141
155
|
name: rsolr
|
|
142
156
|
requirement: !ruby/object:Gem::Requirement
|