cloudinary 1.20.0 → 1.21.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 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