cloudinary 1.20.0 → 1.23.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +15 -14
- data/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
- data/.github/pull_request_template.md +18 -11
- data/.travis.yml +12 -2
- data/CHANGELOG.md +68 -0
- data/README.md +87 -237
- data/cloudinary.gemspec +10 -1
- data/lib/cloudinary/account_api.rb +9 -13
- data/lib/cloudinary/api.rb +774 -124
- data/lib/cloudinary/auth_token.rb +7 -2
- data/lib/cloudinary/base_api.rb +24 -5
- data/lib/cloudinary/carrier_wave/process.rb +9 -1
- data/lib/cloudinary/search.rb +40 -7
- data/lib/cloudinary/uploader.rb +62 -25
- data/lib/cloudinary/utils.rb +155 -20
- data/lib/cloudinary/version.rb +1 -1
- data/lib/cloudinary.rb +21 -1
- data/vendor/assets/javascripts/cloudinary/jquery.cloudinary.js +20 -3
- metadata +4 -3
@@ -21,7 +21,12 @@ module Cloudinary
|
|
21
21
|
start = options[:start_time]
|
22
22
|
expiration = options[:expiration]
|
23
23
|
ip = options[:ip]
|
24
|
+
|
24
25
|
acl = options[:acl]
|
26
|
+
if acl.present?
|
27
|
+
acl = acl.is_a?(String) ? [acl] : acl
|
28
|
+
end
|
29
|
+
|
25
30
|
duration = options[:duration]
|
26
31
|
url = options[:url]
|
27
32
|
start = Time.new.getgm.to_i if start == 'now'
|
@@ -41,9 +46,9 @@ module Cloudinary
|
|
41
46
|
token << "ip=#{ip}" if ip
|
42
47
|
token << "st=#{start}" if start
|
43
48
|
token << "exp=#{expiration}"
|
44
|
-
token << "acl=#{escape_to_lower(acl)}" if acl
|
49
|
+
token << "acl=#{escape_to_lower(acl.join('!'))}" if acl && acl.size > 0
|
45
50
|
to_sign = token.clone
|
46
|
-
to_sign << "url=#{escape_to_lower(url)}" if url
|
51
|
+
to_sign << "url=#{escape_to_lower(url)}" if url && (acl.blank? || acl.size == 0)
|
47
52
|
auth = digest(to_sign.join(SEPARATOR), key)
|
48
53
|
token << "hmac=#{auth}"
|
49
54
|
"#{name}=#{token.join(SEPARATOR)}"
|
data/lib/cloudinary/base_api.rb
CHANGED
@@ -19,9 +19,12 @@ module Cloudinary::BaseApi
|
|
19
19
|
# This sets the instantiated self as the response Hash
|
20
20
|
update Cloudinary::Api.parse_json_response response
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
# According to RFC 2616, header names are case-insensitive.
|
23
|
+
lc_headers = response.headers.transform_keys(&:downcase)
|
24
|
+
|
25
|
+
@rate_limit_allowed = lc_headers[:x_featureratelimit_limit].to_i if lc_headers[:x_featureratelimit_limit]
|
26
|
+
@rate_limit_reset_at = Time.parse(lc_headers[:x_featureratelimit_reset]) if lc_headers[:x_featureratelimit_reset]
|
27
|
+
@rate_limit_remaining = lc_headers[:x_featureratelimit_remaining].to_i if lc_headers[:x_featureratelimit_remaining]
|
25
28
|
end
|
26
29
|
end
|
27
30
|
end
|
@@ -59,13 +62,14 @@ module Cloudinary::BaseApi
|
|
59
62
|
|
60
63
|
private
|
61
64
|
|
62
|
-
def call_cloudinary_api(method, uri,
|
65
|
+
def call_cloudinary_api(method, uri, auth, params, options, &api_url_builder)
|
63
66
|
cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || 'https://api.cloudinary.com'
|
64
67
|
api_url = Cloudinary::Utils.smart_escape(api_url_builder.call(cloudinary, uri).flatten.join('/'))
|
65
68
|
timeout = options[:timeout] || Cloudinary.config.timeout || 60
|
66
69
|
proxy = options[:api_proxy] || Cloudinary.config.api_proxy
|
67
70
|
|
68
71
|
headers = { "User-Agent" => Cloudinary::USER_AGENT }
|
72
|
+
headers.merge!("Authorization" => get_authorization_header_value(auth))
|
69
73
|
|
70
74
|
if options[:content_type] == :json
|
71
75
|
payload = params.to_json
|
@@ -74,6 +78,21 @@ module Cloudinary::BaseApi
|
|
74
78
|
payload = params.reject { |_, v| v.nil? || v == "" }
|
75
79
|
end
|
76
80
|
|
77
|
-
call_json_api(method, api_url, payload, timeout, headers, proxy
|
81
|
+
call_json_api(method, api_url, payload, timeout, headers, proxy)
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_authorization_header_value(auth)
|
85
|
+
if auth[:oauth_token].present?
|
86
|
+
"Bearer #{auth[:oauth_token]}"
|
87
|
+
else
|
88
|
+
"Basic #{Base64.urlsafe_encode64("#{auth[:key]}:#{auth[:secret]}")}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def validate_authorization(api_key, api_secret, oauth_token)
|
93
|
+
return if oauth_token.present?
|
94
|
+
|
95
|
+
raise("Must supply api_key") if api_key.nil?
|
96
|
+
raise("Must supply api_secret") if api_secret.nil?
|
78
97
|
end
|
79
98
|
end
|
@@ -154,8 +154,16 @@ module Cloudinary::CarrierWave
|
|
154
154
|
format = Cloudinary::PreloadedFile.split_format(original_filename || "").last
|
155
155
|
return format || "" if resource_type == "raw"
|
156
156
|
format = requested_format || format || default_format
|
157
|
-
|
157
|
+
|
158
158
|
format = format.to_s.downcase
|
159
159
|
Cloudinary::FORMAT_ALIASES[format] || format
|
160
160
|
end
|
161
|
+
|
162
|
+
def store!(new_file=nil)
|
163
|
+
super
|
164
|
+
|
165
|
+
column = model.send(:_mounter, mounted_as).send(:serialization_column)
|
166
|
+
identifier = model.send(:attribute, column)
|
167
|
+
retrieve_from_store!(identifier) unless identifier.nil?
|
168
|
+
end
|
161
169
|
end
|
data/lib/cloudinary/search.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
class Cloudinary::Search
|
2
|
+
SORT_BY = :sort_by
|
3
|
+
AGGREGATE = :aggregate
|
4
|
+
WITH_FIELD = :with_field
|
5
|
+
KEYS_WITH_UNIQUE_VALUES = [SORT_BY, AGGREGATE, WITH_FIELD].freeze
|
6
|
+
|
2
7
|
def initialize
|
3
8
|
@query_hash = {
|
4
|
-
|
5
|
-
|
6
|
-
|
9
|
+
SORT_BY => {},
|
10
|
+
AGGREGATE => {},
|
11
|
+
WITH_FIELD => {}
|
7
12
|
}
|
8
13
|
end
|
9
14
|
|
@@ -28,23 +33,51 @@ class Cloudinary::Search
|
|
28
33
|
self
|
29
34
|
end
|
30
35
|
|
36
|
+
# Sets the `sort_by` field.
|
37
|
+
#
|
38
|
+
# @param [String] field_name The field to sort by. You can specify more than one sort_by parameter;
|
39
|
+
# results will be sorted according to the order of the fields provided.
|
40
|
+
# @param [String] dir Sort direction. Valid sort directions are 'asc' or 'desc'. Default: 'desc'.
|
41
|
+
#
|
42
|
+
# @return [Cloudinary::Search]
|
31
43
|
def sort_by(field_name, dir = 'desc')
|
32
|
-
@query_hash[
|
44
|
+
@query_hash[SORT_BY][field_name] = { field_name => dir }
|
33
45
|
self
|
34
46
|
end
|
35
47
|
|
48
|
+
# The name of a field (attribute) for which an aggregation count should be calculated and returned in the response.
|
49
|
+
#
|
50
|
+
# You can specify more than one aggregate parameter.
|
51
|
+
#
|
52
|
+
# @param [String] value Supported values: resource_type, type, pixels (only the image assets in the response are
|
53
|
+
# aggregated), duration (only the video assets in the response are aggregated), format, and
|
54
|
+
# bytes. For aggregation fields without discrete values, the results are divided into
|
55
|
+
# categories.
|
56
|
+
# @return [Cloudinary::Search]
|
36
57
|
def aggregate(value)
|
37
|
-
@query_hash[
|
58
|
+
@query_hash[AGGREGATE][value] = value
|
38
59
|
self
|
39
60
|
end
|
40
61
|
|
62
|
+
# The name of an additional asset attribute to include for each asset in the response.
|
63
|
+
#
|
64
|
+
# @param [String] value Possible value: context, tags, and for Tier 2 also image_metadata, and image_analysis.
|
65
|
+
#
|
66
|
+
# @return [Cloudinary::Search]
|
41
67
|
def with_field(value)
|
42
|
-
@query_hash[
|
68
|
+
@query_hash[WITH_FIELD][value] = value
|
43
69
|
self
|
44
70
|
end
|
45
71
|
|
72
|
+
# Returns the query as an hash.
|
73
|
+
#
|
74
|
+
# @return [Hash]
|
46
75
|
def to_h
|
47
|
-
@query_hash.
|
76
|
+
@query_hash.each_with_object({}) do |(key, value), query|
|
77
|
+
next if value.nil? || ((value.is_a?(Array) || value.is_a?(Hash)) && value.blank?)
|
78
|
+
|
79
|
+
query[key] = KEYS_WITH_UNIQUE_VALUES.include?(key) ? value.values : value
|
80
|
+
end
|
48
81
|
end
|
49
82
|
|
50
83
|
def execute(options = {})
|
data/lib/cloudinary/uploader.rb
CHANGED
@@ -21,6 +21,7 @@ class Cloudinary::Uploader
|
|
21
21
|
:access_control => Cloudinary::Utils.json_array_param(options[:access_control]),
|
22
22
|
:access_mode => options[:access_mode],
|
23
23
|
:allowed_formats => Cloudinary::Utils.build_array(options[:allowed_formats]).join(","),
|
24
|
+
:asset_folder => options[:asset_folder],
|
24
25
|
:async => Cloudinary::Utils.as_safe_bool(options[:async]),
|
25
26
|
:auto_tagging => options[:auto_tagging] && options[:auto_tagging].to_f,
|
26
27
|
:background_removal => options[:background_removal],
|
@@ -33,6 +34,7 @@ class Cloudinary::Uploader
|
|
33
34
|
:custom_coordinates => Cloudinary::Utils.encode_double_array(options[:custom_coordinates]),
|
34
35
|
:detection => options[:detection],
|
35
36
|
:discard_original_filename => Cloudinary::Utils.as_safe_bool(options[:discard_original_filename]),
|
37
|
+
:display_name => options[:display_name],
|
36
38
|
:eager => Cloudinary::Utils.build_eager(options[:eager]),
|
37
39
|
:eager_async => Cloudinary::Utils.as_safe_bool(options[:eager_async]),
|
38
40
|
:eager_notification_url => options[:eager_notification_url],
|
@@ -53,6 +55,7 @@ class Cloudinary::Uploader
|
|
53
55
|
:phash => Cloudinary::Utils.as_safe_bool(options[:phash]),
|
54
56
|
:proxy => options[:proxy],
|
55
57
|
:public_id => options[:public_id],
|
58
|
+
:public_id_prefix => options[:public_id_prefix],
|
56
59
|
:quality_analysis => Cloudinary::Utils.as_safe_bool(options[:quality_analysis]),
|
57
60
|
:quality_override => options[:quality_override],
|
58
61
|
:raw_convert => options[:raw_convert],
|
@@ -66,6 +69,7 @@ class Cloudinary::Uploader
|
|
66
69
|
:unique_filename => Cloudinary::Utils.as_safe_bool(options[:unique_filename]),
|
67
70
|
:upload_preset => options[:upload_preset],
|
68
71
|
:use_filename => Cloudinary::Utils.as_safe_bool(options[:use_filename]),
|
72
|
+
:use_filename_as_display_name => Cloudinary::Utils.as_safe_bool(options[:use_filename_as_display_name]),
|
69
73
|
:accessibility_analysis => Cloudinary::Utils.as_safe_bool(options[:accessibility_analysis]),
|
70
74
|
:metadata => Cloudinary::Utils.encode_context(options[:metadata])
|
71
75
|
}
|
@@ -110,6 +114,9 @@ class Cloudinary::Uploader
|
|
110
114
|
else
|
111
115
|
filename = "cloudinaryfile"
|
112
116
|
end
|
117
|
+
|
118
|
+
filename = options[:filename] if options[:filename]
|
119
|
+
|
113
120
|
unique_upload_id = Cloudinary::Utils.random_public_id
|
114
121
|
upload = nil
|
115
122
|
index = 0
|
@@ -159,7 +166,9 @@ class Cloudinary::Uploader
|
|
159
166
|
:from_public_id => from_public_id,
|
160
167
|
:to_public_id => to_public_id,
|
161
168
|
:to_type => options[:to_type],
|
162
|
-
:invalidate => Cloudinary::Utils.as_safe_bool(options[:invalidate])
|
169
|
+
:invalidate => Cloudinary::Utils.as_safe_bool(options[:invalidate]),
|
170
|
+
:context => options[:context],
|
171
|
+
:metadata => options[:metadata]
|
163
172
|
}
|
164
173
|
end
|
165
174
|
end
|
@@ -207,17 +216,42 @@ class Cloudinary::Uploader
|
|
207
216
|
end
|
208
217
|
end
|
209
218
|
|
210
|
-
|
219
|
+
SLIDESHOW_PARAMS = [:notification_url, :public_id, :upload_preset]
|
220
|
+
|
221
|
+
# Creates auto-generated video slideshow.
|
222
|
+
#
|
223
|
+
# @param [Hash] options Additional options.
|
224
|
+
#
|
225
|
+
# @return [Hash] Hash with meta information URLs of generated slideshow resources.
|
226
|
+
def self.create_slideshow(options = {})
|
227
|
+
options[:resource_type] ||= :video
|
228
|
+
|
229
|
+
call_api("create_slideshow", options) do
|
230
|
+
params = {
|
231
|
+
:timestamp => Time.now.to_i,
|
232
|
+
:transformation => Cloudinary::Utils.build_eager(options[:transformation]),
|
233
|
+
:manifest_transformation => Cloudinary::Utils.build_eager(options[:manifest_transformation]),
|
234
|
+
:manifest_json => options[:manifest_json] && options[:manifest_json].to_json,
|
235
|
+
:tags => options[:tags] && Cloudinary::Utils.build_array(options[:tags]).join(","),
|
236
|
+
:overwrite => Cloudinary::Utils.as_safe_bool(options[:overwrite])
|
237
|
+
}
|
238
|
+
SLIDESHOW_PARAMS.each { |k| params[k] = options[k] unless options[k].nil? }
|
239
|
+
|
240
|
+
params
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Generates sprites by merging multiple images into a single large image.
|
245
|
+
#
|
246
|
+
# @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
|
247
|
+
# @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
|
248
|
+
#
|
249
|
+
# @return [Hash] Hash with meta information URLs of generated sprite resources
|
250
|
+
def self.generate_sprite(tag, options = {})
|
211
251
|
version_store = options.delete(:version_store)
|
212
252
|
|
213
253
|
result = call_api("sprite", options) do
|
214
|
-
|
215
|
-
:timestamp => (options[:timestamp] || Time.now.to_i),
|
216
|
-
:tag => tag,
|
217
|
-
:async => options[:async],
|
218
|
-
:notification_url => options[:notification_url],
|
219
|
-
:transformation => Cloudinary::Utils.generate_transformation_string(options.merge(:fetch_format => options[:format]))
|
220
|
-
}
|
254
|
+
Cloudinary::Utils.build_multi_and_sprite_params(tag, options)
|
221
255
|
end
|
222
256
|
|
223
257
|
if version_store == :file && result && result["version"]
|
@@ -229,16 +263,15 @@ class Cloudinary::Uploader
|
|
229
263
|
return result
|
230
264
|
end
|
231
265
|
|
232
|
-
|
266
|
+
# Creates either a single animated image, video or a PDF.
|
267
|
+
#
|
268
|
+
# @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
|
269
|
+
# @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
|
270
|
+
#
|
271
|
+
# @return [Hash] Hash with meta information URLs of the generated file
|
272
|
+
def self.multi(tag, options = {})
|
233
273
|
call_api("multi", options) do
|
234
|
-
|
235
|
-
:timestamp => (options[:timestamp] || Time.now.to_i),
|
236
|
-
:tag => tag,
|
237
|
-
:format => options[:format],
|
238
|
-
:async => options[:async],
|
239
|
-
:notification_url => options[:notification_url],
|
240
|
-
:transformation => Cloudinary::Utils.generate_transformation_string(options.clone)
|
241
|
-
}
|
274
|
+
Cloudinary::Utils.build_multi_and_sprite_params(tag, options)
|
242
275
|
end
|
243
276
|
end
|
244
277
|
|
@@ -337,11 +370,19 @@ class Cloudinary::Uploader
|
|
337
370
|
options = options.clone
|
338
371
|
return_error = options.delete(:return_error)
|
339
372
|
use_cache = options[:use_cache] || Cloudinary.config.use_cache
|
340
|
-
|
341
373
|
params, non_signable = yield
|
342
374
|
non_signable ||= []
|
343
375
|
|
344
|
-
|
376
|
+
headers = { "User-Agent" => Cloudinary::USER_AGENT }
|
377
|
+
headers['Content-Range'] = options[:content_range] if options[:content_range]
|
378
|
+
headers['X-Unique-Upload-Id'] = options[:unique_upload_id] if options[:unique_upload_id]
|
379
|
+
headers.merge!(options[:extra_headers]) if options[:extra_headers]
|
380
|
+
|
381
|
+
oauth_token = options[:oauth_token] || Cloudinary.config.oauth_token
|
382
|
+
|
383
|
+
if oauth_token
|
384
|
+
headers["Authorization"] = "Bearer #{oauth_token}"
|
385
|
+
elsif !options[:unsigned]
|
345
386
|
api_key = options[:api_key] || Cloudinary.config.api_key || raise(CloudinaryException, "Must supply api_key")
|
346
387
|
api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise(CloudinaryException, "Must supply api_secret")
|
347
388
|
signature_algorithm = options[:signature_algorithm]
|
@@ -353,11 +394,7 @@ class Cloudinary::Uploader
|
|
353
394
|
|
354
395
|
result = nil
|
355
396
|
|
356
|
-
api_url
|
357
|
-
headers = { "User-Agent" => Cloudinary::USER_AGENT }
|
358
|
-
headers['Content-Range'] = options[:content_range] if options[:content_range]
|
359
|
-
headers['X-Unique-Upload-Id'] = options[:unique_upload_id] if options[:unique_upload_id]
|
360
|
-
headers.merge!(options[:extra_headers]) if options[:extra_headers]
|
397
|
+
api_url = Cloudinary::Utils.cloudinary_api_url(action, options)
|
361
398
|
RestClient::Request.execute(:method => :post, :url => api_url, :payload => params.reject { |k, v| v.nil? || v=="" }, :timeout => timeout, :headers => headers, :proxy => proxy) do
|
362
399
|
|response, request, tmpresult|
|
363
400
|
raise CloudinaryException, "Server returned unexpected status code - #{response.code} - #{response.body}" unless [200, 400, 401, 403, 404, 500].include?(response.code)
|
data/lib/cloudinary/utils.rb
CHANGED
@@ -13,6 +13,7 @@ require 'cloudinary/responsive'
|
|
13
13
|
class Cloudinary::Utils
|
14
14
|
# @deprecated Use Cloudinary::SHARED_CDN
|
15
15
|
SHARED_CDN = Cloudinary::SHARED_CDN
|
16
|
+
MODE_DOWNLOAD = "download"
|
16
17
|
DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION = {:width => :auto, :crop => :limit}
|
17
18
|
CONDITIONAL_OPERATORS = {
|
18
19
|
"=" => 'eq',
|
@@ -334,7 +335,7 @@ class Cloudinary::Utils
|
|
334
335
|
"if_" + normalize_expression(if_value) unless if_value.to_s.empty?
|
335
336
|
end
|
336
337
|
|
337
|
-
EXP_REGEXP = Regexp.new('(\$_*[^_ ]+)|(
|
338
|
+
EXP_REGEXP = Regexp.new('(\$_*[^_ ]+)|(?<![\$:])('+PREDEFINED_VARS.keys.join("|")+')'+'|('+CONDITIONAL_OPERATORS.keys.reverse.map { |k| Regexp.escape(k) }.join('|')+')(?=[ _])')
|
338
339
|
EXP_REPLACEMENT = PREDEFINED_VARS.merge(CONDITIONAL_OPERATORS)
|
339
340
|
|
340
341
|
def self.normalize_expression(expression)
|
@@ -351,18 +352,30 @@ class Cloudinary::Utils
|
|
351
352
|
# @return [string] layer transformation string
|
352
353
|
# @private
|
353
354
|
def self.process_layer(layer)
|
355
|
+
if layer.is_a? String and layer.start_with?("fetch:")
|
356
|
+
layer = {:url => layer[6..-1]} # omit "fetch:" prefix
|
357
|
+
end
|
354
358
|
if layer.is_a? Hash
|
355
359
|
layer = symbolize_keys layer
|
356
360
|
public_id = layer[:public_id]
|
357
361
|
format = layer[:format]
|
362
|
+
fetch = layer[:url]
|
358
363
|
resource_type = layer[:resource_type] || "image"
|
359
|
-
type = layer[:type]
|
364
|
+
type = layer[:type]
|
360
365
|
text = layer[:text]
|
361
366
|
text_style = nil
|
362
367
|
components = []
|
363
368
|
|
369
|
+
if type.nil?
|
370
|
+
if fetch.nil?
|
371
|
+
type = "upload"
|
372
|
+
else
|
373
|
+
type = "fetch"
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
364
377
|
if public_id.present?
|
365
|
-
if type == "fetch"
|
378
|
+
if type == "fetch" and public_id.match(%r(^https?:/)i)
|
366
379
|
public_id = Base64.urlsafe_encode64(public_id)
|
367
380
|
else
|
368
381
|
public_id = public_id.gsub("/", ":")
|
@@ -370,14 +383,15 @@ class Cloudinary::Utils
|
|
370
383
|
end
|
371
384
|
end
|
372
385
|
|
373
|
-
if
|
374
|
-
|
386
|
+
if fetch.present? && fetch.match(%r(^https?:/)i)
|
387
|
+
fetch = Base64.urlsafe_encode64(fetch)
|
388
|
+
elsif text.blank? && resource_type != "text"
|
389
|
+
if public_id.blank? && type != "fetch"
|
375
390
|
raise(CloudinaryException, "Must supply public_id for resource_type layer_parameter")
|
376
391
|
end
|
377
392
|
if resource_type == "subtitles"
|
378
393
|
text_style = text_style(layer)
|
379
394
|
end
|
380
|
-
|
381
395
|
else
|
382
396
|
resource_type = "text"
|
383
397
|
type = nil
|
@@ -403,6 +417,7 @@ class Cloudinary::Utils
|
|
403
417
|
components.push(type) if type != "upload"
|
404
418
|
components.push(text_style)
|
405
419
|
components.push(public_id)
|
420
|
+
components.push(fetch)
|
406
421
|
components.push(text)
|
407
422
|
layer = components.reject(&:blank?).join(":")
|
408
423
|
end
|
@@ -430,6 +445,8 @@ class Cloudinary::Utils
|
|
430
445
|
]
|
431
446
|
|
432
447
|
def self.text_style(layer)
|
448
|
+
return layer[:text_style] if layer[:text_style].present?
|
449
|
+
|
433
450
|
font_family = layer[:font_family]
|
434
451
|
font_size = layer[:font_size]
|
435
452
|
keywords = []
|
@@ -719,6 +736,43 @@ class Cloudinary::Utils
|
|
719
736
|
params
|
720
737
|
end
|
721
738
|
|
739
|
+
# Helper method for generating download URLs
|
740
|
+
#
|
741
|
+
# @param [String] action @see Cloudinary::Utils.cloudinary_api_url
|
742
|
+
# @param [Hash] params Query parameters in generated URL
|
743
|
+
# @param [Hash] options Additional options
|
744
|
+
# @yield [query_parameters] Invokes the block with query parameters to override how to encode them
|
745
|
+
#
|
746
|
+
# @return [String]
|
747
|
+
def self.cloudinary_api_download_url(action, params, options = {})
|
748
|
+
cloudinary_params = sign_request(params.merge(mode: MODE_DOWNLOAD), options)
|
749
|
+
|
750
|
+
"#{Cloudinary::Utils.cloudinary_api_url(action, options)}?#{hash_query_params(cloudinary_params)}"
|
751
|
+
end
|
752
|
+
private_class_method :cloudinary_api_download_url
|
753
|
+
|
754
|
+
# Return a signed URL to the 'generate_sprite' endpoint with 'mode=download'.
|
755
|
+
#
|
756
|
+
# @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
|
757
|
+
# @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
|
758
|
+
#
|
759
|
+
# @return [String] The signed URL to download sprite
|
760
|
+
def self.download_generated_sprite(tag, options = {})
|
761
|
+
params = build_multi_and_sprite_params(tag, options)
|
762
|
+
cloudinary_api_download_url("sprite", params, options)
|
763
|
+
end
|
764
|
+
|
765
|
+
# Return a signed URL to the 'multi' endpoint with 'mode=download'.
|
766
|
+
#
|
767
|
+
# @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
|
768
|
+
# @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
|
769
|
+
#
|
770
|
+
# @return [String] The signed URL to download multi
|
771
|
+
def self.download_multi(tag, options = {})
|
772
|
+
params = build_multi_and_sprite_params(tag, options)
|
773
|
+
cloudinary_api_download_url("multi", params, options)
|
774
|
+
end
|
775
|
+
|
722
776
|
def self.private_download_url(public_id, format, options = {})
|
723
777
|
cloudinary_params = sign_request({
|
724
778
|
:timestamp=>Time.now.to_i,
|
@@ -767,11 +821,10 @@ class Cloudinary::Utils
|
|
767
821
|
# @option options [String] :keep_derived (false) keep the derived images used for generating the archive
|
768
822
|
# @return [String] archive url
|
769
823
|
def self.download_archive_url(options = {})
|
770
|
-
|
771
|
-
|
824
|
+
params = Cloudinary::Utils.archive_params(options)
|
825
|
+
cloudinary_api_download_url("generate_archive", params, options)
|
772
826
|
end
|
773
827
|
|
774
|
-
|
775
828
|
# Returns a URL that when invokes creates an zip archive and returns it.
|
776
829
|
# @see download_archive_url
|
777
830
|
def self.download_zip_url(options = {})
|
@@ -832,7 +885,7 @@ class Cloudinary::Utils
|
|
832
885
|
|
833
886
|
# Based on CGI::unescape. In addition keeps '+' character as is
|
834
887
|
def self.smart_unescape(string)
|
835
|
-
CGI.unescape(string.
|
888
|
+
CGI.unescape(string.gsub('+', '%2B'))
|
836
889
|
end
|
837
890
|
|
838
891
|
def self.random_public_id
|
@@ -1050,7 +1103,7 @@ class Cloudinary::Utils
|
|
1050
1103
|
Cloudinary::AuthToken.generate options
|
1051
1104
|
|
1052
1105
|
end
|
1053
|
-
|
1106
|
+
|
1054
1107
|
private
|
1055
1108
|
|
1056
1109
|
|
@@ -1067,24 +1120,24 @@ class Cloudinary::Utils
|
|
1067
1120
|
source
|
1068
1121
|
end
|
1069
1122
|
private_class_method :fully_unescape
|
1070
|
-
|
1123
|
+
|
1071
1124
|
def self.hash_query_params(hash)
|
1072
1125
|
if hash.respond_to?("to_query")
|
1073
1126
|
hash.to_query
|
1074
1127
|
else
|
1075
|
-
flat_hash_to_query_params(hash)
|
1128
|
+
flat_hash_to_query_params(hash)
|
1076
1129
|
end
|
1077
1130
|
end
|
1078
1131
|
|
1079
1132
|
def self.flat_hash_to_query_params(hash)
|
1080
|
-
hash.collect do |key, value|
|
1133
|
+
hash.collect do |key, value|
|
1081
1134
|
if value.is_a?(Array)
|
1082
1135
|
value.map{|v| "#{CGI.escape(key.to_s)}[]=#{CGI.escape(v.to_s)}"}.join("&")
|
1083
|
-
else
|
1136
|
+
else
|
1084
1137
|
"#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
|
1085
|
-
end
|
1138
|
+
end
|
1086
1139
|
end.compact.sort!.join('&')
|
1087
|
-
end
|
1140
|
+
end
|
1088
1141
|
|
1089
1142
|
def self.number_pattern
|
1090
1143
|
"([0-9]*)\\.([0-9]+)|([0-9]+)"
|
@@ -1132,9 +1185,10 @@ class Cloudinary::Utils
|
|
1132
1185
|
|
1133
1186
|
# A video codec parameter can be either a String or a Hash.
|
1134
1187
|
#
|
1135
|
-
# @param [Object] param <code>vc_<codec>[ : <profile> : [<level>]]</code>
|
1188
|
+
# @param [Object] param <code>vc_<codec>[ : <profile> : [<level> : [<b_frames>]]]</code>
|
1136
1189
|
# or <code>{ codec: 'h264', profile: 'basic', level: '3.1' }</code>
|
1137
|
-
#
|
1190
|
+
# or <code>{ codec: 'h265', profile: 'auto', level: 'auto', b_frames: false }</code>
|
1191
|
+
# @return [String] <code><codec> : <profile> : [<level> : [<b_frames>]]]</code> if a Hash was provided
|
1138
1192
|
# or the param if a String was provided.
|
1139
1193
|
# Returns NIL if param is not a Hash or String
|
1140
1194
|
# @private
|
@@ -1148,6 +1202,9 @@ class Cloudinary::Utils
|
|
1148
1202
|
video.concat ":" + param[:profile]
|
1149
1203
|
if param.has_key? :level
|
1150
1204
|
video.concat ":" + param[:level]
|
1205
|
+
if param.has_key?(:b_frames) && param[:b_frames] === false
|
1206
|
+
video.concat ":bframes_no"
|
1207
|
+
end
|
1151
1208
|
end
|
1152
1209
|
end
|
1153
1210
|
end
|
@@ -1191,6 +1248,41 @@ class Cloudinary::Utils
|
|
1191
1248
|
REMOTE_URL_REGEX === url
|
1192
1249
|
end
|
1193
1250
|
|
1251
|
+
# Build params for multi, download_multi, generate_sprite, and download_generated_sprite methods
|
1252
|
+
#
|
1253
|
+
# @param [String|Hash] tag_or_options Treated as additional options when hash is passed, otherwise as a tag
|
1254
|
+
# @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
|
1255
|
+
#
|
1256
|
+
# @return [Hash]
|
1257
|
+
#
|
1258
|
+
# @private
|
1259
|
+
def self.build_multi_and_sprite_params(tag_or_options, options)
|
1260
|
+
if tag_or_options.is_a?(Hash)
|
1261
|
+
if options.blank?
|
1262
|
+
options = tag_or_options
|
1263
|
+
tag_or_options = nil
|
1264
|
+
else
|
1265
|
+
raise "First argument must be a tag when additional options are passed"
|
1266
|
+
end
|
1267
|
+
end
|
1268
|
+
urls = options.delete(:urls)
|
1269
|
+
|
1270
|
+
if tag_or_options.blank? && urls.blank?
|
1271
|
+
raise "Either tag or urls are required"
|
1272
|
+
end
|
1273
|
+
|
1274
|
+
{
|
1275
|
+
:tag => tag_or_options,
|
1276
|
+
:urls => urls,
|
1277
|
+
:transformation => Cloudinary::Utils.generate_transformation_string(options.merge(:fetch_format => options[:format])),
|
1278
|
+
:notification_url => options[:notification_url],
|
1279
|
+
:format => options[:format],
|
1280
|
+
:async => options[:async],
|
1281
|
+
:mode => options[:mode],
|
1282
|
+
:timestamp => (options[:timestamp] || Time.now.to_i)
|
1283
|
+
}
|
1284
|
+
end
|
1285
|
+
|
1194
1286
|
# The returned url should allow downloading the backedup asset based on the version and asset id
|
1195
1287
|
#
|
1196
1288
|
# asset and version id are returned with resource(<PUBLIC_ID1>, { versions: true })
|
@@ -1224,10 +1316,53 @@ class Cloudinary::Utils
|
|
1224
1316
|
end
|
1225
1317
|
end
|
1226
1318
|
|
1319
|
+
# Verifies the authenticity of an API response signature.
|
1320
|
+
#
|
1321
|
+
# @param [String] public_id he public id of the asset as returned in the API response
|
1322
|
+
# @param [Fixnum] version The version of the asset as returned in the API response
|
1323
|
+
# @param [String] signature Actual signature. Can be retrieved from the X-Cld-Signature header
|
1324
|
+
# @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash
|
1325
|
+
# @param [Hash] options
|
1326
|
+
# @option options [String] :api_secret API secret, if not passed taken from global config
|
1327
|
+
#
|
1328
|
+
# @return [Boolean]
|
1329
|
+
def self.verify_api_response_signature(public_id, version, signature, signature_algorithm = nil, options = {})
|
1330
|
+
api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise("Must supply api_secret")
|
1331
|
+
|
1332
|
+
parameters_to_sign = {
|
1333
|
+
:public_id => public_id,
|
1334
|
+
:version => version
|
1335
|
+
}
|
1336
|
+
|
1337
|
+
signature == api_sign_request(parameters_to_sign, api_secret, signature_algorithm)
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
# Verifies the authenticity of a notification signature.
|
1341
|
+
#
|
1342
|
+
# @param [String] body JSON of the request's body
|
1343
|
+
# @param [Fixnum] timestamp Unix timestamp. Can be retrieved from the X-Cld-Timestamp header
|
1344
|
+
# @param [String] signature Actual signature. Can be retrieved from the X-Cld-Signature header
|
1345
|
+
# @param [Fixnum] valid_for The desired time in seconds for considering the request valid
|
1346
|
+
# @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash
|
1347
|
+
# @param [Hash] options
|
1348
|
+
# @option options [String] :api_secret API secret, if not passed taken from global config
|
1349
|
+
#
|
1350
|
+
# @return [Boolean]
|
1351
|
+
def self.verify_notification_signature(body, timestamp, signature, valid_for = 7200, signature_algorithm = nil, options = {})
|
1352
|
+
api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise("Must supply api_secret")
|
1353
|
+
raise("Body should be of String type") unless body.is_a?(String)
|
1354
|
+
# verify that signature is valid for the given timestamp
|
1355
|
+
return false if timestamp < (Time.now - valid_for).to_i
|
1356
|
+
|
1357
|
+
payload_hash = hash("#{body}#{timestamp}#{api_secret}", signature_algorithm, :hexdigest)
|
1358
|
+
|
1359
|
+
signature == payload_hash
|
1360
|
+
end
|
1361
|
+
|
1227
1362
|
# Computes hash from input string using specified algorithm.
|
1228
1363
|
#
|
1229
1364
|
# @param [String] input String which to compute hash from
|
1230
|
-
# @param [
|
1365
|
+
# @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash
|
1231
1366
|
# @param [Symbol] hash_method Hash method applied to a signature algorithm (:digest or :hexdigest)
|
1232
1367
|
#
|
1233
1368
|
# @return [String] Computed hash value
|
data/lib/cloudinary/version.rb
CHANGED