cloudinary 1.20.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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