-
Notifications
You must be signed in to change notification settings - Fork 288
/
Copy pathuploader_helper.rb
391 lines (355 loc) · 18.3 KB
/
uploader_helper.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
module CamaleonCms
module UploaderHelper
include ActionView::Helpers::NumberHelper
include CamaleonCms::CamaleonHelper
# upload a file into server
# settings:
# folder: Directory where the file will be saved (default: "")
# sample: temporal => will save in /rails_path/public/temporal
# generate_thumb: true, # generate thumb image if this is image format (default true)
# maximum: maximum bytes permitted to upload (default: 1000MG)
# dimension: dimension for the image (sample: 30x30 | x30 | 30x | 300x300?)
# formats: extensions permitted, sample: jpg,png,... or generic: images | videos | audios | documents (default *)
# remove_source: Boolean (delete source file after saved if this is true, default false)
# same_name: Boolean (save the file with the same name if defined true, else search for a non used name)
# versions: (String) Create addtional multiple versions of the image uploaded, sample: '300x300,505x350' ==> Will create two extra images with these dimensions
# sample "test.png", versions: '200x200,450x450' will generate: thumb/test-png_200x200.png, test-png_450x450.png
# thumb_size: String (redefine the dimensions of the thumbnail, sample: '100x100' ==> only for images)
# temporal_time: if great than 0 seconds, then this file will expire (removed) in that time (default: 0)
# To manage jobs, please check https://edgeguides.rubyonrails.org/active_job_basics.html
# Note: if you are using temporal_time, you will need to copy the file to another directory later
# sample: upload_file(params[:my_file], {formats: "images", folder: "temporal"})
# sample: upload_file(params[:my_file], {formats: "jpg,png,gif,mp3,mp4", temporal_time: 10.minutes, maximum: 10.megabytes})
def upload_file(uploaded_io, settings = {})
cached_name = uploaded_io.is_a?(ActionDispatch::Http::UploadedFile) ? uploaded_io.original_filename : nil
return { error: 'File is empty', file: nil, size: nil } unless uploaded_io.present?
if uploaded_io.is_a?(String) && uploaded_io.match(%r{^https?://}).present? # download url file
tmp = cama_tmp_upload(uploaded_io)
return tmp if tmp[:error].present?
settings[:remove_source] = true
uploaded_io = tmp[:file_path]
end
uploaded_io = File.open(uploaded_io) if uploaded_io.is_a?(String)
if settings[:dimension].present?
uploaded_io = File.open(cama_resize_upload(uploaded_io.path, settings[:dimension]))
end
settings = settings.to_sym
settings[:uploaded_io] = uploaded_io
settings = {
folder: '',
maximum: current_site.get_option('filesystem_max_size', 100).to_f.megabytes,
formats: '*',
generate_thumb: true,
temporal_time: 0,
filename: begin
cached_name || uploaded_io.original_filename
rescue StandardError
uploaded_io.path.split('/').last
end.cama_fix_filename,
file_size: File.size(uploaded_io.to_io),
remove_source: false,
same_name: false,
versions: '',
thumb_size: nil
}.merge(settings)
hooks_run('before_upload', settings)
res = { error: nil }
# formats validations
return { error: "#{ct('file_format_error')} (#{settings[:formats]})" } unless cama_uploader.class.validate_file_format(
uploaded_io.path, settings[:formats]
)
# file size validations
if settings[:maximum] < settings[:file_size]
res[:error] =
"#{ct('file_size_exceeded', default: 'File size exceeded')} (#{number_to_human_size(settings[:maximum])})"
return res
end
# save file
key = File.join(settings[:folder], settings[:filename]).to_s.cama_fix_slash
res = cama_uploader.add_file(settings[:uploaded_io], key, { same_name: settings[:same_name] })
# generate image versions
if res['file_type'] == 'image'
settings[:versions].to_s.gsub(' ', '').split(',').each do |v|
version_path = cama_resize_upload(settings[:uploaded_io].path, v, { replace: false })
cama_uploader.add_file(version_path, cama_uploader.version_path(res['key'], v), is_thumb: true,
same_name: true)
FileUtils.rm_f(version_path)
end
end
# generate thumb
if settings[:generate_thumb] && res['thumb'].present?
cama_uploader_generate_thumbnail(uploaded_io.path, res['key'], settings[:thumb_size],
settings[:remove_source])
end
FileUtils.rm_f(uploaded_io.path) if settings[:remove_source] && File.exist?(uploaded_io.path)
hooks_run('after_upload', settings)
# temporal file upload (always put as local for temporal files)
if settings[:temporal_time] > 0
CamaleonCmsUploader.delete_block.call(settings, cama_uploader, key)
end
res
end
# generate thumbnail of a existent image
# key: key of the current file
# the thumbnail will be saved in my_images/my_img.png => my_images/thumb/my_img.png
def cama_uploader_generate_thumbnail(uploaded_io, key, thumb_size = nil, remove_source = false)
w = cama_uploader.thumb[:w]
h = cama_uploader.thumb[:h]
w, h = thumb_size.split('x') if thumb_size.present?
uploaded_io = File.open(uploaded_io) if uploaded_io.is_a?(String)
path_thumb = cama_resize_and_crop(uploaded_io.path, w, h)
thumb = cama_uploader.add_file(path_thumb, cama_uploader.version_path(key).sub('.svg', '.jpg'), is_thumb: true,
same_name: true)
FileUtils.rm_f(path_thumb) if remove_source
thumb
end
# helper to find an available filename for file_path in that directory
# sample: uploader_verify_name("/var/www/my_image.jpg")
# return "/var/www/my_image_1.jpg" => if "/var/www/my_image.jpg" exist
# return "/var/www/my_image.jpg" => if "/var/www/my_image.jpg" doesn't exist
def uploader_verify_name(file_path)
dir = File.dirname(file_path)
filename = File.basename(file_path).to_s.cama_fix_filename
files = Dir.entries(dir)
if files.include?(filename)
i = 1
_filename = filename
while files.include?(_filename)
_filename = "#{File.basename(filename, File.extname(filename))}_#{i}#{File.extname(filename)}"
i += 1
end
filename = _filename
end
"#{File.dirname(file_path)}/#{filename}"
end
# convert downloaded file path into public url
def cama_file_path_to_url(file_path)
file_path.sub(Rails.public_path.to_s, begin
root_url
rescue StandardError
cama_root_url
end)
end
# convert public url to file path
def cama_url_to_file_path(url)
File.join(Rails.public_path, URI(url.to_s).path)
end
# crop and image and saved as imagename_crop.ext
# file: file path
# w: new width
# h: new height
# w_offset: left offset
# w_offset: top offset
# resize: true/false
# (true => resize the image to this dimension)
# (false => crop the image with this dimension)
# replace: Boolean (replace current image or create another file)
def cama_crop_image(file_path, w = nil, h = nil, w_offset = 0, h_offset = 0, resize = false, replace = true)
force = ''
force = '!' if w.present? && h.present? && !w.include?('?') && !h.include?('?')
img = MiniMagick::Image.open(file_path)
w = img[:width].to_f > w.sub('?', '').to_i ? w.sub('?', '') : img[:width] if w.present? && w.to_s.include?('?')
h = img[:height].to_f > h.sub('?', '').to_i ? h.sub('?', '') : img[:height] if h.present? && h.to_s.include?('?')
data = { img: img, w: w, h: h, w_offset: w_offset, h_offset: h_offset, resize: resize, replace: replace }
hooks_run('before_crop_image', data)
data[:img].combine_options do |i|
i.resize("#{w if w.present?}x#{h if h.present?}#{force}") if data[:resize]
i.crop "#{w if w.present?}x#{h if h.present?}+#{w_offset}+#{h_offset}#{force}" unless data[:resize]
end
res = file_path
unless data[:replace]
ext = File.extname(file_path)
res = file_path.gsub(ext, "_crop#{ext}")
end
data[:img].write res
res
end
# resize and crop a file
# SVGs are converted to JPEGs for editing
# Params:
# file: (String) File path
# w: (Integer) width
# h: (Integer) height
# settings:
# gravity: (Sym, default :north_east) Crop position: :north_west, :north, :north_east, :east, :south_east, :south, :south_west, :west, :center
# overwrite: (Boolean, default true) true for overwrite current image with resized resolutions, false: create other file called with prefix "crop_"
# output_name: (String, default prefixd name with crop_), permit to define the output name of the thumbnail if overwrite = true
# Return: (String) file path where saved this cropped
# sample: cama_resize_and_crop(my_file, 200, 200, {gravity: :north_east, overwrite: false})
def cama_resize_and_crop(file, w, h, settings = {})
settings = { gravity: :north_east, overwrite: true, output_name: '' }.merge(settings)
img = MiniMagick::Image.open(file)
if file.end_with? '.svg'
img.format 'jpg'
file.sub! '.svg', '.jpg'
settings[:output_name]&.sub!('.svg', '.jpg')
end
w = img[:width].to_f > w.sub('?', '').to_i ? w.sub('?', '') : img[:width] if w.present? && w.to_s.include?('?')
h = img[:height].to_f > h.sub('?', '').to_i ? h.sub('?', '') : img[:height] if h.present? && h.to_s.include?('?')
w_original = img[:width].to_f
h_original = img[:height].to_f
w = w.to_i if w.present?
h = h.to_i if h.present?
# check proportions
if w_original * h < h_original * w
op_resize = "#{w.to_i}x"
w_result = w
h_result = (h_original * w / w_original)
else
op_resize = "x#{h.to_i}"
w_result = (w_original * h / h_original)
h_result = h
end
w_offset, h_offset = cama_crop_offsets_by_gravity(settings[:gravity], [w_result, h_result], [w, h])
data = { img: img, w: w, h: h, w_offset: w_offset, h_offset: h_offset, op_resize: op_resize, settings: settings }
hooks_run('before_resize_crop', data)
data[:img].combine_options do |i|
i.resize(data[:op_resize])
i.gravity(settings[:gravity])
i.crop "#{data[:w].to_i}x#{data[:h].to_i}+#{data[:w_offset]}+#{data[:h_offset]}!"
end
if settings[:overwrite]
data[:img].write(file.sub('.svg', '.jpg'))
elsif settings[:output_name].present?
data[:img].write(file = File.join(File.dirname(file), settings[:output_name]).to_s)
else
data[:img].write(file = uploader_verify_name(File.join(File.dirname(file),
"crop_#{File.basename(file.sub('.svg', '.jpg'))}")))
end
file
end
# upload tmp file
# support for url and local path
# sample:
# cama_tmp_upload('https://camaleon.website/media/132/logo2.png') ==> /var/rails/my_project/public/tmp/1/logo2.png
# cama_tmp_upload('/var/www/media/132/logo 2.png') ==> /var/rails/my_project/public/tmp/1/logo-2.png
# accept args:
# name: to indicate the name to use, sample: cama_tmp_upload('/var/www/media/132/logo 2.png', {name: 'owen.png', formats: 'images'})
# formats: extensions permitted, sample: jpg,png,... or generic: images | videos | audios | documents (default *)
# dimension: 20x30
# return: {file_path, error}
def cama_tmp_upload(uploaded_io, args = {})
tmp_path = args[:path] || File.join(Rails.public_path, 'tmp', current_site.id.to_s).to_s
FileUtils.mkdir_p(tmp_path) unless Dir.exist?(tmp_path)
saved = false
if uploaded_io.is_a?(String) && uploaded_io.start_with?('data:') # create tmp file using base64 format
_tmp_name = args[:name]
return { error: cama_t('camaleon_cms.admin.media.name_required').to_s } unless params[:name].present?
return { error: "#{ct('file_format_error')} (#{args[:formats]})" } unless cama_uploader.class.validate_file_format(
_tmp_name, args[:formats]
)
path = uploader_verify_name(File.join(tmp_path, _tmp_name))
File.open(path, 'wb') { |f| f.write(Base64.decode64(uploaded_io.split(';base64,').last)) }
uploaded_io = File.open(path)
saved = true
elsif uploaded_io.is_a?(String) && (uploaded_io.start_with?('http://') || uploaded_io.start_with?('https://'))
return { error: "#{ct('file_format_error')} (#{args[:formats]})" } unless cama_uploader.class.validate_file_format(
uploaded_io, args[:formats]
)
if uploaded_io.include?(current_site.the_url(locale: nil))
uploaded_io = File.join(Rails.public_path, uploaded_io.sub(current_site.the_url(locale: nil), '')).to_s
end
_tmp_name = uploaded_io.split('/').last.split('?').first
args[:name] = args[:name] || _tmp_name
uploaded_io = URI(uploaded_io).open
end
uploaded_io = File.open(uploaded_io) if uploaded_io.is_a?(String)
return { error: "#{ct('file_format_error')} (#{args[:formats]})" } unless cama_uploader.class.validate_file_format(
_tmp_name || uploaded_io.path, args[:formats]
)
if args[:maximum].present? && args[:maximum] < begin
uploaded_io.size
rescue StandardError
File.size(uploaded_io)
end
return { error: "#{ct('file_size_exceeded',
default: 'File size exceeded')} (#{number_to_human_size(args[:maximum])})" }
end
name = args[:name] || uploaded_io&.original_filename || uploaded_io.path.split('/').last
name = "#{File.basename(name, File.extname(name)).parameterize}#{File.extname(name)}"
path ||= uploader_verify_name(File.join(tmp_path, name))
File.open(path, 'wb') { |f| f.write(uploaded_io.read) } unless saved
path = cama_resize_upload(path, args[:dimension]) if args[:dimension].present?
{ file_path: path, error: nil }
end
# resize image if the format is correct
# return resized file path
def cama_resize_upload(image_path, dimesion, args = {})
if cama_uploader.class.validate_file_format(image_path, 'image') && dimesion.present?
r = { file: image_path, w: dimesion.split('x')[0], h: dimesion.split('x')[1], w_offset: 0, h_offset: 0,
resize: !dimesion.split('x')[2] || dimesion.split('x')[2] == 'resize', replace: true, gravity: :north_east }.merge(args)
hooks_run('on_uploader_resize', r)
image_path = if r[:w].present? && r[:h].present?
cama_resize_and_crop(r[:file], r[:w], r[:h], { overwrite: r[:replace], gravity: r[:gravity] })
else
cama_crop_image(r[:file], r[:w], r[:h], r[:w_offset], r[:h_offset], r[:resize], r[:replace])
end
end
image_path
end
# return the current uploader
def cama_uploader
@cama_uploader ||= lambda {
thumb = current_site.get_option('filesystem_thumb_size', '100x100').split('x')
args = {
server: current_site.get_option('filesystem_type', 'local').downcase,
thumb: { w: thumb[0], h: thumb[1] },
aws_settings: {
region: current_site.get_option('filesystem_region', 'us-west-2'),
access_key: current_site.get_option('filesystem_s3_access_key'),
secret_key: current_site.get_option('filesystem_s3_secret_key'),
bucket: current_site.get_option('filesystem_s3_bucket_name'),
cloud_front: current_site.get_option('filesystem_s3_cloudfront'),
aws_file_upload_settings: lambda { |settings|
settings
}, # permit to add your custom attributes for file_upload https://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Object.html#upload_file-instance_method
aws_file_read_settings: lambda { |data, _s3_file|
data
} # permit to read custom attributes from aws file and add to file parsed object
},
custom_uploader: nil # posibility to use custom file uploader
}
hooks_run('on_uploader', args)
return args[:custom_uploader] if args[:custom_uploader].present?
case args[:server]
when 's3', 'aws'
CamaleonCmsAwsUploader.new(
{ current_site: current_site, thumb: args[:thumb], aws_settings: args[:aws_settings] }, self
)
else
CamaleonCmsLocalUploader.new({ current_site: current_site, thumb: args[:thumb] }, self)
end
}.call
end
def slugify(val)
val.to_s.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
end
def slugify_folder(val)
splitted_folder = val.split('/')
splitted_folder[-1] = slugify(splitted_folder.last)
splitted_folder.join('/')
end
private
# helper for resize and crop method
def cama_crop_offsets_by_gravity(gravity, original_dimensions, cropped_dimensions)
original_width, original_height = original_dimensions
cropped_width, cropped_height = cropped_dimensions
vertical_offset = case gravity
when :north_west, :north, :north_east then 0
when :center, :east, :west then [((original_height - cropped_height) / 2.0).to_i, 0].max
when :south_west, :south, :south_east then (original_height - cropped_height).to_i
end
horizontal_offset = case gravity
when :north_west, :west, :south_west then 0
when :center, :north, :south then [((original_width - cropped_width) / 2.0).to_i, 0].max
when :north_east, :east, :south_east then (original_width - cropped_width).to_i
end
[horizontal_offset, vertical_offset]
end
# convert file path into thumb path format
# return the image name into thumb format: owewen.png into thumb/owen-png.png
def cama_parse_for_thumb_name(file_path)
"#{@fog_connection_hook_res[:thumb_folder_name]}/#{File.basename(file_path).parameterize}#{File.extname(file_path)}"
end
end
end