Skip to content

Commit

Permalink
Merge pull request #1890 from DFE-Digital/upload-convert-to-pdf
Browse files Browse the repository at this point in the history
Convert files to PDF on upload
  • Loading branch information
thomasleese authored Jan 10, 2024
2 parents de86cae + 14b17ed commit 4a50b6c
Show file tree
Hide file tree
Showing 35 changed files with 395 additions and 242 deletions.
11 changes: 9 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ RUN apk upgrade --no-cache openssl libssl3 libcrypto3 curl
# postgresql-dev: postgres driver and libraries
# git: dependencies for bundle
# vips-dev: dependencies for ruby-vips (image processing library)
RUN apk add --update --no-cache build-base yarn postgresql14-dev git vips-dev
# imagemagick-dev: dependencies for rmagick (image conversion library)
RUN apk add --update --no-cache build-base yarn postgresql14-dev git vips-dev imagemagick-dev

# Install gems defined in Gemfile
COPY Gemfile Gemfile.lock ./
Expand Down Expand Up @@ -82,7 +83,13 @@ RUN apk upgrade --no-cache openssl libssl3 libcrypto3 curl

# libpq: required to run postgres
# vips-dev: dependencies for ruby-vips (image processing library)
RUN apk add --update --no-cache libpq vips-dev
# libreoffice-writer: for converting word documents to PDF
# imagemagick-dev and imagemagick-pdf: for converting images to PDF
RUN apk add --update --no-cache libpq vips-dev libreoffice-writer imagemagick-dev imagemagick-pdf

# Install fonts suitable for rendering DOCX and ODT files to PDF
# https://wiki.alpinelinux.org/wiki/Fonts#Installation
RUN apk add font-terminus font-bitstream-100dpi font-bitstream-75dpi font-bitstream-type1 font-noto font-noto-extra font-arabic-misc font-misc-cyrillic font-mutt-misc font-screen-cyrillic font-winitzki-cyrillic font-cronyx-cyrillic font-noto-arabic font-noto-armenian font-noto-cherokee font-noto-devanagari font-noto-ethiopic font-noto-georgian font-noto-hebrew font-noto-lao font-noto-malayalam font-noto-tamil font-noto-thaana font-noto-thai

# Copy files generated in the builder image
COPY --from=builder /app /app
Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ gem "activerecord-session_store"
gem "azure-storage-blob"
gem "bootsnap", require: false
gem "business"
gem "combine_pdf"
gem "cssbundling-rails"
gem "devise"
gem "devise_invitable"
Expand All @@ -17,6 +18,7 @@ gem "email_address"
gem "faraday"
gem "indefinite_article"
gem "jsbundling-rails"
gem "libreconv"
gem "matrix"
gem "okcomputer"
gem "omniauth-azure-activedirectory-v2"
Expand All @@ -28,6 +30,7 @@ gem "propshaft"
gem "puma"
gem "pundit"
gem "rack-attack"
gem "rmagick"
gem "ruby-vips"
gem "sentry-rails"
gem "sentry-ruby"
Expand Down
11 changes: 11 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ GEM
xpath (~> 3.2)
cgi (0.4.1)
climate_control (1.2.0)
combine_pdf (1.0.26)
matrix
ruby-rc4 (>= 0.1.5)
concurrent-ruby (1.2.2)
connection_pool (2.4.1)
crack (0.4.5)
Expand Down Expand Up @@ -288,6 +291,7 @@ GEM
json (2.7.1)
jwt (2.7.1)
language_server-protocol (3.17.0.3)
libreconv (0.9.5)
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
Expand Down Expand Up @@ -362,6 +366,7 @@ GEM
racc
pdf-core (0.9.0)
pg (1.5.4)
pkg-config (1.5.6)
prawn (2.4.0)
pdf-core (~> 0.9.0)
ttfunk (~> 1.7)
Expand Down Expand Up @@ -444,6 +449,8 @@ GEM
retriable (3.1.2)
rexml (3.2.6)
rladr (1.2.0)
rmagick (5.3.0)
pkg-config (~> 1.4)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
Expand Down Expand Up @@ -500,6 +507,7 @@ GEM
rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22)
ruby-progressbar (1.13.0)
ruby-rc4 (0.1.5)
ruby-vips (2.2.0)
ffi (~> 1.12)
ruby2_keywords (0.0.5)
Expand Down Expand Up @@ -602,6 +610,7 @@ DEPENDENCIES
business
capybara
climate_control
combine_pdf
cssbundling-rails
cuprite
debug
Expand All @@ -621,6 +630,7 @@ DEPENDENCIES
govuk_markdown
indefinite_article
jsbundling-rails
libreconv
mail-notify
matrix
okcomputer
Expand All @@ -636,6 +646,7 @@ DEPENDENCIES
rack-attack
rails (= 7.1.2)
rladr
rmagick
rspec
rspec-rails
rubocop-govuk
Expand Down
38 changes: 30 additions & 8 deletions app/components/check_your_answers_summary/component.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module CheckYourAnswersSummary
class Component < ViewComponent::Base
def initialize(
Expand Down Expand Up @@ -63,11 +65,9 @@ def value_for(field)
end

def href_for(field)
return if (path = field[:href]).blank?

return path if path.is_a?(String)

Rails.application.routes.url_helpers.polymorphic_path(path)
if (path = field[:href]).present?
path.is_a?(String) ? path : url_helpers.polymorphic_path(path)
end
end

def row_for_field(field)
Expand Down Expand Up @@ -135,13 +135,21 @@ def format_document(document, field)
.order(:created_at)
.select { |upload| upload.attachment.present? }

malware_scan_active =
FeatureFlags::FeatureFlag.active?(:fetch_malware_scan_result)

[
format_array(uploads, field),
if malware_scan_active && scope.scan_result_suspect.exists?
"<em>One or more upload has been deleted by the virus scanner.</em>"
elsif request.path.starts_with?("/assessor") &&
convert_to_pdf_active && !uploads.all?(&:is_pdf?)
helpers.govuk_link_to(
"Download as PDF (opens in a new tab)",
url_helpers.assessor_interface_application_form_document_pdf_path(
document,
field[:translation] ? "translated" : "original",
),
target: :_blank,
rel: :noopener,
)
end,
].compact_blank.join("<br /><br />").html_safe
end
Expand All @@ -150,5 +158,19 @@ def format_document(document, field)
def format_array(list, field)
list.map { |v| format_value(v, field) }.join("<br />").html_safe
end

def url_helpers
@url_helpers ||= Rails.application.routes.url_helpers
end

def malware_scan_active
@malware_scan_active ||=
FeatureFlags::FeatureFlag.active?(:fetch_malware_scan_result)
end

def convert_to_pdf_active
@convert_to_pdf_active =
FeatureFlags::FeatureFlag.active?(:convert_documents_to_pdf)
end
end
end
3 changes: 2 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ class ApplicationController < ActionController::Base
include DfE::Analytics::Requests
include Pundit::Authorization

default_form_builder GOVUKDesignSystemFormBuilder::FormBuilder
append_view_path Rails.root.join("app/components")
default_form_builder GOVUKDesignSystemFormBuilder::FormBuilder
layout "two_thirds"

before_action :authenticate,
unless: -> { FeatureFlags::FeatureFlag.active?(:service_open) }
Expand Down
2 changes: 0 additions & 2 deletions app/controllers/assessor_interface/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,5 @@ class AssessorInterface::BaseController < ApplicationController
include AssessorCurrentNamespace
include StaffAuthenticatable

layout "two_thirds"

after_action :verify_authorized
end
40 changes: 40 additions & 0 deletions app/controllers/assessor_interface/documents_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

module AssessorInterface
class DocumentsController < BaseController
include ActiveStorage::Streaming
include StreamedResponseAuthenticatable
include RescueActiveStorageErrors
include UploadHelper

skip_before_action :authenticate_staff!
before_action { authenticate_or_redirect(:staff) }

def show_pdf
authorize %i[assessor_interface application_form]

unless all_uploads_downloadable?
render "shared/malware_scan"
return
end

translation = params[:scope] == "translated"

if (pdf_data = ConvertToPDF.call(document:, translation:))
send_data(pdf_data, type: "application/pdf", disposition: :inline)
else
error_internal_server_error
end
end

private

def document
@document ||= Document.find(params[:id])
end

def all_uploads_downloadable?
document.uploads.all? { |upload| upload_downloadable?(upload) }
end
end
end
4 changes: 2 additions & 2 deletions app/controllers/assessor_interface/uploads_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ class UploadsController < BaseController
include UploadHelper

skip_before_action :authenticate_staff!
before_action -> { authenticate_or_redirect(:staff) }
before_action { authenticate_or_redirect(:staff) }

def show
authorize %i[assessor_interface application_form]

if downloadable?(upload)
if upload_downloadable?(upload)
send_blob_stream(upload.attachment, disposition: :inline)
else
render "shared/malware_scan"
Expand Down
2 changes: 0 additions & 2 deletions app/controllers/eligibility_interface/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ module EligibilityInterface
class BaseController < ApplicationController
include EligibilityCurrentNamespace

layout "two_thirds"

before_action :load_region
after_action :save_eligibility_check_id

Expand Down
1 change: 0 additions & 1 deletion app/controllers/errors_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ class ErrorsController < ApplicationController
include EligibilityCurrentNamespace

skip_before_action :verify_authenticity_token
layout "two_thirds"

def forbidden
render "forbidden", formats: :html, status: :forbidden
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/performance_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ def index
stats.live_service_usage
@time_to_complete_data = stats.time_to_complete
@usage_by_country_count, @usage_by_country_data = stats.usage_by_country

render layout: "full_from_desktop"
end
end
2 changes: 0 additions & 2 deletions app/controllers/static_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,4 @@

class StaticController < ApplicationController
include EligibilityCurrentNamespace

layout "two_thirds"
end
2 changes: 0 additions & 2 deletions app/controllers/teacher_interface/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
class TeacherInterface::BaseController < ApplicationController
include TeacherCurrentNamespace

layout "two_thirds"

before_action :authenticate_teacher!

def load_application_form
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/teacher_interface/uploads_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class UploadsController < BaseController
before_action :load_upload, only: %i[delete destroy show]

def show
if downloadable?(@upload)
if upload_downloadable?(@upload)
send_blob_stream(@upload.attachment, disposition: :inline)
else
render "shared/malware_scan"
Expand Down
60 changes: 25 additions & 35 deletions app/helpers/upload_helper.rb
Original file line number Diff line number Diff line change
@@ -1,50 +1,40 @@
# frozen_string_literal: true

module UploadHelper
def upload_link_to(upload)
if downloadable?(upload)
href =
govuk_link_to(
"#{upload.name} (opens in a new tab)",
[interface, :application_form, upload.document, upload],
upload_path(upload),
target: :_blank,
rel: :noopener,
)
else
output =
govuk_link_to(
upload.name,
[interface, :application_form, upload.document, upload],
)
if show_scan_result_error?(upload)
output +=
tag.p(
"There’s a problem with this file",
class: "govuk-error-message",
)
end
output
end
end

def interface
if request.path.start_with?("/assessor/")
:assessor_interface
else
:teacher_interface
end
end
scan_result_problem =
malware_scanning_enabled? &&
(upload.scan_result_error? || upload.scan_result_suspect?)

def downloadable?(upload)
unless FeatureFlags::FeatureFlag.active?(:fetch_malware_scan_result)
return true
end
return href unless scan_result_problem

upload.scan_result_clean?
href +
tag.p("There’s a problem with this file", class: "govuk-error-message")
end

def show_scan_result_error?(upload)
unless FeatureFlags::FeatureFlag.active?(:fetch_malware_scan_result)
return false
end
def upload_path(upload)
interface =
if request.path.start_with?("/assessor/")
:assessor_interface
else
:teacher_interface
end
[interface, :application_form, upload.document, upload]
end

def upload_downloadable?(upload)
!malware_scanning_enabled? || upload.scan_result_clean?
end

upload.scan_result_error? || upload.scan_result_suspect?
def malware_scanning_enabled?
FeatureFlags::FeatureFlag.active?(:fetch_malware_scan_result)
end
end
Loading

0 comments on commit 4a50b6c

Please sign in to comment.