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

benwbrum / fromthepage / 18198125262

02 Oct 2025 03:43PM UTC coverage: 65.028%. Remained the same
18198125262

push

github

web-flow
Rubocop lint remove array whitespaces (#4950)

1848 of 3359 branches covered (55.02%)

Branch coverage included in aggregate %.

154 of 201 new or added lines in 62 files covered. (76.62%)

8087 of 11919 relevant lines covered (67.85%)

109.57 hits per line

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

62.94
/app/controllers/collection_controller.rb
1
# handles administrative tasks for the collection object
2
class CollectionController < ApplicationController
1✔
3
  include ApplicationHelper
1✔
4
  include ContributorHelper
1✔
5
  include AddWorkHelper
1✔
6
  include CollectionHelper
1✔
7

8
  public :render_to_string
1✔
9

10
  protect_from_forgery except: [
1✔
11
    :set_collection_title,
12
    :set_collection_intro_block,
13
    :set_collection_footer_block
14
  ]
15

16
  edit_actions = [:edit, :edit_tasks, :edit_look, :edit_privacy, :edit_help, :edit_quality_control, :edit_danger]
1✔
17

18
  before_action :set_collection, only: edit_actions + [:show, :update, :contributors, :new_work, :works_list, :needs_transcription_pages, :needs_review_pages, :start_transcribing]
1✔
19
  before_action :authorized?, only: [
1✔
20
    :new,
21
    :edit,
22
    :edit_tasks,
23
    :edit_look,
24
    :edit_privacy,
25
    :edit_help,
26
    :edit_quality_control,
27
    :edit_danger,
28
    :update,
29
    :blank_collection,
30
    :delete,
31
    :create,
32
    :edit_owners,
33
    :remove_owner,
34
    :add_owner,
35
    :edit_collaborators,
36
    :remove_collaborator,
37
    :add_collaborator,
38
    :edit_reviewers,
39
    :remove_reviewer,
40
    :add_reviewer,
41
    :new_mobile_user,
42
    :search_users,
43
    :edit_buttons,
44
    :update_buttons
45
  ]
46
  before_action :review_authorized?, only: [:reviewer_dashboard, :works_to_review, :one_off_list, :recent_contributor_list, :user_contribution_list]
1✔
47
  before_action :load_settings, only: [:upload, :edit_collaborators, :edit_owners, :block_users, :remove_owner, :remove_collaborator, :edit_reviewers, :remove_reviewer]
1✔
48
  before_action :permit_only_transcribed_works_flag, only: [:works_list]
1✔
49

50
  def search_users
1✔
51
    query = "%#{params[:term].to_s.downcase}%"
4✔
52
    user_type = (params[:user_type] || 'collaborator').to_sym
4✔
53

54
    owner_ids = @collection.owners.select(:id)
4✔
55
    blocked_user_ids = @collection.blocked_users.select(:id)
4✔
56
    reviewer_ids = @collection.reviewers.select(:id)
4✔
57
    collaborator_ids = @collection.collaborators.select(:id)
4✔
58

59
    excluded_ids = case user_type
4✔
60
    when :owner
1✔
61
                     User.where(id: owner_ids).or(User.where(id: blocked_user_ids)).select(:id)
1✔
62
    when :blocked
1✔
63
                     User.where(id: blocked_user_ids).or(User.where(id: owner_ids)).select(:id)
1✔
64
    when :reviewer
1✔
65
                     reviewer_ids
1✔
66
    else
67
                     # collaborator
1✔
68
                     collaborator_ids
1✔
69
    end
70

71
    users = User.where('LOWER(real_name) LIKE :search OR LOWER(email) LIKE :search', search: query)
4✔
72
                .where.not(id: excluded_ids)
73
                .where.not(id: @collection.owner.id)
74
                .limit(100)
75

76
    render json: { results: users.map { |u| { text: "#{u.display_name} #{u.email}", id: u.id } } }
4✔
77
  end
78

79
  def reviewer_dashboard
1✔
80
    # works which have at least one page needing review
81
    @total_pages=@collection.pages.count
×
82
    @pages_needing_review=@collection.pages.where(status: :needs_review).count
×
83
    @transcribed_pages=@collection.pages.where(status: Page::NOT_INCOMPLETE_STATUSES).count
×
84
    @works_to_review = @collection.pages.where(status: :needs_review).pluck(:work_id).uniq.count
×
85
  end
86

87
  def works_to_review
1✔
88
    @works = @collection.works.joins(:work_statistic).includes(:notes, :pages).where.not('work_statistics.needs_review' => 0).reorder('works.title')
×
89
                        .paginate(page: params[:page], per_page: 15)
90
  end
91

92
  def one_off_list
1✔
93
    @pages = @collection.pages_needing_review_for_one_off
1✔
94
  end
95

96
  def recent_contributor_list
1✔
97
    @unreviewed_users = @collection.never_reviewed_users
1✔
98
  end
99

100
  def user_contribution_list
1✔
101
    unless params[:quality_sampling_id].blank?
×
102
      @quality_sampling = QualitySampling.find(params[:quality_sampling_id])
×
103
    end
104
    @pages = @collection.pages.where(status: :needs_review).where(last_editor_user_id: @user.id)
×
105
  end
106

107
  def approve_all
1✔
108
    @quality_sampling = QualitySampling.find(params[:quality_sampling_id])
×
109
    @pages = @collection.pages.where(status: :needs_review).where(last_editor_user_id: @user.id)
×
110
    page_count = @pages.count
×
111
    @pages.update_all(status: :transcribed)
×
112
    @collection.works.each do |work|
×
113
      work.work_statistic.recalculate({ type: Page.statuses[:needs_review] }) if work.work_statistic
×
114
    end
115
    flash[:notice] = t('.approved_n_pages', page_count: page_count)
×
116
    redirect_to(collection_quality_sampling_path(@collection.owner, @collection, @quality_sampling))
×
117
  end
118

119
  def edit_buttons
1✔
120
    @prefer_html = @collection.editor_buttons.where(prefer_html: true).exists?
1✔
121
  end
122

123
  def update_buttons
1✔
124
    @collection.editor_buttons.delete_all
1✔
125

126
    prefer_html = (params[:prefer_html] == 'true')
1✔
127

128
    EditorButton::BUTTON_MAP.keys.each do |key|
1✔
129
      if params[key] == '1'
20!
130
        button_config = EditorButton.new
×
131
        button_config.key = key
×
132
        button_config.prefer_html = prefer_html
×
133
        button_config.collection = @collection
×
134
        button_config.save
×
135
      end
136
    end
137

138
    flash[:notice] = t('.editor_buttons_updated')
1✔
139
    ajax_redirect_to(edit_tasks_collection_path(@collection.owner, @collection))
1✔
140
  end
141

142
  def search
1✔
143
    @search_string = search_params[:term]
2✔
144
    @es_query = Elasticsearch::MultiQuery.new(
2✔
145
      query: @search_string,
146
      query_params: {
147
        mode: @collection.is_a?(DocumentSet) ? 'docset' : 'collection',
2✔
148
        slug: @collection.slug
149
      },
150
      page: params[:page] || 1,
151
      scope: search_params[:filter],
152
      user: current_user
153
    ).call
154

155
    @breadcrumb_scope = { collection: true }
2✔
156
    if @collection.is_a?(DocumentSet)
2✔
157
      @docset_filter = @es_query.docset_filter
1✔
158
    else
1✔
159
      @collection_filter = @es_query.collection_filter
1✔
160
    end
161

162
    @search_results = @es_query.results
2✔
163
    @full_count = @es_query.total_count
2✔
164
    @type_counts = @es_query.type_counts
2✔
165
  end
166

167
  def facets
1✔
168
    collection = Collection.find(params[:collection_id])
6✔
169
    @metadata_coverages = collection.metadata_coverages
6✔
170
  end
171

172
  def facet_search
1✔
173
    mc = @collection.metadata_coverages.where(key: params['facet_search']['label']).first
×
174
    first_year = params['facet_search']['date'].split.first.to_i
×
175
    last_year = params['facet_search']['date'].split.last.to_i
×
176
    order = params['facet_search']['date_order'].to_i
×
177
    years = (first_year..last_year).to_a
×
178
    date_order = "d#{order}"
×
179
    year = "w.work_facet.#{date_order}.year"
×
180

181
    facets = []
×
182

183
    @collection.works.each do |w|
×
184
      unless w.work_facet.nil?
×
185
        if years.include?(eval(year))
×
186
          facets << w.work_facet
×
187
        end
188
      end
189
    end
190

191
    facet_ids = facets.pluck(:id)
×
192

193
    @works = Work.joins(:work_facet).where('work_facets.id in (?)', facet_ids).paginate(page: params[:page], per_page: 10)
×
194
    @search = WorkSearch.new(params[:page])
×
195

NEW
196
    render plain: @works.to_json(methods: [:thumbnail])
×
197
  end
198

199
  def new_mobile_user
1✔
200
    # don't show popup again
201
    session[:new_mobile_user] = false
×
202
  end
203

204
  def show
1✔
205
    if current_user && CollectionBlock.find_by(collection_id: @collection.id, user_id: current_user.id).present?
116!
206
      flash[:error] = t('unauthorized_collection', project: @collection.title)
×
207
      redirect_to user_profile_path(@collection.owner)
×
208
    else
116✔
209
      if @collection.alphabetize_works
116✔
210
        order_clause = 'works.title ASC'
116✔
211
      else
×
212
        order_clause = 'work_statistics.complete ASC, work_statistics.transcribed_percentage ASC, work_statistics.needs_review_percentage DESC'
×
213
      end
214

215
      @new_mobile_user = !!(session[:new_mobile_user])
116✔
216
      unless @collection.nil?
116✔
217
        if @collection.restricted
116✔
218
          if !user_signed_in? || !@collection.show_to?(current_user)
11✔
219
            flash[:error] = t('unauthorized_collection', project: @collection.title)
1✔
220
            redirect_to user_profile_path(@collection.owner)
1✔
221
          end
222
        end
223

224
        # Coming from work title/metadata search
225
        if params[:search_attempt_id]
116!
226
          @search_attempt = SearchAttempt.find_by(id: params[:search_attempt_id])
×
227
          if session[:search_attempt_id] != @search_attempt.id
×
228
            session[:search_attempt_id] = @search_attempt.id
×
229
          end
230
          @works = @search_attempt.query_results.paginate(page: params[:page], per_page: 10)
×
231

116✔
232
        elsif params[:works] == 'untranscribed'
116!
233
          ids = @collection.works.includes(:work_statistic).where.not(work_statistics: { complete: 100 }).pluck(:id)
×
234
          @works = @collection.works.order_by_incomplete.where(id: ids).paginate(page: params[:page], per_page: 10)
×
235
          # show all works
116✔
236
        elsif params[:works] == 'show'
116✔
237
          @works = @collection.works.joins(:work_statistic).reorder(order_clause).paginate(page: params[:page], per_page: 10)
4✔
238
          # hide incomplete works
112✔
239
        elsif params[:works] == 'hide' || (@collection.hide_completed)
112✔
240
          # find ids of completed translation works
90✔
241
          translation_ids = @collection.works.incomplete_translation.pluck(:id)
90✔
242
          # find ids of completed transcription works
243
          transcription_ids = @collection.works.incomplete_transcription.pluck(:id)
90✔
244
          # combine ids anduse to get works that aren't complete
245
          ids = translation_ids + transcription_ids
90✔
246

247
          if @collection.metadata_entry?
90✔
248
            description_ids = @collection.works.incomplete_description.pluck(:id)
10✔
249
            ids += description_ids
10✔
250
          end
251

252
          works = @collection.works.joins(:work_statistic).where(id: ids).reorder(order_clause).paginate(page: params[:page], per_page: 10)
90✔
253

254
          if works.empty?
90✔
255
            @works = @collection.works.joins(:work_statistic).reorder(order_clause).paginate(page: params[:page], per_page: 10)
7✔
256
          else
83✔
257
            @works = works
83✔
258
          end
259
        else
22✔
260
          @works = @collection.works.joins(:work_statistic).reorder(order_clause).paginate(page: params[:page], per_page: 10)
22✔
261
        end
262

263
        if @collection.facets_enabled?
116✔
264
          # construct the search object from the parameters
×
265
          @search = WorkSearch.new(params)
×
NEW
266
          @search.filter([:work, :collection_id]).value=@collection.id
×
267
          # the search results are WorkFacets, not works, so we need to fetch the works themselves
268
          facet_ids = @search.result.pluck(:id)
×
269
          @works = @collection.works.joins(:work_facet).where('work_facets.id in (?)', facet_ids).includes(:work_facet).paginate(page: params[:page], per_page: @per_page) unless params[:search].is_a?(String)
×
270

271
          @date_ranges = []
×
272
          date_configs = @collection.facet_configs.where(input_type: 'date').where.not(order: nil).order(order: :asc)
×
273
          if date_configs.size > 0
×
274
            collection_facets = WorkFacet.joins(:work).where("works.collection_id = #{@collection.id}")
×
275
            date_configs.each do |facet_config|
×
NEW
276
              facet_attr = [:d0, :d1, :d2][facet_config.order]
×
277

278
              selection_values = @works.map { |w| w.work_facet.send(facet_attr) }.reject { |v| v.nil? }
×
279
              collection_values = collection_facets.map { |work_facet| work_facet.send(facet_attr) }.reject { |v| v.nil? }
×
280

281
              @date_ranges << {
×
282
                facet: facet_attr,
283
                max: collection_values.max.year,
284
                min: collection_values.min.year,
285
                top: selection_values.max.year,
286
                bottom: selection_values.min.year
287
              }
288
            end
289
          end
290

291
          if params[:search_attempt_id]
×
292
            @search_attempt = SearchAttempt.find_by(id: params[:search_attempt_id])
×
293
            if session[:search_attempt_id] != @search_attempt.id
×
294
              session[:search_attempt_id] = @search_attempt.id
×
295
            end
296
            @works = @search_attempt.query_results.paginate(page: params[:page], per_page: 10)
×
297
          elsif params[:works] == 'untranscribed'
×
298
            ids = @collection.works.includes(:work_statistic).where.not(work_statistics: { complete: 100 }).pluck(:id)
×
299
            @works = @collection.works.order_by_incomplete.where(id: ids).paginate(page: params[:page], per_page: 10)
×
300
            # show all works
×
301
          elsif params[:works] == 'show'
×
302
            @works = @collection.works.joins(:work_statistic).reorder(order_clause).paginate(page: params[:page], per_page: 10)
×
303
            # hide incomplete works
×
304
          elsif params[:works] == 'hide' || (@collection.hide_completed)
×
305
            # find ids of completed translation works
×
306
            translation_ids = @collection.works.incomplete_translation.pluck(:id)
×
307
            # find ids of completed transcription works
308
            transcription_ids = @collection.works.incomplete_transcription.pluck(:id)
×
309
            # combine ids anduse to get works that aren't complete
310
            ids = translation_ids + transcription_ids
×
311

312
            if @collection.metadata_entry?
×
313
              description_ids = @collection.works.incomplete_description.pluck(:id)
×
314
              ids += description_ids
×
315
            end
316

317
            works = @collection.works.joins(:work_statistic).where(id: ids).reorder(order_clause).paginate(page: params[:page], per_page: 10)
×
318

319
            if works.empty?
×
320
              @works = @collection.works.joins(:work_statistic).reorder(order_clause).paginate(page: params[:page], per_page: 10)
×
321
            else
×
322
              @works = works
×
323
            end
324
          else
×
325
            @works = @collection.works.joins(:work_statistic).reorder(order_clause).paginate(page: params[:page], per_page: 10)
×
326
          end
327

328
          if @collection.facets_enabled?
×
329
            # construct the search object from the parameters
×
330
            @search = WorkSearch.new(params)
×
NEW
331
            @search.filter([:work, :collection_id]).value=@collection.id
×
332
            # the search results are WorkFacets, not works, so we need to fetch the works themselves
333
            facet_ids = @search.result.pluck(:id)
×
334
            @works = @collection.works.joins(:work_facet).where('work_facets.id in (?)', facet_ids).includes(:work_facet).paginate(page: params[:page], per_page: @per_page) unless params[:search].is_a?(String)
×
335

336
            @date_ranges = []
×
337
            date_configs = @collection.facet_configs.where(input_type: 'date').where.not(order: nil).order(order: :asc)
×
338
            if date_configs.size > 0
×
339
              collection_facets = WorkFacet.joins(:work).where("works.collection_id = #{@collection.id}")
×
340
              date_configs.each do |facet_config|
×
NEW
341
                facet_attr = [:d0, :d1, :d2][facet_config.order]
×
342

343
                selection_values = @works.map { |w| w.work_facet.send(facet_attr) }.reject { |v| v.nil? }
×
344
                collection_values = collection_facets.map { |work_facet| work_facet.send(facet_attr) }.reject { |v| v.nil? }
×
345

346
                @date_ranges << {
×
347
                  facet: facet_attr,
348
                  max: collection_values.max.year,
349
                  min: collection_values.min.year,
350
                  top: selection_values.max.year,
351
                  bottom: selection_values.min.year
352
                }
353
              end
354
            end
355
          end
356
        end
357

358
        # Set meta information for collection pages for better archival
359
        @page_title = "#{@collection.title} - FromThePage"
116✔
360
        @meta_description = "#{@collection.title}: #{to_snippet(@collection.intro_block)}".truncate(160)
116✔
361
        @meta_keywords = [@collection.title, 'historical documents', 'digital archive', 'transcription', 'collection'].compact.join(', ')
116✔
362

363
        # Generate structured data for collection
364
        @structured_data = {
365
          '@context' => 'https://schema.org',
116✔
366
          '@type' => 'Collection',
367
          'name' => @collection.title,
368
          'description' => to_snippet(@collection.intro_block),
369
          'url' => request.original_url,
370
          'dateModified' => @collection.most_recent_deed_created_at&.iso8601,
116✔
371
          'publisher' => {
372
            '@type' => 'Organization',
373
            'name' => 'FromThePage'
374
          },
375
          'numberOfItems' => @collection.works_count
376
        }
377

378
        # Add archival-friendly headers
379
        respond_to do |format|
116✔
380
          format.html do
116✔
381
            response.headers['X-Robots-Tag'] = 'index, follow, archive'
116✔
382
          end
383
        end
384
      else
×
385
        redirect_to '/404'
×
386
      end
387
    end
388
  end
389

390
  def add_owner
1✔
391
    unless @user.owner
4✔
392
      @user.owner = true
3✔
393
      @user.account_type = 'Staff'
3✔
394
      @user.save!
3✔
395
    end
396
    @collection.owners << @user
4✔
397
    @user.notification.owner_stats = true
4✔
398
    @user.notification.save!
4✔
399
    if @user.notification.add_as_owner
4✔
400
      send_email(@user, @collection)
3✔
401
    end
402
    redirect_to collection_edit_owners_path(@collection)
4✔
403
  end
404

405
  def remove_owner
1✔
406
    @collection.owners.delete(@user)
2✔
407
    redirect_to collection_edit_owners_path(@collection)
2✔
408
  end
409

410
  def remove_block_user
1✔
411
    collection_block = CollectionBlock.find_by(collection_id: @collection.id, user_id: @user.id)
×
412
    collection_block.destroy if collection_block
×
413
    redirect_to collection_block_users_path(@collection)
×
414
  end
415

416
  def block_users
1✔
417
    @list_of_blocked_users = @collection.blocked_users
×
418
    render layout: false
×
419
  end
420

421
  def add_collaborator
1✔
422
    collaborator = User.find_by(id: params[:collaborator_id])
2✔
423
    @collection.collaborators << collaborator
2✔
424
    send_email(collaborator, @collection) if collaborator.notification.add_as_collaborator
2✔
425

426
    redirect_to collection_edit_collaborators_path(@collection)
2✔
427
  end
428

429
  def remove_collaborator
1✔
430
    collaborator = User.find_by(id: params[:collaborator_id])
2✔
431
    @collection.collaborators.delete(collaborator)
2✔
432

433
    redirect_to collection_edit_collaborators_path(@collection)
2✔
434
  end
435

436
  def add_reviewer
1✔
437
    @collection.reviewers << @user
×
438
    if @user.notification.add_as_collaborator
×
439
      send_email(@user, @collection)
×
440
    end
441
    redirect_to collection_edit_reviewers_path(@collection)
×
442
  end
443

444
  def add_block_user
1✔
445
    CollectionBlock.create(collection_id: @collection.id, user_id: @user.id)
×
446
    redirect_to collection_block_users_path(@collection)
×
447
  end
448

449
  def remove_reviewer
1✔
450
    @collection.reviewers.delete(@user)
×
451
    redirect_to collection_edit_reviewers_path(@collection)
×
452
  end
453

454
  def send_email(user, collection)
1✔
455
    if SMTP_ENABLED
4✔
456
      begin
457
        UserMailer.collection_collaborator(user, collection).deliver!
4✔
458
      # :nocov:
459
      rescue StandardError => e
460
        print "SMTP Failed: Exception: #{e.message}"
461
      end
462
      # :nocov:
463
    end
464
  end
465

466
  def publish_collection
1✔
467
    @collection.restricted = false
×
468
    @collection.save!
×
469
    redirect_back fallback_location: edit_privacy_collection_path(@collection.owner, @collection)
×
470
  end
471

472
  def toggle_collection_active(is_active)
1✔
473
    # Register New Deed for In/Active
474
    deed = Deed.new
×
475
    deed.collection = @collection
×
476
    deed.user = current_user
×
477
    if is_active
×
478
      deed.deed_type = DeedType::COLLECTION_ACTIVE
×
479
    else
×
480
      deed.deed_type = DeedType::COLLECTION_INACTIVE
×
481
    end
482
    deed.save!
×
483
  end
484

485
  def restrict_collection
1✔
486
    @collection.restricted = true
×
487
    @collection.save!
×
488
    redirect_back fallback_location: edit_privacy_collection_path(@collection.owner, @collection)
×
489
  end
490

491
  def restrict_transcribed
1✔
492
    @result = Collection::RestrictTranscribed.new(collection: @collection).call
2✔
493

494
    respond_to(&:turbo_stream)
2✔
495
  end
496

497
  def enable_fields
1✔
498
    @collection.field_based = true
×
499
    @collection.voice_recognition = false
×
500
    @collection.language = nil
×
501
    @collection.save!
×
502
    redirect_to collection_edit_fields_path(@collection.owner, @collection)
×
503
  end
504

505
  def delete
1✔
506
    @collection.destroy
4✔
507

508
    flash[:notice] = t('.notice')
4✔
509
    redirect_to dashboard_owner_path
4✔
510
  end
511

512
  def new
1✔
513
    @collection = Collection.new
9✔
514
  end
515

516
  def edit
1✔
517
    @tags_options = Tag.where(canonical: true)
50✔
518
    @selected_tags = @collection.tags.pluck(:id)
50✔
519
  end
520

521
  def edit_tasks
1✔
522
    flash.now[:info] = t('.alert') if @collection.field_based && !@collection.transcription_fields.present?
17✔
523
  end
524

525
  def edit_look
1✔
526
    # Edit look form
527
  end
528

529
  def edit_privacy
1✔
530
    @main_owner = @collection.owner
9✔
531
    @collaborators = @collection.collaborators
9✔
532
    @owners = User.where(id: @main_owner.id).or(User.where(id: @collection.owners.select(:id)))
9✔
533
    @blocked_users = @collection.blocked_users
9✔
534
    @works_to_restrict_count = works_to_restrict_count
9✔
535
  end
536

537
  def edit_help
1✔
538
    @works_with_custom_conventions = @collection.works
4✔
539
                                                .includes(collection: :owner)
540
                                                .where.not(transcription_conventions: nil)
541
  end
542

543
  def edit_quality_control
1✔
544
    @reviewers = @collection.reviewers
2✔
545
  end
546

547
  def edit_danger
1✔
548
    # Edit danger form
549
  end
550

551
  def update
1✔
552
    @result = Collection::Update.new(
33✔
553
      collection: @collection,
554
      collection_params: collection_params,
555
      user: current_user
556
    ).call
557

558
    @collection = @result.collection
33✔
559

560
    respond_to do |format|
33✔
561
      template = case params[:scope]
33✔
562
      when 'edit_tasks'
9✔
563
                   'collection/update_tasks'
9✔
564
      when 'edit_look'
10✔
565
                   'collection/update_look'
10✔
566
      when 'edit_privacy'
3✔
567
                   @main_owner = @collection.owner
3✔
568
                   @collaborators = @collection.collaborators
3✔
569
                   @owners = User.where(id: @main_owner.id).or(User.where(id: @collection.owners.select(:id)))
3✔
570
                   @blocked_users = @collection.blocked_users
3✔
571
                   @works_to_restrict_count = works_to_restrict_count
3✔
572
                   'collection/update_privacy'
3✔
573
      when 'edit_help'
2✔
574
                   @works_with_custom_conventions = @collection.works
2✔
575
                                                               .includes(collection: :owner)
576
                                                               .where.not(transcription_conventions: nil)
577
                   'collection/update_help'
2✔
578
      when 'edit_quality_control'
2✔
579
                   @reviewers = @collection.reviewers
2✔
580
                   'collection/update_quality_control'
2✔
581
      when 'edit_danger'
3✔
582
                   'collection/update_danger'
3✔
583
      else
584
                   # edit
4✔
585
                   @tags_options = Tag.where(canonical: true)
4✔
586
                   @selected_tags = @collection.tags.pluck(:id)
4✔
587
                   'collection/update_general'
4✔
588
      end
589

590
      format.turbo_stream { render template }
66✔
591
    end
592
  end
593

594
  # tested
595
  def create
1✔
596
    @collection = Collection.new
7✔
597
    @collection.title = params[:collection][:title]
7✔
598
    @collection.intro_block = params[:collection][:intro_block]
7✔
599
    if current_user.account_type != 'Staff'
7✔
600
      @collection.owner = current_user
6✔
601
    else
1✔
602
      extant_collection = current_user.collections.detect { |c| c.owner.account_type != 'Staff' }
2✔
603
      @collection.owner = extant_collection.owner
1✔
604
      @collection.owners << current_user
1✔
605
    end
606
    if @collection.save
7✔
607
      flash[:notice] = t('.notice')
7✔
608
      if request.referrer.include?('sc_collections')
7!
609
        session[:iiif_collection] = @collection.id
×
610
        ajax_redirect_to(request.referrer)
×
611
      else
7✔
612
        ajax_redirect_to({ controller: 'dashboard', action: 'startproject', collection_id: @collection.id })
7✔
613
      end
614
    else
×
615
      render action: 'new'
×
616
    end
617
  end
618

619
  def add_work_to_collection
1✔
620
    logger.debug("DEBUG collection1=#{@collection}")
×
621
    set_collection_for_work(@collection, @work)
×
622
    logger.debug("DEBUG collection2=#{@collection}")
×
623
    # redirect_to action: 'edit', collection_slug: @collection.slug
624
    redirect_to action: 'edit', collection_id: @collection.id
×
625
  end
626

627
  def remove_work_from_collection
1✔
628
    set_collection_for_work(nil, @work)
×
629
    # redirect_to action: 'edit', collection_slug: @collection.slug
630
    redirect_to action: 'edit', collection_id: @collection.id
×
631
  end
632

633
  def new_work
1✔
634
    @work = Work.new
1✔
635
    @work.collection = @collection
1✔
636
    @document_upload = DocumentUpload.new
1✔
637
    @document_upload.collection=@collection
1✔
638
    @universe_collections = ScCollection.universe
1✔
639
    @sc_collections = ScCollection.all
1✔
640
  end
641

642
  def contributors
1✔
643
    # Get the start and end date params from date picker, if none, set defaults
644
    start_date = params[:start_date]
2✔
645
    end_date = params[:end_date]
2✔
646

647
    if start_date.nil?
2!
648
      start_date = 1.week.ago
2✔
649
      end_date = DateTime.now.utc
2✔
650
    end
651

652
    start_date = start_date.to_datetime.beginning_of_day
2✔
653
    end_date = end_date.to_datetime.end_of_day
2✔
654

655
    @start_deed = start_date.strftime('%b %d, %Y')
2✔
656
    @end_deed = end_date.strftime('%b %d, %Y')
2✔
657

658
    new_contributors(@collection, start_date, end_date)
2✔
659
    @stats = @collection.get_stats_hash(start_date, end_date)
2✔
660
  end
661

662
  def blank_collection
1✔
663
    @result = Collection::Blankout.new(collection: @collection).call
2✔
664

665
    flash[:notice] = t('.notice')
2✔
666
    redirect_to collection_path(@result.collection.owner, @result.collection)
2✔
667
  end
668

669
  def works_list
1✔
670
    filtered_works
14✔
671

672
    respond_to do |format|
14✔
673
      format.html
14✔
674
      format.turbo_stream
14✔
675
    end
676
  end
677

678
  def needs_transcription_pages
1✔
679
    work_ids = @collection.works.pluck(:id)
1✔
680
    @review='transcription'
1✔
681
    @pages = Page.where(work_id: work_ids).joins(:work).merge(Work.unrestricted).needs_transcription.order(work_id: :asc, position: :asc).paginate(page: params[:page], per_page: 10)
1✔
682
    @count = @pages.count
1✔
683
    @incomplete_pages = Page.where(work_id: work_ids).joins(:work).merge(Work.unrestricted).needs_completion.order(work_id: :asc, position: :asc).paginate(page: params[:page], per_page: 10)
1✔
684
    @incomplete_count = @incomplete_pages.count
1✔
685
    @heading = t('.pages_need_transcription')
1✔
686
  end
687

688
  def needs_review_pages
1✔
689
    work_ids = @collection.works.pluck(:id)
1✔
690
    @review='review'
1✔
691
    @pages = Page.where(work_id: work_ids).joins(:work).merge(Work.unrestricted).review.paginate(page: params[:page], per_page: 10)
1✔
692
    @heading = t('.pages_need_review')
1✔
693
  end
694

695
  def needs_metadata_works
1✔
696
    if params['need_review']
×
697
      @works = @collection.works.where(description_status: 'needsreview')
×
698
    else
×
NEW
699
      @works = @collection.works.where(description_status: ['incomplete', 'undescribed'])
×
700
    end
701
  end
702

703
  def start_transcribing
1✔
704
    page = find_untranscribed_page
1✔
705
    if page.nil?
1!
706
      flash[:notice] = t('.notice')
×
707
      redirect_to collection_path(@collection.owner, @collection)
×
708
    else
1✔
709
      if !user_signed_in?
1✔
710
        redirect_to collection_guest_page_path(page.collection.owner, page.collection, page.work, page)
1✔
711
      else
×
712
        redirect_to collection_transcribe_page_path(page.collection.owner, page.collection, page.work, page)
×
713
      end
714
    end
715
  end
716

717
  def enable_ocr
1✔
718
    @collection.enable_ocr
1✔
719
    flash[:notice] = t('.notice')
1✔
720
    redirect_back fallback_location: edit_tasks_collection_path(@collection.owner, @collection)
1✔
721
  end
722

723
  def disable_ocr
1✔
724
    @collection.disable_ocr
1✔
725
    flash[:notice] = t('.notice')
1✔
726
    redirect_back fallback_location: edit_tasks_collection_path(@collection.owner, @collection)
1✔
727
  end
728

729
  def email_link
1✔
730
    if SMTP_ENABLED
×
731
      begin
×
732
        UserMailer.new_mobile_user(current_user, @collection).deliver!
×
733
      rescue StandardError => e
734
        log_smtp_error(e, current_user)
×
735
      end
736
    end
737
    flash[:notice] = 'Email sent.'
×
738
    ajax_redirect_to(collection_path(@collection.owner, @collection))
×
739
  end
740

741
  private
1✔
742

743
  def authorized?
1✔
744
    unless user_signed_in?
215✔
745
      respond_to do |format|
10✔
746
        format.html { redirect_to dashboard_path }
20✔
747
        format.js   { ajax_redirect_to dashboard_path }
10✔
748
      end
749
      return
10✔
750
    end
751

752
    if @collection && !current_user.like_owner?(@collection)
205✔
753
      respond_to do |format|
10✔
754
        format.html { redirect_to collection_path(@collection.owner, @collection) }
20✔
755
        format.js   { ajax_redirect_to collection_path(@collection.owner, @collection) }
10✔
756
      end
757
      nil
758
    end
759
  end
760

761
  def review_authorized?
1✔
762
    unless user_signed_in? && current_user.can_review?(@collection)
2!
763
      redirect_to new_user_session_path
×
764
    end
765
  end
766

767
  def set_collection
1✔
768
    unless @collection
291✔
769
      if Collection.friendly.exists?(params[:id])
236✔
770
        @collection = Collection.friendly.find(params[:id])
216✔
771
      elsif DocumentSet.friendly.exists?(params[:id])
20✔
772
        @collection = DocumentSet.friendly.find(params[:id])
20!
773
      elsif !DocumentSet.find_by(slug: params[:id]).nil?
×
774
        @collection = DocumentSet.find_by(slug: params[:id])
×
775
      elsif !Collection.find_by(slug: params[:id]).nil?
×
776
        @collection = Collection.find_by(slug: params[:id])
×
777
      end
778
    end
779
  end
780

781
  def set_collection_for_work(collection, work)
1✔
782
    # first update the id on the work
783
    work.collection = collection
×
784
    work.save!
×
785
    # then update the id on the articles
786
    # consider moving this to the work model?
787
    for article in work.articles
×
788
      article.collection = collection
×
789
      article.save!
×
790
    end
791
  end
792

793
  def updated_fields_hash
1✔
NEW
794
    @collection.changed.to_h { |field| [field, @collection.send(field)] }
×
795
  end
796

797
  def collection_params
1✔
798
    params.require(:collection).permit(
33✔
799
      :title,
800
      :slug,
801
      :intro_block,
802
      :legend,
803
      :transcription_conventions,
804
      :help,
805
      :link_help,
806
      :subjects_disabled,
807
      :subjects_enabled,
808
      :review_type,
809
      :hide_completed,
810
      :text_language,
811
      :default_orientation,
812
      :voice_recognition,
813
      :picture,
814
      :user_download,
815
      :enable_spellcheck,
816
      :messageboards_enabled,
817
      :facets_enabled,
818
      :supports_document_sets,
819
      :api_access,
820
      :data_entry_type,
821
      :field_based,
822
      :is_active,
823
      :search_attempt_id,
824
      :alphabetize_works,
825
      :restricted,
826
      tags: []
827
    )
828
  end
829

830
  def load_settings
1✔
831
    @main_owner = @collection.owner
20✔
832
    @owners = ([@main_owner] + @collection.owners).sort_by(&:display_name)
20✔
833
    @works_not_in_collection = current_user.owner_works - @collection.works
20✔
834
    @collaborators = @collection.collaborators
20✔
835
    @reviewers = @collection.reviewers
20✔
836
    @blocked_users = @collection.blocked_users.sort_by(&:display_name)
20✔
837

838
    collection_owner_ids = @owners.pluck(:id)
20✔
839
    @nonowners = User.where.not(id: collection_owner_ids).order(:display_name).limit(100)
20✔
840
    @noncollaborators = User.where.not(id: @collaborators.pluck(:id) + collection_owner_ids).order(:display_name).limit(100)
20✔
841
    @nonreviewers = User.where.not(id: @reviewers.pluck(:id) + collection_owner_ids).order(:display_name).limit(100)
20✔
842

843
    @collaborators = @collaborators.sort_by(&:display_name)
20✔
844
    @reviewers = @reviewers.sort_by(&:display_name)
20✔
845
  end
846

847
  def permit_only_transcribed_works_flag
1✔
848
    params.permit(:only_transcribed)
14✔
849
  end
850

851
  def filtered_works
1✔
852
    @sorting = (params[:sort] || 'title').to_sym
14✔
853
    @ordering = (params[:order] || 'ASC').downcase.to_sym
14✔
854
    @ordering = [:asc, :desc].include?(@ordering) ? @ordering : :desc
14!
855

856
    works_scope = @collection.works.includes(:work_statistic, :deeds)
14✔
857
    if params[:show] == 'need_transcription'
14✔
858
      works_scope = works_scope.joins(:work_statistic)
1✔
859
                               .where('work_statistics.complete < ?', 100)
860
                               .where('work_statistics.transcribed_percentage < ?', 100)
861
    end
862

863
    if params[:search]
14✔
864
      query = "%#{params[:search].to_s.downcase}%"
3✔
865
      works_scope = works_scope.where(
3✔
866
        'LOWER(works.title) LIKE :search OR LOWER(works.searchable_metadata) like :search',
867
        search: "%#{query}%"
868
      )
869
    end
870

871
    case @sorting
14✔
872
    when :activity
3✔
873
      works_scope = works_scope.left_joins(:deeds)
3✔
874
                               .reorder(Arel.sql("COALESCE((SELECT created_at FROM deeds WHERE deeds.work_id = works.id ORDER BY created_at DESC LIMIT 1), works.created_on) #{@ordering}"))
875
    when :collaboration
1✔
876
      works_scope = works_scope.reorder(restrict_scribes: @ordering)
1✔
877
    else
10✔
878
      works_scope = works_scope.reorder(title: @ordering)
10✔
879
    end
880

881
    works_scope = works_scope.distinct.paginate(page: params[:page], per_page: per_page) unless per_page == -1
14✔
882

883
    @works = works_scope
14✔
884
  end
885

886
  def search_params
1✔
887
    params.permit(:term, :page, :filter, :collection_id, :user_id)
4✔
888
  end
889

890
  def works_to_restrict_count
1✔
891
    @collection.works
12✔
892
               .joins(:work_statistic)
893
               .where(work_statistics: { complete: 100 }, restrict_scribes: false)
894
               .count
895
  end
896
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

© 2026 Coveralls, Inc