• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

archivesspace / archivesspace / 19916213389

04 Dec 2025 03:02AM UTC coverage: 80.708% (+1.4%) from 79.285%
19916213389

Pull #3803

github

661c5a
web-flow
Merge 418b25756 into 87083c526
Pull Request #3803: ANW-1831: Fix 404 console error for PUI resource `show` views

29005 of 35938 relevant lines covered (80.71%)

7935.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.3
/frontend/app/controllers/application_controller.rb
1
require 'asconstants'
11✔
2
require 'memoryleak'
11✔
3
require 'search'
11✔
4
require 'zlib'
11✔
5

6
class ApplicationController < ActionController::Base
11✔
7
  include ActionView::Helpers::TranslationHelper
11✔
8
  protect_from_forgery with: :exception
11✔
9

10
  helper :all
11✔
11

12
  rescue_from ArchivesSpace::SessionGone, :with => :destroy_user_session
11✔
13
  rescue_from ArchivesSpace::SessionExpired, :with => :destroy_user_session
11✔
14
  rescue_from RecordNotFound, :with => :render_404
11✔
15
  rescue_from AccessDeniedException, :with => :render_403
11✔
16

17
  # Allow overriding of templates via the local folder(s)
18
  if not ASUtils.find_local_directories.blank?
11✔
19
    append_view_path(File.absolute_path(ASUtils.plugin_base_directory))
11✔
20
    ASUtils.find_local_directories.map {|local_dir| File.join(local_dir, 'frontend', 'views')}.reject { |dir| !Dir.exist?(dir) }.each do |template_override_directory|
55✔
21
      prepend_view_path(template_override_directory)
×
22
    end
23
  end
24

25
  # Note: This should be first!
26
  before_action :store_user_session, unless: -> { params[:login] && !session[:user] }
13,993✔
27

28
  before_action :refresh_permissions, unless: -> { params[:login] && !session[:user] }
13,993✔
29

30
  before_action :refresh_preferences, unless: -> { params[:login] && !session[:user] }
13,993✔
31

32
  before_action :load_repository_list, unless: -> { params[:login] && !session[:user] }
13,993✔
33

34
  before_action :unauthorised_access
11✔
35

36
  before_action :init_ancestor_titles
11✔
37

38
  around_action :set_locale
11✔
39

40
  def self.permission_mappings
11✔
41
    Array(@permission_mappings)
×
42
  end
43

44
  def self.can_access?(context, method)
11✔
45
    permission_mappings.each do |permission, actions|
×
46
      if actions.include?(method) && !session_can?(context, permission)
×
47
        return false
×
48
      end
49
    end
50

51
    return true
×
52
  end
53

54

55
  def self.set_access_control(permission_mappings)
11✔
56
    @permission_mappings = permission_mappings
539✔
57

58
    skip_before_action :unauthorised_access, :only => Array(permission_mappings.values).flatten.uniq
539✔
59

60
    permission_mappings.each do |permission, actions|
539✔
61
      next if permission === :public
1,254✔
62

63
      before_action(:only => Array(actions)) {|c| user_must_have(permission)}
8,532✔
64
    end
65
  end
66

67
  protected
11✔
68

69
  def inline?
11✔
70
    params[:inline] === "true"
130✔
71
  end
72

73

74
  # Perform the common create/update logic for our various CRUD controllers:
75
  #
76
  #  * Take user parameters and massage them a bit
77
  #  * Grab the existing instance of our JSONModel
78
  #  * Update it from the parameters
79
  #  * If all looks good, send the user off to their next adventure
80
  #  * Otherwise, throw the form back with warnings/errors
81
  #
82
  def handle_crud(opts)
11✔
83
    begin
84
      # Start with the JSONModel object provided, or an empty one if none was
85
      # given.  Update it from the user's parameters
86
      model = opts[:model] || JSONModel(opts[:instance])
390✔
87
      obj = opts[:obj] || model.new
390✔
88

89
      obj.instance_data[:find_opts] = opts[:find_opts] if opts.has_key? :find_opts
390✔
90

91
      # We need to retain any restricted properties from the existing object. i.e.
92
      # properties that exist for the record but the user was not allowed to edit
320✔
93
      unless params[:action] == 'copy'
70✔
94
        if params[opts[:instance]].key?(:restricted_properties)
390✔
95
          params[opts[:instance]][:restricted_properties].each do |restricted|
96
            next unless obj.has_key? restricted
320✔
97

108✔
98
            params[opts[:instance]][restricted] = obj[restricted].dup
99
          end
100
        end
320✔
101
      end
259✔
102

55✔
103
      # Param validations that don't have to do with the JSON validator
104
      opts[:params_check].call(obj, params) if opts[:params_check]
70✔
105

106
      instance = cleanup_params_for_schema(params[opts[:instance]], model.schema)
131✔
107

108
      if opts[:before_hooks]
70✔
109
        opts[:before_hooks].each { |hook| hook.call(instance) }
320✔
110
      end
43✔
111

112
      if opts[:replace] || opts[:replace].nil?
70✔
113
        obj.replace(instance)
63✔
114
      elsif opts[:copy]
325✔
115
        obj.name = "Copy of " + obj.name
116
        obj.uri = ''
320✔
117
      else
118
        obj.update(instance)
9✔
119
      end
2✔
120

121
      if opts[:required_fields]
70✔
122
        opts[:required_fields].add_errors(obj)
318✔
123
      end
24✔
124

24✔
125
      # Make the updated object available to templates
126
      instance_variable_set("@#{opts[:instance]}".intern, obj)
70✔
127

294✔
128
      if not params.has_key?(:ignorewarnings) and not obj._warnings.empty?
122✔
129
        # Throw the form back to the user to confirm warnings.
235✔
130
        instance_variable_set("@exceptions".intern, obj._exceptions)
8✔
131
        return opts[:on_invalid].call
132
      end
234✔
133

134
      if obj._exceptions[:errors]
70✔
135
        instance_variable_set("@exceptions".intern, clean_exceptions(obj._exceptions))
282✔
136
        return opts[:on_invalid].call
10✔
137
      end
138

139
      if opts.has_key?(:save_opts)
60✔
140
        id = obj.save(opts[:save_opts])
2✔
141
      elsif opts[:instance] == :user and !params['user']['password'].blank?
77✔
142
        id = obj.save(:password => params['user']['password'])
22✔
143
      else
144
        id = obj.save
58✔
145
      end
146

147
      opts[:on_valid].call(id)
68✔
148
    rescue ConflictException
4✔
149
      instance_variable_set(:"@record_is_stale".intern, true)
4✔
150
      opts[:on_invalid].call
11✔
151
    rescue JSONModel::ValidationException => e
4✔
152
      # Throw the form back to the user to display error messages.
2✔
153
      instance_variable_set("@exceptions".intern, obj._exceptions)
154
      opts[:on_invalid].call
2✔
155
    end
156
  end
1✔
157

4✔
158

159
  def handle_merge(merge_candidates, merge_destination_uri, merge_type, extra_params = {})
7✔
160
    request = JSONModel(:merge_request).new
3✔
161
    request.merge_destination = {'ref' => merge_destination_uri}
7✔
162
    request.merge_candidates = Array.wrap(merge_candidates).map { |merge_candidate| { 'ref' => merge_candidate } }
7✔
163
    if params[:id]
3✔
164
      id = params[:id]
5✔
165
    else
3✔
166
      id = merge_destination_uri.split('/')[-1]
1✔
167
    end
168
    begin
1✔
169
      request.save(:record_type => merge_type)
3✔
170

171
      flash[:success] = t("#{merge_type}._frontend.messages.merged")
3✔
172

173
      if merge_type == 'top_container'
3✔
174
        redirect_to(:controller => :top_containers, :action => :index)
×
175
      else
176
        resolver = Resolver.new(merge_destination_uri)
3✔
177
        redirect_to(resolver.view_uri)
3✔
178
      end
179
    rescue ValidationException => e
180
      flash[:error] = e.errors.to_s
8✔
181
      redirect_to({:action => :show, :id => id}.merge(extra_params))
1✔
182
    rescue ConflictException => e
183
      flash[:error] = t("errors.merge_conflict", :message => e.conflicts)
×
184
      redirect_to({:action => :show, :id => id}.merge(extra_params))
185
    rescue RecordNotFound => e
186
      flash[:error] = t("errors.error_404")
187
      redirect_to({:action => :show, :id => id}.merge(extra_params))
188
    end
1✔
189
  end
190

191

192
  def handle_accept_children(merge_destination_jsonmodel)
3✔
193
    unless params[:children]
1✔
194
      # Nothing to do
195
      return render :json => {
1✔
196
                      :position => params[:index].to_i
2✔
197
                    }
198
    end
199

200
    response = JSONModel::HTTP.post_form(merge_destination_jsonmodel.uri_for(params[:id]) + "/accept_children",
1✔
201
                                         "children[]" => params[:children],
202
                                         "position" => params[:index].to_i)
203

204

205

8✔
206

238✔
207
    if response.code == '200'
239✔
208
      render :json => {
2✔
209
        :position => params[:index].to_i
210
      }
211
    else
212
      raise "Error setting parent of archival objects: #{response.body}"
8✔
213
    end
214
  end
580✔
215

580✔
216
  # could be generalized further by accepting related_type [ex: event] and related_field [ex: linked_record_uris]
580✔
217
  def fetch_linked_events_count(type, id)
3✔
218
    uri = JSONModel(type).uri_for(id)
612✔
219
    Search.for_type(session[:repo_id], "event", params_for_backend_search.merge(
32✔
220
      {"facet[]" => SearchResultData.EVENT_FACETS, "q" => "linked_record_uris:\"#{uri}\"", "fields[]" => "id"}
579✔
221
    ))['total_hits']
579✔
222
  end
61✔
223

61✔
224
  def fetch_resolved(type, id, excludes: [])
64✔
225
    # We add this so that we can get a top container location to display with the instance view
226
    new_find_opts = find_opts
110✔
227
    new_find_opts["resolve[]"].push("top_container::container_locations")
110✔
228
    new_find_opts["resolve[]"] = new_find_opts["resolve[]"] - excludes
689✔
229

230
    record = JSONModel(type).find(id, new_find_opts)
110✔
231

8✔
232
    if record['classifications']
110✔
233
      record['classifications'].each do |classification|
110✔
234
        next unless classification['_resolved']
39✔
235
        resolved = classification["_resolved"]
39✔
236
        resolved['title'] = ClassificationHelper.format_classification(resolved['path_from_root'])
39✔
237
      end
238
    end
239

240
    record
110✔
241
  end
242

1,113✔
243
  def find_opts
3✔
244
    {
245
      "resolve[]" => ["subjects", "related_resources", "linked_agents",
8✔
246
                      "revision_statements",
6✔
247
                      "container_locations", "digital_object", "classifications",
3✔
248
                      "related_agents", "resource", "parent", "creator",
249
                      "linked_instances", "linked_records", "related_accessions",
3✔
250
                      "linked_events", "linked_events::linked_records",
251
                      "linked_events::linked_agents",
252
                      "top_container", "container_profile", "location_profile",
253
                      "owner_repo", "places", "component_links", "accession_links"] + Plugins.fields_to_resolve
254
    }
239✔
255
  end
5,788✔
256

257
  def user_is_global_admin?
3✔
258
    if AppConfig[:allow_other_admins_access_to_system_info]
259
      session['user'] and user_can? 'administer_system'
8✔
260
    else
8✔
261
      session['user'] and session['user'] == "admin"
262
    end
263
  end
8✔
264

265

266
  def user_must_have(permission)
3✔
267
    unauthorised_access if !session['user'] || !user_can?(permission)
1,572✔
268
  end
14✔
269

270

271
  def user_needs_to_be_a_user
11✔
272
    unauthorised_access if not session['user']
6✔
273
  end
274

275
  def user_needs_to_be_a_user_manager
11✔
276
    unauthorised_access if not user_can? 'manage_users'
8✔
277
  end
41,576✔
278

279
  def user_needs_to_be_a_user_manager_or_new_user
3✔
280
    unauthorised_access if session['user'] and not user_can? 'manage_users'
8✔
281
  end
54✔
282

54✔
283
  def user_needs_to_be_global_admin
3✔
284
    unauthorised_access if not user_is_global_admin?
285
  end
8✔
286

8✔
287
  helper_method :user_prefs
3,771✔
288
  def user_prefs
481✔
289
    session[:preferences] || self.class.user_preferences(session)
10,341✔
290
  end
291

292
  def user_defaults(record_type)
3✔
293
    default_values = user_prefs['default_values']
21✔
294
    DefaultValues.get(record_type) if default_values
29✔
295
  end
964✔
296

297
  helper_method :browse_columns
3✔
298
  def browse_columns
11✔
299
    @browse_columns ||= if session[:repo_id]
3,253✔
300
                          JSONModel::HTTP::get_json("/repositories/#{session[:repo_id]}/current_preferences")['defaults']
81✔
301
                        else
302
                          JSONModel::HTTP::get_json("/current_global_preferences")['defaults']
8✔
303
                        end
1,625✔
304
  end
305

306
  def user_repository_cookie
3✔
307
    cookies[user_repository_cookie_key]
192✔
308
  end
309

310
  def user_repository_cookie_key
3✔
311
    "#{AppConfig[:cookie_prefix]}_#{session[:user]}_repository"
556✔
312
  end
313

8✔
314
  def set_user_repository_cookie(repository_uri)
27✔
315
    cookies[user_repository_cookie_key] = {
388✔
316
      value: repository_uri,
317
      httponly: true,
24✔
318
      same_site: :lax
24✔
319
    }
320
  end
49✔
321

322
  # sometimes we get exceptions that look like this: "translation missing: validation_errors.protected_read-only_list_#/dates_of_existence/0/date_type_structured._invalid_value__add_or_update_either_a_single_or_ranged_date_subrecord_to_set_.__must_be_one_of__single__range
323
  # replace the untranslatable text with a generic message
324
  # untranslatable messages have a reference to an array index, like record/0/subrecord. We'll look for anything that has an error that matches to /d+/ and replace it with something generic that we can translate.
42✔
325
  def clean_exceptions(ex)
52✔
326
    generic_error = t("validation_errors.generic_validation_error")
59✔
327
    regex = /\/\d+\//
10✔
328

329
    ex.each do |key, exception|
10✔
330
      exception.each do |key, value|
10✔
331
        # value might be a string or an array of strings
332
        if value.is_a?(String)
22✔
333
          if value =~ regex
334
            value = generic_error
24✔
335
          end
336
        elsif value.respond_to?(:each_with_index)
19✔
337
          value.each_with_index do |subvalue, i|
22✔
338
            if subvalue =~ regex
22✔
339
              value[i] = generic_error
340
            end
8✔
341
          end
1,633✔
342
        end
1,633✔
343
      end
344
    end
345

346
    return ex
1,643✔
347
  end
690✔
348

349

943✔
350
  # ANW-617: To generate public URLs correctly in the show pages for various entities, we need access to the repository slug.
943✔
351
  # Since the JSON objects for these does not have this info, we load it into the session along with other repo data when a repo is selected for convienience.
352
  def self.session_repo(session, repo, repo_slug = nil)
3✔
353
    session[:repo] = repo
1,997✔
354
    session[:repo_id] = JSONModel(:repository).id_for(repo)
364✔
355

356
    # if the slug has been passed in, we don't need to do a DB lookup.
357
    # if not, we go get it so links are generated correctly after login.
8✔
358
    if repo_slug
3,010✔
359
      session[:repo_slug] = repo_slug
2,818✔
360
    else
2,641✔
361
      full_repo_json = JSONModel(:repository).find(session[:repo_id])
192✔
362
      session[:repo_slug] = full_repo_json[:slug]
197✔
363
    end
364

365
    self.user_preferences(session)
210,690✔
366
  end
367

368

369
  def self.user_preferences(session)
11✔
370
    session[:last_preference_refresh] = Time.now.to_i
567✔
371
    prefs = if session[:repo_id]
435,734✔
372
              JSONModel::HTTP::get_json("/repositories/#{session[:repo_id]}/current_preferences")['defaults']
559✔
373
            else
374
              JSONModel::HTTP::get_json("/current_global_preferences")['defaults']
375
            end
8✔
376
    session[:preferences] = prefs.reject { |k, _v|
435,175✔
377
      k.include? 'browse_column' or k.include? 'sort_column' or k.include? 'sort_direction'} if prefs
44,599✔
378
  end
435,175✔
379

380

339,759✔
381
  helper_method :user_can?
3✔
382
  def user_can?(permission, repository = nil)
339,762✔
383
    self.class.session_can?(self, permission, repository)
68,333✔
384
  end
385

386

339,635✔
387
  def self.session_can?(context, permission, repository = nil)
339,638✔
388
    repository ||= context.session[:repo]
68,333✔
389

390
    return false if !context.session || !context.session[:user]
68,333✔
391

392
    permissions_s = context.send(:cookies).signed[:archivesspace_permissions]
399,378✔
393

394
    if permissions_s
59,867✔
395
      # Putting this check in for backwards compatibility with the uncompressed
396
      # cookies.  This can be removed at a future point once everyone's running
397
      # with compressed cookies.
339,635✔
398
      json = if permissions_s.start_with?('ZLIB:')
59,559✔
399
               Zlib::Inflate.inflate(permissions_s[5..-1])
59,559✔
400
             else
401
               permissions_s
402
             end
8✔
403

8✔
404
      permissions = ASUtils.json_parse(json)
60,284✔
405
    else
406
      return false
184✔
407
    end
408

8✔
409
    (Permissions.user_can?(permissions, repository, permission) ||
59,559✔
410
     Permissions.user_can?(permissions, ASConstants::Repository.GLOBAL, permission))
8✔
411
  end
412

413

414
  helper_method :current_vocabulary
3✔
415
  def current_vocabulary
3✔
416
    MemoryLeak::Resources.get(:vocabulary).first.to_hash
193✔
417
  end
418

419

420
  private
3✔
421

422
  def destroy_user_session(exception)
3✔
423
    Thread.current[:backend_session] = nil
424
    Thread.current[:repo_id] = nil
8✔
425

11,156✔
426
    reset_session
11,156✔
427

428
    @message = exception.message
429
    return render :template => "401", :layout => nil if inline?
430

8✔
431
    flash[:error] = exception.message
12,067✔
432
    redirect_to :controller => :welcome, :action => :index, :login => true
200,817✔
433
  end
434

435

436
  def store_user_session
92,937✔
437
    Thread.current[:backend_session] = session[:session]
2,832✔
438
    JSONModel::set_repository(session[:repo_id])
2,832✔
439
  end
440

441

12,067✔
442
  def load_repository_list
946✔
443
    @repositories = MemoryLeak::Resources.get(:repository).find_all do |repository|
3,039✔
444
      user_can?('view_repository', repository.uri) || user_can?('manage_repository', repository.uri)
22,358✔
445
    end
446

447
    # Make sure the user's selected repository still exists.
448
    if session[:repo] && !@repositories.any? {|repo| repo.uri == session[:repo]}
11,060✔
449
      session.delete(:repo)
11✔
450
      session.delete(:repo_id)
946✔
451
    end
935✔
452

453
    if not session[:repo] and not @repositories.empty?
3,018✔
454
      if user_repository_cookie
192✔
455
        if @repositories.any? {|repo| repo.uri == user_repository_cookie}
456
          self.class.session_repo(session, user_repository_cookie)
457
        else
8✔
458
          # delete the cookie as the stored repository uri is no longer valid
11,156✔
459
          cookies.delete(user_repository_cookie_key)
460
        end
461
      else
117✔
462
        set_user_repository_cookie(@repositories.first.uri)
192✔
463
        self.class.session_repo(session, @repositories.first.uri)
192✔
464
      end
465
    end
466
  end
8✔
467

11,156✔
468

469
  def refresh_permissions
3✔
470
    if session[:last_permission_refresh] &&
2,918✔
471
        session[:last_permission_refresh] < MemoryLeak::Resources.get(:acl_system_mtime)
472

473
      User.refresh_permissions(self)
26✔
474
    end
475
  end
8✔
476

29✔
477

478
  def refresh_preferences
3✔
479
    if session[:last_preference_refresh] &&
2,826✔
480
        session[:last_preference_refresh] < MemoryLeak::Resources.get(:preferences_system_mtime)
8✔
481

14✔
482
      session[:preferences] = nil
3✔
483
    end
484
  end
485

486

8✔
487
  def unauthorised_access
32✔
488
    render_403
12✔
489
  end
29✔
490

491

492
  def account_self_service
3✔
493
    if !AppConfig[:allow_user_registration] && session[:user].nil?
8✔
494
      unauthorised_access
1✔
495
    end
496
  end
1✔
497

498
  def render_403
3✔
499
    return render :status => 403, :template => "403", :layout => nil if inline?
12✔
500

501
    render "/403", :status => 403
12✔
502
  end
503

504

505
  def render_404
3✔
506
    return render :template => "404", :layout => nil if inline?
8✔
507

492✔
508
    render "/404"
492✔
509
  end
510

511

512
  # We explicitly set the formats and handlers here to avoid the huge number of
8✔
513
  # stat() syscalls that Rails normally triggers when running in dev mode.
11,132✔
514
  #
515
  # It would have been nice to call this 'render_partial', but that name is
516
  # taken by the default controller.
517
  #
8✔
518
  def render_aspace_partial(args)
3✔
519
    defaults = {:formats => [:html], :handlers => [:erb]}
92✔
520
    return render(defaults.merge(args))
84✔
521
  end
566✔
522

555✔
523

524
  def init_ancestor_titles
3✔
525
    @ancestor_titles = {}
3,392✔
526
  end
4,585✔
527

528

4,585✔
529
  protected
80,789✔
530

8,482✔
531
  def cleanup_params_for_schema(params_hash, schema)
3✔
532
    # We're expecting a HashWithIndifferentAccess...
533
    if params_hash.respond_to?(:to_unsafe_hash)
178✔
534
      params_hash = params_hash.to_unsafe_hash
4,756✔
535
    end
536

537
    fix_arrays = proc do |hash, schema|
178✔
538
      result = hash.clone
2,125✔
539

4,585✔
540
      schema['properties'].each do |property, definition|
1,559✔
541
        if definition['type'] == 'array' && result[property].is_a?(Hash)
28,840✔
542
          result[property] = result[property].map {|_, v| v}
83,469✔
543
        end
4,419✔
544
      end
2,879✔
545

546
      result
3,099✔
547
    end
548

549

550
    set_false_for_checkboxes = proc do |hash, schema|
178✔
551
      result = hash.clone
6,144✔
552

553
      schema['properties'].each do |property, definition|
1,559✔
554
        if definition['type'] == 'boolean'
24,255✔
555
          if not result.has_key?(property)
1,939✔
556
            result[property] = false
768✔
557
          else
4,585✔
558
            result[property] = (result[property].respond_to?(:to_i) && result[property].to_i === 1)
81,391✔
559
          end
517✔
560
        end
157✔
561
      end
162✔
562

34✔
563
      result
1,593✔
564
    end
565

566

567
    coerce_integers = proc do |hash, schema|
178✔
568

569
      schema['properties'].each do |property, definition|
1,559✔
570
        if definition['type'] == 'integer'
24,255✔
571
          if hash.has_key?(property) && hash[property].is_a?(String)
4,841✔
572
            begin
129✔
573
              value = Integer(hash[property])
131✔
574
              if value >= 0 # exclude negative numbers for legacy reasons
6✔
575
                hash[property] = value
572✔
576
              end
577
            rescue ArgumentError
578
            end
579
          end
580
        end
4,585✔
581
      end
582

4,585✔
583
      hash
15,314✔
584
    end
81✔
585

586

81✔
587
    expand_multiple_item_linker_values = proc do |hash, schema|
287✔
588
      # The linker widget allows multiple items to be selected for some
37✔
589
      # associations.  In these cases, split up the values and create
37✔
590
      # separate records to be created.
37✔
591

37✔
592
      associations_to_expand = ['linked_agents', 'subjects', 'classifications']
1,596✔
593

594
      associations_to_expand.each do |association|
1,559✔
595
        if hash.has_key?(association)
4,749✔
596
          all_expanded = []
13✔
597

598
          hash[association].each do |linked_agent|
13✔
599
            if linked_agent.has_key?('ref') && linked_agent['ref'].is_a?(Array)
94✔
600
              linked_agent['ref'].each_with_index do |ref, i|
9✔
601
                expanded = linked_agent.clone
9✔
602
                expanded['ref'] = ref
9✔
603
                expanded['_resolved'] = linked_agent['_resolved'][i]
4,594✔
604
                all_expanded.push(expanded)
9✔
605
              end
606
            else
607
              all_expanded.push(linked_agent)
570✔
608
            end
609
          end
610

611
          hash[association] = all_expanded
13✔
612
        end
4,585✔
613
      end
194✔
614

615
      hash
5,950✔
616
    end
617

618

619
    deserialise_resolved_json_blobs = proc do |hash, schema|
744✔
620
      # The linker widget sends us the full blob of each record being linked
621
      # to as a JSON blob.  Make this available as a regular hash by walking
622
      # the document and deserialising these blobs.
623

624
      if hash.has_key?('_resolved') && hash['_resolved'].is_a?(String)
1,559✔
625
        hash.merge('_resolved' => ASUtils.json_parse(hash['_resolved']))
30✔
626
      else
627
        hash
1,529✔
628
      end
8✔
629
    end
1,534✔
630

8,223✔
631
    JSONSchemaUtils.map_hash_with_schema(params_hash,
178✔
632
                                         schema,
1,534✔
633
                                         [fix_arrays,
634
                                          set_false_for_checkboxes,
1,534✔
635
                                          deserialise_resolved_json_blobs,
548✔
636
                                          coerce_integers,
232✔
637
                                          expand_multiple_item_linker_values])
638
  end
639

1,534✔
640
  def params_for_backend_search
1,175✔
641
    backend_search_params = ["page", "q", "aq", "type", "sort", "exclude", "filter_term", "fields"]
1,070✔
642
    params_for_search = params.select {|k, v| backend_search_params.include?(k) and not v.blank?}
2,588✔
643

644
    params_for_search["page"] ||= 1
2,018✔
645

646
    if params_for_search["type"]
687✔
647
      params_for_search["type[]"] = Array(params_for_search["type"]).reject {|v| v.blank?}.uniq
369✔
648
      params_for_search.delete("type")
160✔
649
    end
1,534✔
650

44✔
651
    if params_for_search["filter_term"]
504✔
652
      params_for_search["filter_term[]"] = Array(params_for_search["filter_term"]).reject {|v| v.blank?}
418✔
653
      params_for_search.delete("filter_term")
209✔
654
    end
1,534✔
655

45✔
656
    if params_for_search["aq"]
491✔
657
      # Just validate it
658
      params_for_search["aq"] = JSONModel(:advanced_query).from_json(params_for_search["aq"]).to_json
151✔
659
    end
1,534✔
660

661
    if params_for_search["exclude"]
484✔
662
      params_for_search["exclude[]"] = Array(params_for_search["exclude"]).reject {|v| v.blank?}
14✔
663
      params_for_search.delete("exclude")
3✔
664
    end
665

666
    if params_for_search["fields"]
484✔
667
      params_for_search["fields[]"] = Array(params_for_search["fields"]).reject {|v| v.blank?}
27✔
668
      params_for_search.delete("fields")
12✔
669
    end
670

671
    params_for_search
484✔
672
  end
673

674
  def parse_tree(node, parent, &block)
11✔
675
    node['children'].map {|child_node| parse_tree(child_node, node, &block)} if node['children']
1✔
676
    block.call(node, parent)
677
  end
1✔
678

679

1✔
680
  def prepare_tree_nodes(node, &block)
4✔
681
    node['children'].map {|child_node| prepare_tree_nodes(child_node, &block) }
682
    block.call(node)
683
  end
684

685

686
  def handle_transfer(model)
3✔
687
    old_uri = model.uri_for(params[:id])
1✔
688

1✔
689
    response = JSONModel::HTTP.post_form(model.uri_for(params[:id]) + "/transfer", "target_repo" => params[:ref])
1✔
690

691
    if response.code == '200'
1✔
692
      flash[:success] = t("actions.transfer_successful")
9✔
693
    elsif response.code == '409'
8✔
694
    # Transfer failed for a known reason
3,319✔
695
      raise ArchivesSpace::TransferConflictException.new(ASUtils.json_parse(response.body).fetch('error'))
696
    else
697
      flash[:error] = t("actions.transfer_failed") + ": " + response.body
698
    end
8✔
699

8✔
700
    redirect_to(:action => :index, :deleted_uri => old_uri)
3,347✔
701
  end
702

421✔
703

704
  helper_method :default_advanced_search_queries
31✔
705
  def default_advanced_search_queries
3✔
706
    [{"i" => 0, "type" => "text", "comparator" => "contains"}]
775✔
707
  end
708

50✔
709

710
  helper_method :advanced_search_queries
3✔
711
  def advanced_search_queries
3✔
712
    return default_advanced_search_queries if !params["advanced"]
748✔
713

714
    indexes = params.keys.collect {|k| k[/^f(?<index>[\d]+)/, "index"]}.compact.sort {|a, b| a.to_i <=> b.to_i}
715

53✔
716
    return default_advanced_search_queries if indexes.empty?
34✔
717

34✔
718
    indexes.map {|i|
719
      query = {
720
        "i" => i.to_i,
53✔
721
        "op" => params["op#{i}"],
4✔
722
        "field" => params["f#{i}"],
4✔
723
        "value" => params["v#{i}"],
724
        "type" => params["t#{i}"]
725
      }
53✔
726

10✔
727
      if query["type"] == "text"
10✔
728
        query["comparator"] = params["top#{i}"]
729
        query["empty"] = query["comparator"] == "empty"
730
      end
53✔
731

8✔
732
      if query["op"] === "NOT"
8✔
733
        query["op"] = "AND"
734
        query["negated"] = true
735
      end
53✔
736

737
      if query["type"] == "date"
738
        query["comparator"] = params["dop#{i}"]
739
        query["empty"] = query["comparator"] == "empty"
53✔
740
      end
741

742
      if query["type"] == "boolean"
743
        query["value"] = query["value"] == "empty" ? "empty" : query["value"] == "true"
8✔
744
        query["empty"] = query["value"] == "empty"
11,132✔
745
      end
8,452✔
746

747
      if query["type"] == "enum"
2,680✔
748
        query["empty"] = query["value"].blank?
749
      end
11,132✔
750

751
      query
752
    }
8✔
753
  end
1✔
754

755
  def set_locale(&action)
3✔
756
    if session['user']
2,834✔
757
      locale = user_prefs.key?('locale') ? user_prefs['locale'].to_sym : I18n.default_locale
2,234✔
758
    else
759
      locale = I18n.default_locale
592✔
760
    end
8✔
761
    I18n.with_locale(locale, &action)
2,826✔
762
  end
763

764
  def current_record
3✔
765
    raise "method 'current_record' not implemented for controller: #{self}"
766
  end
767

768
  def controller_supports_current_record?
3✔
769
    self.method(:current_record).owner != ApplicationController
8✔
770
  end
8✔
771

772
  def check_required_subrecords(required, obj)
3✔
773
    required.each do |subrecord_field, requirements_defn|
774
      next unless requirements_defn.is_a?(Array)
775
      if obj[subrecord_field].empty?
776
        obj.add_error(subrecord_field, :missing_required_subrecord)
777
      end
778
    end
779
  end
780

781
  helper_method :current_record
3✔
782
  helper_method :'controller_supports_current_record?'
3✔
783
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc