cloudinary 1.20.0 → 1.21.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9b641514503d45c3a6c4e8ba5ed87f0f5b8e521260eda789212543be5ccf77a1
4
- data.tar.gz: 0f65e99aa08e446f01d783af5284a1265aaa4f28a1e95c284addd30beb36ec8c
3
+ metadata.gz: fbc5f576707d33b684e97610585e92838695cbd373cace7c16b0078021b46871
4
+ data.tar.gz: a5fc7e5d8f0930223cb03b4ca07910ba3b8a6e8ac18b5d6028f9a8963311f8d8
5
5
  SHA512:
6
- metadata.gz: 191f627013b5f97fca44e748fd1c4631f9af58a69680b02fdc6b4604e2ddf31216542fcb88e2d1daa825c0ff22a138f2c7d0b41d4895a69cc1b7eaa42a1ab16a
7
- data.tar.gz: 8a5abe09f562e188e1e6d43d05f462939c62c4070ac50e93c3acaf876998e651aa2fc50775a10c5fb324d388fac9d599c5fe0e60480cdbbebeb7eedf699057e8
6
+ metadata.gz: 888d693726c91cba5113ca6d4e13f89b859fc01fde5603ace994eb13a907881d4d5e84a7150501c970b1792c47286ae4a9d889f61b1e41408e25a4aea20cf11d
7
+ data.tar.gz: 6a76cfcc6187c87314f1676a769b8fbfcfda08a5a22d00403f689056a680e4acb15d7360b919a2132c254295c8439e2072be74a8a2891362920f217d387d14ef
@@ -3,7 +3,7 @@ name: Bug report
3
3
  about: Bug report for Cloudinary Ruby SDK
4
4
  title: ''
5
5
  labels: ''
6
- assignees: const-cloudinary
6
+ assignees: ''
7
7
 
8
8
  ---
9
9
 
@@ -14,11 +14,11 @@ Before proceeding, please update to latest version and test if the issue persist
14
14
 
15
15
 
16
16
  ## Issue Type (Can be multiple)
17
- [ ] Build - Can’t install or import the SDK
18
- [ ] Performance - Performance issues
19
- [ ] Behaviour - Functions aren’t working as expected (Such as generate URL)
20
- [ ] Documentation - Inconsistency between the docs and behaviour
21
- [ ] Other (Specify)
17
+ - [ ] Build - Cannot install or import the SDK
18
+ - [ ] Performance - Performance issues
19
+ - [ ] Behaviour - Functions are not working as expected (such as generate URL)
20
+ - [ ] Documentation - Inconsistency between the docs and behaviour
21
+ - [ ] Other (Specify)
22
22
 
23
23
  ## Steps to reproduce
24
24
  … if applicable
@@ -27,16 +27,17 @@ Before proceeding, please update to latest version and test if the issue persist
27
27
 
28
28
 
29
29
  ## Operating System
30
- [ ] Linux
31
- [ ] Windows
32
- [ ] OSX
33
- [ ] All
30
+ - [ ] Linux
31
+ - [ ] Windows
32
+ - [ ] macOS
33
+ - [ ] All
34
34
 
35
35
  ## Environment and Libraries (fill in the version numbers)
36
- Cloudinary Ruby SDK version - 0.0.0
37
- Ruby Version - 0.0.0
38
- Rails Version - 0.0.0
39
- Other Libraries (Carrierwave, ActiveStorage, etc) - 0.0.0
36
+ - Cloudinary Ruby SDK version - 0.0.0
37
+ - Ruby Version - 0.0.0
38
+ - Rails Version - 0.0.0
39
+ - Other Libraries (Carrierwave, ActiveStorage, etc) - 0.0.0
40
40
 
41
41
  ## Repository
42
+
42
43
  If possible, please provide a link to a reproducible repository that showcases the problem
@@ -3,7 +3,7 @@ name: Feature request
3
3
  about: Feature request for Cloudinary Ruby SDK
4
4
  title: ''
5
5
  labels: ''
6
- assignees: const-cloudinary
6
+ assignees: ''
7
7
 
8
8
  ---
9
9
 
@@ -4,21 +4,28 @@ Provide some context as to what was changed, from an implementation standpoint.
4
4
  -->
5
5
 
6
6
  #### What does this PR address?
7
- [ ] Gitub issue (Add reference - #XX)
8
- [ ] Refactoring
9
- [ ] New feature
10
- [ ] Bug fix
11
- [ ] Adds more tests
7
+ - [ ] GitHub issue (Add reference - #XX)
8
+ - [ ] Refactoring
9
+ - [ ] New feature
10
+ - [ ] Bug fix
11
+ - [ ] Adds more tests
12
12
 
13
13
  #### Are tests included?
14
- [ ] Yes
15
- [ ] No
14
+ - [ ] Yes
15
+ - [ ] No
16
16
 
17
- #### Reviewer, Please Note:
17
+ #### Reviewer, please note:
18
18
  <!--
19
19
  List anything here that the reviewer should pay special attention to. This might
20
20
  include, for example:
21
- Dependence on other PRs
22
- Reference to other Cloudinary SDKs
23
- Changes that seem arbitrary without further explanations
21
+ * Dependence on other PRs
22
+ * Reference to other Cloudinary SDKs
23
+ * Changes that seem arbitrary without further explanations
24
24
  -->
25
+
26
+ #### Checklist:
27
+ <!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
28
+ <!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
29
+ - [ ] My code follows the code style of this project.
30
+ - [ ] My change requires a change to the documentation.
31
+ - [ ] I ran the full test suite before pushing the changes and all the tests pass.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ 1.21.0 / 2021-08-23
2
+ ==================
3
+
4
+ New functionality and features
5
+ ------------------------------
6
+
7
+ * Add support for `create_slideshow` Upload API
8
+ * Add support for variables in text style
9
+ * Add support for `context` and `metadata` in `rename` Upload API
10
+ * Add support for `reorder_metadata_field_datasource` Admin API
11
+ * Add `verify_api_response_signature` and `verify_notification_signature` helpers
12
+ * Add `download_generated_sprite` and `download_multi` methods
13
+ * Add support for `urls` in multi and sprite APIs
14
+ * Add support for `live` parameter in upload presets
15
+ * Add support for `metadata` parameter in `resources` Admin APIs
16
+
17
+ Other Changes
18
+ -------------
19
+
20
+ * Fix `transformations` API call
21
+ * Fix named parameters normalization issue
22
+ * Remove duplicates in Search Api fields
23
+ * Improve configuration tests
24
+ * Add tests for Provisioning API
25
+ * Refactor metadata usage in tests
26
+ * Update GitHub templates
27
+
1
28
  1.20.0 / 2021-03-26
2
29
  ==================
3
30
 
data/lib/cloudinary.rb CHANGED
@@ -133,13 +133,22 @@ module Cloudinary
133
133
  #
134
134
  # @return [OpenStruct]
135
135
  def self.make_new_config(config_module)
136
- OpenStruct.new((YAML.load(ERB.new(IO.read(config_dir.join("cloudinary.yml"))).result)[config_env] rescue {})).tap do |config|
136
+ import_settings_from_file.tap do |config|
137
137
  config.extend(config_module)
138
138
  config.load_config_from_env
139
139
  end
140
140
  end
141
141
 
142
142
  private_class_method :make_new_config
143
+
144
+ # Import settings from yaml file
145
+ #
146
+ # @return [OpenStruct]
147
+ def self.import_settings_from_file
148
+ OpenStruct.new((YAML.load(ERB.new(IO.read(config_dir.join("cloudinary.yml"))).result)[config_env] rescue {}))
149
+ end
150
+
151
+ private_class_method :import_settings_from_file
143
152
  end
144
153
  # Prevent require loop if included after Rails is already initialized.
145
154
  require "cloudinary/helper" if defined?(::ActionView::Base)
@@ -128,23 +128,18 @@ class Cloudinary::AccountApi
128
128
  end
129
129
 
130
130
  # Lists users in the account.
131
- # @param [Boolean] pending Whether to only return pending users. Default: all users
131
+ # @param [Boolean] pending Limit results to pending users (true), users that are not pending (false), or all users (nil, the default)
132
132
  # @param [Array<String>] user_ids A list of up to 100 user IDs. When provided, other parameters are ignored.
133
133
  # @param [String] prefix Returns users where the name or email address begins with the specified case-insensitive string.
134
134
  # @param [String] sub_account_id Only returns users who have access to the specified account.
135
135
  # @param [Object] options additional options
136
136
  def self.users(pending = nil, user_ids = [], prefix = nil, sub_account_id = nil, options = {})
137
- params = if user_ids && user_ids.count > 0
138
- {
139
- ids: user_ids
140
- }
141
- else
142
- {
143
- prefix: prefix,
144
- sub_account_id: sub_account_id,
145
- pending: pending
146
- }
147
- end
137
+ params = {
138
+ ids: user_ids,
139
+ prefix: prefix,
140
+ sub_account_id: sub_account_id,
141
+ pending: pending
142
+ }
148
143
 
149
144
  call_account_api(:get, 'users', params, options.merge(content_type: :json))
150
145
  end
@@ -34,25 +34,25 @@ class Cloudinary::Api
34
34
  type = options[:type]
35
35
  uri = "resources/#{resource_type}"
36
36
  uri += "/#{type}" unless type.blank?
37
- call_api(:get, uri, only(options, :next_cursor, :max_results, :prefix, :tags, :context, :moderations, :direction, :start_at), options)
37
+ call_api(:get, uri, only(options, :next_cursor, :max_results, :prefix, :tags, :context, :moderations, :direction, :start_at, :metadata), options)
38
38
  end
39
39
 
40
40
  def self.resources_by_tag(tag, options={})
41
41
  resource_type = options[:resource_type] || "image"
42
42
  uri = "resources/#{resource_type}/tags/#{tag}"
43
- call_api(:get, uri, only(options, :next_cursor, :max_results, :tags, :context, :moderations, :direction), options)
43
+ call_api(:get, uri, only(options, :next_cursor, :max_results, :tags, :context, :moderations, :direction, :metadata), options)
44
44
  end
45
45
 
46
46
  def self.resources_by_moderation(kind, status, options={})
47
47
  resource_type = options[:resource_type] || "image"
48
48
  uri = "resources/#{resource_type}/moderations/#{kind}/#{status}"
49
- call_api(:get, uri, only(options, :next_cursor, :max_results, :tags, :context, :moderations, :direction), options)
49
+ call_api(:get, uri, only(options, :next_cursor, :max_results, :tags, :context, :moderations, :direction, :metadata), options)
50
50
  end
51
51
 
52
52
  def self.resources_by_context(key, value=nil, options={})
53
53
  resource_type = options[:resource_type] || "image"
54
54
  uri = "resources/#{resource_type}/context"
55
- params = only(options, :next_cursor, :max_results, :tags, :context, :moderations, :direction,:key,:value)
55
+ params = only(options, :next_cursor, :max_results, :tags, :context, :moderations, :direction, :key, :value, :metadata)
56
56
  params[:key] = key
57
57
  params[:value] = value
58
58
  call_api(:get, uri, params, options)
@@ -177,24 +177,32 @@ class Cloudinary::Api
177
177
  end
178
178
 
179
179
  def self.transformation(transformation, options={})
180
- call_api(:get, "transformations/#{transformation_string(transformation)}", only(options, :next_cursor, :max_results), options)
180
+ params = only(options, :next_cursor, :max_results)
181
+ params[:transformation] = Cloudinary::Utils.build_eager(transformation)
182
+ call_api(:get, "transformations", params, options)
181
183
  end
182
184
 
183
185
  def self.delete_transformation(transformation, options={})
184
- call_api(:delete, "transformations/#{transformation_string(transformation)}", {}, options)
186
+ call_api(:delete, "transformations", {:transformation => Cloudinary::Utils.build_eager(transformation)}, options)
185
187
  end
186
188
 
187
189
  # updates - supports:
188
190
  # "allowed_for_strict" boolean
189
191
  # "unsafe_update" transformation params - updates a named transformation parameters without regenerating existing images
190
192
  def self.update_transformation(transformation, updates, options={})
191
- params = only(updates, :allowed_for_strict)
192
- params[:unsafe_update] = transformation_string(updates[:unsafe_update]) if updates[:unsafe_update]
193
- call_api(:put, "transformations/#{transformation_string(transformation)}", params, options)
193
+ params = only(updates, :allowed_for_strict)
194
+ params[:unsafe_update] = Cloudinary::Utils.build_eager(updates[:unsafe_update]) if updates[:unsafe_update]
195
+ params[:transformation] = Cloudinary::Utils.build_eager(transformation)
196
+ call_api(:put, "transformations", params, options)
194
197
  end
195
198
 
196
199
  def self.create_transformation(name, definition, options={})
197
- call_api(:post, "transformations/#{name}", { :transformation => transformation_string(definition) }, options)
200
+ params = {
201
+ :name => name,
202
+ :transformation => Cloudinary::Utils.build_eager(definition)
203
+ }
204
+
205
+ call_api(:post, "transformations", params, options)
198
206
  end
199
207
 
200
208
  # upload presets
@@ -212,12 +220,12 @@ class Cloudinary::Api
212
220
 
213
221
  def self.update_upload_preset(name, options={})
214
222
  params = Cloudinary::Uploader.build_upload_params(options)
215
- call_api(:put, "upload_presets/#{name}", params.merge(only(options, :unsigned, :disallow_public_id)), options)
223
+ call_api(:put, "upload_presets/#{name}", params.merge(only(options, :unsigned, :disallow_public_id, :live)), options)
216
224
  end
217
225
 
218
226
  def self.create_upload_preset(options={})
219
227
  params = Cloudinary::Uploader.build_upload_params(options)
220
- call_api(:post, "upload_presets", params.merge(only(options, :name, :unsigned, :disallow_public_id)), options)
228
+ call_api(:post, "upload_presets", params.merge(only(options, :name, :unsigned, :disallow_public_id, :live)), options)
221
229
  end
222
230
 
223
231
  def self.root_folders(options={})
@@ -478,6 +486,23 @@ class Cloudinary::Api
478
486
  call_metadata_api(:post, uri, params, options)
479
487
  end
480
488
 
489
+ # Reorders metadata field datasource. Currently supports only value.
490
+ #
491
+ # @param [String] field_external_id The ID of the metadata field
492
+ # @param [String] order_by Criteria for the order. Currently supports only value
493
+ # @param [String] direction Optional (gets either asc or desc)
494
+ # @param [Hash] options Configuration options
495
+ #
496
+ # @return [Cloudinary::Api::Response]
497
+ #
498
+ # @raise [Cloudinary::Api::Error]
499
+ def self.reorder_metadata_field_datasource(field_external_id, order_by, direction = nil, options = {})
500
+ uri = [field_external_id, "datasource", "order"]
501
+ params = { :order_by => order_by, :direction => direction }
502
+
503
+ call_metadata_api(:post, uri, params, options)
504
+ end
505
+
481
506
  protected
482
507
 
483
508
  def self.call_api(method, uri, params, options)
@@ -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
- :sort_by => [],
5
- :aggregate => [],
6
- :with_field => []
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[:sort_by].push(field_name => dir)
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[:aggregate].push(value)
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[:with_field].push(value)
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.select { |_, value| !value.nil? && !(value.is_a?(Array) && value.empty?) }
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 = {})
@@ -159,7 +159,9 @@ class Cloudinary::Uploader
159
159
  :from_public_id => from_public_id,
160
160
  :to_public_id => to_public_id,
161
161
  :to_type => options[:to_type],
162
- :invalidate => Cloudinary::Utils.as_safe_bool(options[:invalidate])
162
+ :invalidate => Cloudinary::Utils.as_safe_bool(options[:invalidate]),
163
+ :context => options[:context],
164
+ :metadata => options[:metadata]
163
165
  }
164
166
  end
165
167
  end
@@ -207,17 +209,42 @@ class Cloudinary::Uploader
207
209
  end
208
210
  end
209
211
 
210
- def self.generate_sprite(tag, options={})
212
+ SLIDESHOW_PARAMS = [:notification_url, :public_id, :upload_preset]
213
+
214
+ # Creates auto-generated video slideshow.
215
+ #
216
+ # @param [Hash] options Additional options.
217
+ #
218
+ # @return [Hash] Hash with meta information URLs of generated slideshow resources.
219
+ def self.create_slideshow(options = {})
220
+ options[:resource_type] ||= :video
221
+
222
+ call_api("create_slideshow", options) do
223
+ params = {
224
+ :timestamp => Time.now.to_i,
225
+ :transformation => Cloudinary::Utils.build_eager(options[:transformation]),
226
+ :manifest_transformation => Cloudinary::Utils.build_eager(options[:manifest_transformation]),
227
+ :manifest_json => options[:manifest_json] && options[:manifest_json].to_json,
228
+ :tags => options[:tags] && Cloudinary::Utils.build_array(options[:tags]).join(","),
229
+ :overwrite => Cloudinary::Utils.as_safe_bool(options[:overwrite])
230
+ }
231
+ SLIDESHOW_PARAMS.each { |k| params[k] = options[k] unless options[k].nil? }
232
+
233
+ params
234
+ end
235
+ end
236
+
237
+ # Generates sprites by merging multiple images into a single large image.
238
+ #
239
+ # @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
240
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
241
+ #
242
+ # @return [Hash] Hash with meta information URLs of generated sprite resources
243
+ def self.generate_sprite(tag, options = {})
211
244
  version_store = options.delete(:version_store)
212
245
 
213
246
  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
- }
247
+ Cloudinary::Utils.build_multi_and_sprite_params(tag, options)
221
248
  end
222
249
 
223
250
  if version_store == :file && result && result["version"]
@@ -229,16 +256,15 @@ class Cloudinary::Uploader
229
256
  return result
230
257
  end
231
258
 
232
- def self.multi(tag, options={})
259
+ # Creates either a single animated image, video or a PDF.
260
+ #
261
+ # @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
262
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
263
+ #
264
+ # @return [Hash] Hash with meta information URLs of the generated file
265
+ def self.multi(tag, options = {})
233
266
  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
- }
267
+ Cloudinary::Utils.build_multi_and_sprite_params(tag, options)
242
268
  end
243
269
  end
244
270
 
@@ -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('(\$_*[^_ ]+)|(?<!\$)('+PREDEFINED_VARS.keys.join("|")+')'+'|('+CONDITIONAL_OPERATORS.keys.reverse.map { |k| Regexp.escape(k) }.join('|')+')(?=[ _])')
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)
@@ -430,6 +431,8 @@ class Cloudinary::Utils
430
431
  ]
431
432
 
432
433
  def self.text_style(layer)
434
+ return layer[:text_style] if layer[:text_style].present?
435
+
433
436
  font_family = layer[:font_family]
434
437
  font_size = layer[:font_size]
435
438
  keywords = []
@@ -719,6 +722,43 @@ class Cloudinary::Utils
719
722
  params
720
723
  end
721
724
 
725
+ # Helper method for generating download URLs
726
+ #
727
+ # @param [String] action @see Cloudinary::Utils.cloudinary_api_url
728
+ # @param [Hash] params Query parameters in generated URL
729
+ # @param [Hash] options Additional options
730
+ # @yield [query_parameters] Invokes the block with query parameters to override how to encode them
731
+ #
732
+ # @return [String]
733
+ def self.cloudinary_api_download_url(action, params, options = {})
734
+ cloudinary_params = sign_request(params.merge(mode: MODE_DOWNLOAD), options)
735
+
736
+ "#{Cloudinary::Utils.cloudinary_api_url(action, options)}?#{hash_query_params(cloudinary_params)}"
737
+ end
738
+ private_class_method :cloudinary_api_download_url
739
+
740
+ # Return a signed URL to the 'generate_sprite' endpoint with 'mode=download'.
741
+ #
742
+ # @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
743
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
744
+ #
745
+ # @return [String] The signed URL to download sprite
746
+ def self.download_generated_sprite(tag, options = {})
747
+ params = build_multi_and_sprite_params(tag, options)
748
+ cloudinary_api_download_url("sprite", params, options)
749
+ end
750
+
751
+ # Return a signed URL to the 'multi' endpoint with 'mode=download'.
752
+ #
753
+ # @param [String|Hash] tag Treated as additional options when hash is passed, otherwise as a tag
754
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
755
+ #
756
+ # @return [String] The signed URL to download multi
757
+ def self.download_multi(tag, options = {})
758
+ params = build_multi_and_sprite_params(tag, options)
759
+ cloudinary_api_download_url("multi", params, options)
760
+ end
761
+
722
762
  def self.private_download_url(public_id, format, options = {})
723
763
  cloudinary_params = sign_request({
724
764
  :timestamp=>Time.now.to_i,
@@ -767,11 +807,10 @@ class Cloudinary::Utils
767
807
  # @option options [String] :keep_derived (false) keep the derived images used for generating the archive
768
808
  # @return [String] archive url
769
809
  def self.download_archive_url(options = {})
770
- cloudinary_params = sign_request(Cloudinary::Utils.archive_params(options.merge(:mode => "download")), options)
771
- return Cloudinary::Utils.cloudinary_api_url("generate_archive", options) + "?" + hash_query_params(cloudinary_params)
810
+ params = Cloudinary::Utils.archive_params(options)
811
+ cloudinary_api_download_url("generate_archive", params, options)
772
812
  end
773
813
 
774
-
775
814
  # Returns a URL that when invokes creates an zip archive and returns it.
776
815
  # @see download_archive_url
777
816
  def self.download_zip_url(options = {})
@@ -1191,6 +1230,41 @@ class Cloudinary::Utils
1191
1230
  REMOTE_URL_REGEX === url
1192
1231
  end
1193
1232
 
1233
+ # Build params for multi, download_multi, generate_sprite, and download_generated_sprite methods
1234
+ #
1235
+ # @param [String|Hash] tag_or_options Treated as additional options when hash is passed, otherwise as a tag
1236
+ # @param [Hash] options Additional options. Should be omitted when +tag_or_options+ is a Hash
1237
+ #
1238
+ # @return [Hash]
1239
+ #
1240
+ # @private
1241
+ def self.build_multi_and_sprite_params(tag_or_options, options)
1242
+ if tag_or_options.is_a?(Hash)
1243
+ if options.blank?
1244
+ options = tag_or_options
1245
+ tag_or_options = nil
1246
+ else
1247
+ raise "First argument must be a tag when additional options are passed"
1248
+ end
1249
+ end
1250
+ urls = options.delete(:urls)
1251
+
1252
+ if tag_or_options.blank? && urls.blank?
1253
+ raise "Either tag or urls are required"
1254
+ end
1255
+
1256
+ {
1257
+ :tag => tag_or_options,
1258
+ :urls => urls,
1259
+ :transformation => Cloudinary::Utils.generate_transformation_string(options.merge(:fetch_format => options[:format])),
1260
+ :notification_url => options[:notification_url],
1261
+ :format => options[:format],
1262
+ :async => options[:async],
1263
+ :mode => options[:mode],
1264
+ :timestamp => (options[:timestamp] || Time.now.to_i)
1265
+ }
1266
+ end
1267
+
1194
1268
  # The returned url should allow downloading the backedup asset based on the version and asset id
1195
1269
  #
1196
1270
  # asset and version id are returned with resource(<PUBLIC_ID1>, { versions: true })
@@ -1224,10 +1298,53 @@ class Cloudinary::Utils
1224
1298
  end
1225
1299
  end
1226
1300
 
1301
+ # Verifies the authenticity of an API response signature.
1302
+ #
1303
+ # @param [String] public_id he public id of the asset as returned in the API response
1304
+ # @param [Fixnum] version The version of the asset as returned in the API response
1305
+ # @param [String] signature Actual signature. Can be retrieved from the X-Cld-Signature header
1306
+ # @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash
1307
+ # @param [Hash] options
1308
+ # @option options [String] :api_secret API secret, if not passed taken from global config
1309
+ #
1310
+ # @return [Boolean]
1311
+ def self.verify_api_response_signature(public_id, version, signature, signature_algorithm = nil, options = {})
1312
+ api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise("Must supply api_secret")
1313
+
1314
+ parameters_to_sign = {
1315
+ :public_id => public_id,
1316
+ :version => version
1317
+ }
1318
+
1319
+ signature == api_sign_request(parameters_to_sign, api_secret, signature_algorithm)
1320
+ end
1321
+
1322
+ # Verifies the authenticity of a notification signature.
1323
+ #
1324
+ # @param [String] body JSON of the request's body
1325
+ # @param [Fixnum] timestamp Unix timestamp. Can be retrieved from the X-Cld-Timestamp header
1326
+ # @param [String] signature Actual signature. Can be retrieved from the X-Cld-Signature header
1327
+ # @param [Fixnum] valid_for The desired time in seconds for considering the request valid
1328
+ # @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash
1329
+ # @param [Hash] options
1330
+ # @option options [String] :api_secret API secret, if not passed taken from global config
1331
+ #
1332
+ # @return [Boolean]
1333
+ def self.verify_notification_signature(body, timestamp, signature, valid_for = 7200, signature_algorithm = nil, options = {})
1334
+ api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise("Must supply api_secret")
1335
+ raise("Body should be of String type") unless body.is_a?(String)
1336
+ # verify that signature is valid for the given timestamp
1337
+ return false if timestamp < (Time.now - valid_for).to_i
1338
+
1339
+ payload_hash = hash("#{body}#{timestamp}#{api_secret}", signature_algorithm, :hexdigest)
1340
+
1341
+ signature == payload_hash
1342
+ end
1343
+
1227
1344
  # Computes hash from input string using specified algorithm.
1228
1345
  #
1229
1346
  # @param [String] input String which to compute hash from
1230
- # @param [String|nil] signature_algorithm Algorithm to use for computing hash
1347
+ # @param [Symbol|nil] signature_algorithm Algorithm to use for computing hash
1231
1348
  # @param [Symbol] hash_method Hash method applied to a signature algorithm (:digest or :hexdigest)
1232
1349
  #
1233
1350
  # @return [String] Computed hash value
@@ -1,4 +1,4 @@
1
1
  # Copyright Cloudinary
2
2
  module Cloudinary
3
- VERSION = "1.20.0"
3
+ VERSION = "1.21.0"
4
4
  end
@@ -4302,12 +4302,20 @@ var slice = [].slice,
4302
4302
  switch (false) {
4303
4303
  case !/w_auto:breakpoints/.test(dataSrc):
4304
4304
  requiredWidth = maxWidth(containerWidth, tag);
4305
- dataSrc = dataSrc.replace(/w_auto:breakpoints([_0-9]*)(:[0-9]+)?/, "w_auto:breakpoints$1:" + requiredWidth);
4305
+ if (requiredWidth) {
4306
+ dataSrc = dataSrc.replace(/w_auto:breakpoints([_0-9]*)(:[0-9]+)?/, "w_auto:breakpoints$1:" + requiredWidth);
4307
+ } else {
4308
+ setUrl = false;
4309
+ }
4306
4310
  break;
4307
4311
  case !(match = /w_auto(:(\d+))?/.exec(dataSrc)):
4308
4312
  requiredWidth = applyBreakpoints.call(this, tag, containerWidth, match[2], options);
4309
4313
  requiredWidth = maxWidth(requiredWidth, tag);
4310
- dataSrc = dataSrc.replace(/w_auto[^,\/]*/g, "w_" + requiredWidth);
4314
+ if (requiredWidth) {
4315
+ dataSrc = dataSrc.replace(/w_auto[^,\/]*/g, "w_" + requiredWidth);
4316
+ } else {
4317
+ setUrl = false;
4318
+ }
4311
4319
  }
4312
4320
  Util.removeAttribute(tag, 'width');
4313
4321
  if (!options.responsive_preserve_height) {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudinary
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.20.0
4
+ version: 1.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nadav Soferman
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2021-03-26 00:00:00.000000000 Z
13
+ date: 2021-08-23 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: aws_cf_signer