2022-05-11 08:57:25 +02:00
# Display a widget for uploading, editing and deleting a file attachment
class Attachment :: EditComponent < ApplicationComponent
2022-11-09 12:33:20 +01:00
attr_reader :champ
attr_reader :attachment
2022-11-21 18:32:17 +01:00
attr_reader :user_can_destroy
alias user_can_destroy? user_can_destroy
2022-12-13 15:22:18 +01:00
attr_reader :user_can_replace
alias user_can_replace? user_can_replace
2022-11-09 12:33:20 +01:00
attr_reader :as_multiple
alias as_multiple? as_multiple
2022-10-25 14:14:24 +02:00
2022-11-09 09:48:36 +01:00
EXTENSIONS_ORDER = [ 'jpeg' , 'png' , 'pdf' , 'zip' ] . freeze
2022-12-13 15:22:18 +01:00
def initialize ( champ : nil , auto_attach_url : nil , attached_file : , direct_upload : true , index : 0 , as_multiple : false , view_as : :link , user_can_destroy : true , user_can_replace : false , ** kwargs )
2022-11-17 19:18:58 +01:00
@as_multiple = as_multiple
2022-05-11 08:57:25 +02:00
@attached_file = attached_file
2022-11-17 19:18:58 +01:00
@auto_attach_url = auto_attach_url
@champ = champ
@direct_upload = direct_upload
@index = index
2022-12-08 00:15:05 +01:00
@view_as = view_as
2022-11-21 18:32:17 +01:00
@user_can_destroy = user_can_destroy
2022-12-13 15:22:18 +01:00
@user_can_replace = user_can_replace
2022-10-25 14:14:24 +02:00
2022-11-09 12:33:20 +01:00
# attachment passed by kwarg because we don't want a default (nil) value.
2022-10-29 16:08:59 +02:00
@attachment = if kwargs . key? ( :attachment )
2022-11-09 12:33:20 +01:00
kwargs . delete ( :attachment )
2022-10-25 14:14:24 +02:00
elsif attached_file . respond_to? ( :attachment )
attached_file . attachment
else
2022-10-29 16:08:59 +02:00
fail ArgumentError , " You must pass an `attachment` kwarg when not using as single attachment like in #{ attached_file . name } . Set it to nil for a new attachment. "
2022-10-25 14:14:24 +02:00
end
2022-11-28 11:38:28 +01:00
# When parent form has nested attributes, pass the form builder object_name
# to correctly infer the input attribute name.
@form_object_name = kwargs . delete ( :form_object_name )
2022-11-17 19:18:58 +01:00
verify_initialization! ( kwargs )
2022-05-11 08:57:25 +02:00
end
2022-11-09 12:33:20 +01:00
def first?
@index . zero?
2022-05-11 08:57:25 +02:00
end
2022-11-09 12:33:20 +01:00
def max_file_size
return if file_size_validator . nil?
file_size_validator . options [ :less_than ]
2022-05-11 08:57:25 +02:00
end
def attachment_id
2022-10-25 14:14:24 +02:00
@attachment_id || = ( attachment & . id || SecureRandom . uuid )
2022-05-11 08:57:25 +02:00
end
2022-11-09 12:33:20 +01:00
def attachment_path ( ** args )
helpers . attachment_path attachment . id , args . merge ( signed_id : attachment . blob . signed_id )
2022-05-11 08:57:25 +02:00
end
2022-11-09 12:33:20 +01:00
def destroy_attachment_path
attachment_path ( champ_id : champ & . id )
end
def attachment_input_class
" attachment-input- #{ attachment_id } "
2022-05-11 08:57:25 +02:00
end
def file_field_options
2022-07-05 16:31:53 +02:00
track_issue_with_missing_validators if missing_validators?
2022-05-11 08:57:25 +02:00
{
2022-12-13 15:22:18 +01:00
class : class_names ( " fr-upload attachment-input " : true , " #{ attachment_input_class } " : true , " hidden " : persisted? , " fr-mt-2w " : user_can_replace? ) ,
2022-05-11 08:57:25 +02:00
direct_upload : @direct_upload ,
2022-11-21 16:06:32 +01:00
id : input_id ,
2022-05-11 08:57:25 +02:00
aria : { describedby : champ & . describedby_id } ,
2022-06-16 15:51:45 +02:00
data : {
2023-01-13 11:13:09 +01:00
auto_attach_url : ,
2023-06-01 15:58:55 +02:00
turbo_force : :server
2022-10-25 14:14:24 +02:00
} . merge ( has_file_size_validator? ? { max_file_size : } : { } )
2022-12-13 15:22:18 +01:00
. merge ( user_can_replace? ? { replace_attachment_target : " input " } : { } )
2022-09-09 15:54:13 +02:00
} . merge ( has_content_type_validator? ? { accept : accept_content_type } : { } )
2022-05-11 08:57:25 +02:00
end
2022-11-09 12:33:20 +01:00
def poll_url
2022-10-25 14:14:24 +02:00
if champ . present?
2022-11-09 12:33:20 +01:00
auto_attach_url
else
2023-09-20 16:52:30 +02:00
attachment_path ( user_can_edit : true , view_as : @view_as , auto_attach_url : @auto_attach_url , direct_upload : @direct_upload )
2022-10-25 14:14:24 +02:00
end
2022-06-16 15:51:45 +02:00
end
2022-12-07 18:36:03 +01:00
def poll_context
return :dossier if champ . present?
nil
end
2023-03-13 09:09:20 +01:00
def field_name ( object_name = nil , method_name = nil , * method_names , multiple : false , index : nil )
2022-11-28 11:38:28 +01:00
helpers . field_name ( @form_object_name || ActiveModel :: Naming . param_key ( @attached_file . record ) , attribute_name )
2022-11-17 19:18:58 +01:00
end
def attribute_name
2022-05-11 08:57:25 +02:00
@attached_file . name
end
def remove_button_options
{
role : 'button' ,
2022-11-09 12:33:20 +01:00
data : { turbo : " true " , turbo_method : :delete }
2022-05-11 08:57:25 +02:00
}
end
2022-12-13 15:22:18 +01:00
def replace_button_options
{
type : 'button' ,
data : {
2022-12-13 16:13:31 +01:00
action : " click->replace-attachment # open " ,
auto_attach_url : auto_attach_url
} . compact
2022-12-13 15:22:18 +01:00
}
end
def replace_controller_attributes
2022-12-13 16:13:31 +01:00
return { } if ! persisted? || ! user_can_replace? || as_multiple?
2022-12-13 15:22:18 +01:00
{
" data-controller " : 'replace-attachment'
}
end
2022-05-11 08:57:25 +02:00
def retry_button_options
{
type : 'button' ,
2022-11-24 14:05:53 +01:00
class : 'fr-btn fr-btn--sm fr-btn--tertiary fr-mt-1w attachment-upload-error-retry' ,
2022-05-11 08:57:25 +02:00
data : { input_target : " . #{ attachment_input_class } " , action : 'autosave#onClickRetryButton' }
}
end
2022-11-09 12:33:20 +01:00
def persisted?
! ! attachment & . persisted?
end
2022-11-21 18:32:17 +01:00
def downloadable?
2022-12-08 00:15:05 +01:00
return false unless @view_as == :download
2022-12-07 23:04:50 +01:00
viewable?
end
def viewable?
2022-11-21 18:32:17 +01:00
return false if attachment . virus_scanner_error?
return false if attachment . watermark_pending?
true
end
2022-11-09 12:33:20 +01:00
def error?
attachment . virus_scanner_error?
end
def error_message
case
when attachment . virus_scanner . infected?
2022-11-22 19:25:45 +01:00
t ( " .errors.virus_infected " )
2022-11-09 12:33:20 +01:00
when attachment . virus_scanner . corrupt?
2022-11-22 19:25:45 +01:00
t ( " .errors.corrupted_file " )
2022-11-09 12:33:20 +01:00
end
end
private
2022-11-21 16:06:32 +01:00
def input_id
2022-11-09 12:33:20 +01:00
if champ . present?
2022-11-24 13:45:33 +01:00
# There is always a single input by champ, its id must match the label "for" attribute.
return champ . input_id
2022-11-09 12:33:20 +01:00
end
2022-11-28 11:38:28 +01:00
helpers . field_id ( @form_object_name || @attached_file . record , attribute_name )
2022-11-09 12:33:20 +01:00
end
def auto_attach_url
return @auto_attach_url if @auto_attach_url . present?
2022-12-13 15:22:18 +01:00
params = { replace_attachment_id : @attachment . id } if user_can_replace? && @attachment . present?
return helpers . auto_attach_url ( @champ , params ) if @champ . present?
2022-11-09 12:33:20 +01:00
2022-11-17 19:18:58 +01:00
nil
2022-11-09 12:33:20 +01:00
end
2022-06-16 15:51:45 +02:00
def file_size_validator
@attached_file . record
2022-11-17 19:18:58 +01:00
. _validators [ attribute_name . to_sym ]
2022-06-16 15:51:45 +02:00
. find { | validator | validator . class == ActiveStorageValidations :: SizeValidator }
end
def content_type_validator
@attached_file . record
2022-11-17 19:18:58 +01:00
. _validators [ attribute_name . to_sym ]
2022-06-16 15:51:45 +02:00
. find { | validator | validator . class == ActiveStorageValidations :: ContentTypeValidator }
end
2022-07-05 16:31:53 +02:00
2022-09-09 15:54:13 +02:00
def accept_content_type
2022-11-09 09:50:05 +01:00
list = content_type_validator . options [ :in ] . dup
list << " .acidcsa " if list . include? ( " application/octet-stream " )
2022-09-09 15:54:13 +02:00
list . join ( ', ' )
end
2022-11-09 09:48:36 +01:00
def allowed_formats
@allowed_formats || = begin
2023-09-20 17:35:17 +02:00
formats = content_type_validator . options [ :in ] . filter_map do | content_type |
2022-11-09 09:48:36 +01:00
MiniMime . lookup_by_content_type ( content_type ) & . extension
end . uniq . sort_by { EXTENSIONS_ORDER . index ( _1 ) || 999 }
2023-09-20 17:35:17 +02:00
# When too many formats are allowed, consider instead manually indicating
# above the input a more comprehensive of formats allowed, like "any image", or a simplified list.
formats . size > 5 ? [ ] : formats
2022-11-09 09:48:36 +01:00
end
end
2022-07-05 16:31:53 +02:00
def has_content_type_validator?
! content_type_validator . nil?
end
def has_file_size_validator?
! file_size_validator . nil?
end
def missing_validators?
return true if ! has_file_size_validator?
return true if ! has_content_type_validator?
return false
end
2022-11-17 19:18:58 +01:00
def verify_initialization! ( kwargs )
fail ArgumentError , " Unknown kwarg #{ kwargs . keys . join ( ', ' ) } " unless kwargs . empty?
2022-12-08 00:15:05 +01:00
fail ArgumentError , " Invalid view_as: #{ @view_as } , must be :download or :link " if [ :download , :link ] . exclude? ( @view_as )
2022-11-17 19:18:58 +01:00
end
2022-07-05 16:31:53 +02:00
def track_issue_with_missing_validators
Sentry . capture_message (
" Strange case of missing validator " ,
extra : {
champ : champ ,
2022-11-17 19:18:58 +01:00
field_name : field_name ,
2022-07-05 16:31:53 +02:00
attachment_id : attachment_id
}
)
end
2022-05-11 08:57:25 +02:00
end