cloudinary 1.25.0 → 1.27.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: 27739f4540fd5b75d22128cc2bb0d885160c4d00163e658048ef770e72ab2d33
4
- data.tar.gz: e4f02a3ce402b625a1323f6e0cc14d8f55436de953127090491266377d9576e9
3
+ metadata.gz: 46b21a3d7dd2411acaaeb35016f2ff7b648aeae5c1d077870f38e858ce1abedf
4
+ data.tar.gz: 5825bea0988e97e71df1d93fde04739b502230cda987f90415cb46f55df8b6b7
5
5
  SHA512:
6
- metadata.gz: 15e356e735c9c9e7a2cf0df6269e553bff2e4416386cfdff9a3d5edc81d6fbd4213c73d4c6e907e6a42e8d3680dcbf74e4d90ded012d7ed1c8167a2271ab251f
7
- data.tar.gz: 2720b423770f63dbb7369233c12096846fa157ba2f0f4f465da94128bd574bdc2992bfba8a89ee81877e571a26fbd33a86b6150cfccb78923bd9d2267c9f0b08
6
+ metadata.gz: 24c1334398c9c24ce9b0b536e991fb76aa633e1bda93b5e8dcccc1d2ac2f747e4645f52b61ad3b6db7bb9a59a092df602c35b964a05661c151fb6026cd1954c0
7
+ data.tar.gz: f8f3cdbe7b7374532f5dae2f5dca3543697a2fb2398050b4a7874d6527b3e87ffc84970684aefe25c65f255ba9ec89e15470e28658c28287df781e4ee2144799
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ 1.27.0 / 2023-07-31
2
+ ==================
3
+
4
+ New functionality and features
5
+ ------------------------------
6
+
7
+ * Add support for `visual_search` Admin API
8
+ * Add support for Search URL
9
+ * Add support for `SearchFolders` API
10
+ * Add support for `media_metadata` API parameter
11
+
12
+ 1.26.0 / 2023-06-01
13
+ ==================
14
+
15
+ New functionality and features
16
+ ------------------------------
17
+
18
+ * Add support for related assets Admin APIs
19
+ * Add support for expressions in `start_offset` and `end_offset` parameters
20
+ * Add support for Conditional Metadata Rules API
21
+ * Add support for large files in Active Storage
22
+
1
23
  1.25.0 / 2023-01-04
2
24
  ==================
3
25
 
data/cloudinary.gemspec CHANGED
@@ -39,8 +39,12 @@ Gem::Specification.new do |s|
39
39
  else
40
40
  s.add_development_dependency "rake", "<= 12.2.1"
41
41
  end
42
+ if RUBY_VERSION >= "2.7.0"
43
+ s.add_development_dependency "sqlite3"
44
+ else
45
+ s.add_development_dependency "sqlite3", "< 1.6.0"
46
+ end
42
47
 
43
- s.add_development_dependency "sqlite3"
44
48
  s.add_development_dependency "rspec", '>=3.5'
45
49
  s.add_development_dependency "rspec-retry"
46
50
 
@@ -57,6 +61,8 @@ Gem::Specification.new do |s|
57
61
 
58
62
  if RUBY_VERSION <= "2.4.0"
59
63
  s.add_development_dependency "simplecov", "<= 0.17.1" # support testing Ruby 1.9
64
+ s.add_development_dependency 'loofah', '~>2.19.1'
65
+ s.add_development_dependency "rails-html-sanitizer", "<1.5.0"
60
66
  else
61
67
  s.add_development_dependency "simplecov", "> 0.18.0"
62
68
  end
@@ -39,7 +39,7 @@ module ActiveStorage
39
39
  begin
40
40
  extra_headers = checksum.nil? ? {} : {Headers::CONTENT_MD5 => checksum}
41
41
  options = @options.merge(options)
42
- Cloudinary::Uploader.upload(
42
+ Cloudinary::Uploader.upload_large(
43
43
  io,
44
44
  public_id: public_id_internal(key),
45
45
  resource_type: resource_type(io, key),
@@ -207,6 +207,19 @@ class Cloudinary::Api
207
207
  call_api(:get, uri, params, options)
208
208
  end
209
209
 
210
+ # Find images based on their visual content.
211
+ #
212
+ # @param [Hash] options The optional parameters.
213
+ #
214
+ # @return [Cloudinary::Api::Response]
215
+ #
216
+ # @raise [Cloudinary::Api::Error]
217
+ def self.visual_search(options = {})
218
+ uri = "resources/visual_search"
219
+ params = only(options, :image_url, :image_asset_id, :text)
220
+ call_api(:get, uri, params, options)
221
+ end
222
+
210
223
  # Returns the details of the specified asset and all its derived assets.
211
224
  #
212
225
  # Note that if you only need details about the original asset,
@@ -406,6 +419,76 @@ class Cloudinary::Api
406
419
  call_api(:delete, uri, params, options)
407
420
  end
408
421
 
422
+ # Relates an asset to other assets by public IDs.
423
+ #
424
+ # @param [String] public_id The public ID of the asset.
425
+ # @param [String|Array] assets_to_relate The array of up to 10 fully_qualified_public_ids given as
426
+ # resource_type/type/public_id.
427
+ # @param [Hash] options The optional parameters. See the
428
+ # {https://cloudinary.com/documentation/admin_api#add_related_assets Admin API} documentation.
429
+ #
430
+ # @return [Cloudinary::Api::Response]
431
+ #
432
+ # @raise [Cloudinary::Api::Error]
433
+ def self.add_related_assets(public_id, assets_to_relate, options={})
434
+ resource_type = options[:resource_type] || "image"
435
+ type = options[:type] || "upload"
436
+ uri = "resources/related_assets/#{resource_type}/#{type}/#{public_id}"
437
+ params = {:assets_to_relate => Cloudinary::Utils.build_array(assets_to_relate)}
438
+ call_api(:post, uri, params, options)
439
+ end
440
+
441
+ # Relates an asset to other assets by asset IDs.
442
+ #
443
+ # @param [String] asset_id The asset ID of the asset to update.
444
+ # @param [String|Array] assets_to_relate The array of up to 10 asset IDs.
445
+ # @param [Hash] options The optional parameters. See the
446
+ # {https://cloudinary.com/documentation/admin_api#add_related_assets_by_asset_id Admin API} documentation.
447
+ #
448
+ # @return [Cloudinary::Api::Response]
449
+ #
450
+ # @raise [Cloudinary::Api::Error]
451
+ def self.add_related_assets_by_asset_ids(asset_id, assets_to_relate, options={})
452
+ uri = "resources/related_assets/#{asset_id}"
453
+ params = {:assets_to_relate => Cloudinary::Utils.build_array(assets_to_relate)}
454
+ call_api(:post, uri, params, options)
455
+ end
456
+
457
+ # Unrelates an asset from other assets by public IDs.
458
+ #
459
+ # @param [String] public_id The public ID of the asset.
460
+ # @param [String|Array] assets_to_unrelate The array of up to 10 fully_qualified_public_ids given as
461
+ # resource_type/type/public_id.
462
+ # @param [Hash] options The optional parameters. See the
463
+ # {https://cloudinary.com/documentation/admin_api#delete_related_assets Admin API} documentation.
464
+ #
465
+ # @return [Cloudinary::Api::Response]
466
+ #
467
+ # @raise [Cloudinary::Api::Error]
468
+ def self.delete_related_assets(public_id, assets_to_unrelate, options={})
469
+ resource_type = options[:resource_type] || "image"
470
+ type = options[:type] || "upload"
471
+ uri = "resources/related_assets/#{resource_type}/#{type}/#{public_id}"
472
+ params = {:assets_to_unrelate => Cloudinary::Utils.build_array(assets_to_unrelate)}
473
+ call_api(:delete, uri, params, options)
474
+ end
475
+
476
+ # Unrelates an asset from other assets by asset IDs.
477
+ #
478
+ # @param [String] asset_id The asset ID of the asset to update.
479
+ # @param [String|Array] assets_to_unrelate The array of up to 10 asset IDs.
480
+ # @param [Hash] options The optional parameters. See the
481
+ # {https://cloudinary.com/documentation/admin_api#delete_related_assets_by_asset_id Admin API} documentation.
482
+ #
483
+ # @return [Cloudinary::Api::Response]
484
+ #
485
+ # @raise [Cloudinary::Api::Error]
486
+ def self.delete_related_assets_by_asset_ids(asset_id, assets_to_unrelate, options={})
487
+ uri = "resources/related_assets/#{asset_id}"
488
+ params = {:assets_to_unrelate => Cloudinary::Utils.build_array(assets_to_unrelate)}
489
+ call_api(:delete, uri, params, options)
490
+ end
491
+
409
492
  # Lists all the tags currently used for a specified asset type.
410
493
  #
411
494
  # @param [Hash] options The optional parameters. See the
@@ -1077,6 +1160,75 @@ class Cloudinary::Api
1077
1160
  call_metadata_api(:put, uri, params, options)
1078
1161
  end
1079
1162
 
1163
+ # Lists all metadata rules definitions.
1164
+ #
1165
+ # @param [Hash] options The optional parameters.
1166
+ #
1167
+ # @return [Cloudinary::Api::Response]
1168
+ #
1169
+ # @raise [Cloudinary::Api::Error]
1170
+ #
1171
+ # @see https://cloudinary.com/documentation/conditional_metadata_rules_api#get_metadata_rules
1172
+ def self.list_metadata_rules(options = {})
1173
+ call_metadata_rules_api(:get, [], {}, options)
1174
+ end
1175
+
1176
+
1177
+ # Creates a new metadata rule definition.
1178
+ #
1179
+ # @param [Hash] rule The rule to add.
1180
+ # @param [Hash] options The optional parameters.
1181
+ #
1182
+ # @return [Cloudinary::Api::Response]
1183
+ #
1184
+ # @raise [Cloudinary::Api::Error]
1185
+ #
1186
+ # @see https://cloudinary.com/documentation/conditional_metadata_rules_api#create_a_metadata_rule
1187
+ def self.add_metadata_rule(rule, options = {})
1188
+ params = only(rule, :metadata_field_id, :condition, :result, :name)
1189
+
1190
+ call_metadata_rules_api(:post, [], params, options)
1191
+ end
1192
+
1193
+ # Updates a metadata rule by external ID.
1194
+ #
1195
+ # Updates an existing metadata rule definition. Expects a JSON object which defines the updated rule.
1196
+ #
1197
+ # @param [String] external_id The ID of the rule to update.
1198
+ # @param [Hash] rule The rule definition.
1199
+ # @param [Hash] options The optional parameters.
1200
+ #
1201
+ # @return [Cloudinary::Api::Response]
1202
+ #
1203
+ # @raise [Cloudinary::Api::Error]
1204
+ #
1205
+ # @see https://cloudinary.com/documentation/conditional_metadata_rules_api#update_a_metadata_rule_by_id
1206
+ def self.update_metadata_rule(external_id, rule, options = {})
1207
+ uri = [external_id]
1208
+ params = only(rule, :metadata_field_id, :condition, :result, :name, :state)
1209
+
1210
+ call_metadata_rules_api(:put, uri, params, options)
1211
+ end
1212
+
1213
+ # Deletes a metadata rule definition by external ID.
1214
+ #
1215
+ # The rule should no longer be considered a valid candidate for all other endpoints
1216
+ # (it will not show up in the list of rules, etc).
1217
+ #
1218
+ # @param [String] external_id The ID of the rule to delete.
1219
+ # @param [Hash] options The optional parameters.
1220
+ #
1221
+ # @return [Cloudinary::Api::Response]
1222
+ #
1223
+ # @raise [Cloudinary::Api::Error]
1224
+ #
1225
+ # @see https://cloudinary.com/documentation/conditional_metadata_rules_api#delete_a_metadata_rule_by_id
1226
+ def self.delete_metadata_rule(external_id, options = {})
1227
+ uri = [external_id]
1228
+
1229
+ call_metadata_rules_api(:delete, uri, {}, options)
1230
+ end
1231
+
1080
1232
  protected
1081
1233
 
1082
1234
  # Execute a call api for input params.
@@ -1128,6 +1280,22 @@ class Cloudinary::Api
1128
1280
  call_api(method, uri, params, options)
1129
1281
  end
1130
1282
 
1283
+ # Protected function that assists with performing an API call to the metadata_rules part of the Admin API.
1284
+ #
1285
+ # @protected
1286
+ # @param [Symbol] method The HTTP method. Valid methods: get, post, put, delete
1287
+ # @param [Array] uri REST endpoint of the API (without 'metadata_rules')
1288
+ # @param [Hash] params Query/body parameters passed to the method
1289
+ # @param [Hash] options Additional options. Can be an override of the configuration, headers, etc.
1290
+ # @return [Cloudinary::Api::Response] Returned response from Cloudinary
1291
+ # @raise [Cloudinary::Api::Error]
1292
+ def self.call_metadata_rules_api(method, uri, params, options)
1293
+ options[:content_type] = :json
1294
+ uri = ["metadata_rules", uri].reject(&:empty?).join("/")
1295
+
1296
+ call_api(method, uri, params, options)
1297
+ end
1298
+
1131
1299
  # Prepares optional parameters for asset/assetByAssetId API calls.
1132
1300
  # @param [Hash] options Additional options
1133
1301
  # @return [Object] Optional parameters
@@ -1138,6 +1306,7 @@ class Cloudinary::Api
1138
1306
  :faces,
1139
1307
  :quality_analysis,
1140
1308
  :image_metadata,
1309
+ :media_metadata,
1141
1310
  :phash,
1142
1311
  :pages,
1143
1312
  :cinemagraph_analysis,
@@ -1,15 +1,23 @@
1
1
  class Cloudinary::Search
2
+ ENDPOINT = 'resources'
3
+
2
4
  SORT_BY = :sort_by
3
5
  AGGREGATE = :aggregate
4
6
  WITH_FIELD = :with_field
5
7
  KEYS_WITH_UNIQUE_VALUES = [SORT_BY, AGGREGATE, WITH_FIELD].freeze
6
8
 
9
+ TTL = 300 # Used for search URLs
10
+
7
11
  def initialize
8
12
  @query_hash = {
9
13
  SORT_BY => {},
10
14
  AGGREGATE => {},
11
15
  WITH_FIELD => {}
12
16
  }
17
+
18
+ @endpoint = self.class::ENDPOINT
19
+
20
+ @ttl = self.class::TTL
13
21
  end
14
22
 
15
23
  ## implicitly generate an instance delegate the method
@@ -69,11 +77,21 @@ class Cloudinary::Search
69
77
  self
70
78
  end
71
79
 
80
+ # Sets the time to live of the search URL.
81
+ #
82
+ # @param [Object] ttl The time to live in seconds.
83
+ #
84
+ # @return [Cloudinary::Search]
85
+ def ttl(ttl)
86
+ @ttl = ttl
87
+ self
88
+ end
89
+
72
90
  # Returns the query as an hash.
73
91
  #
74
92
  # @return [Hash]
75
93
  def to_h
76
- @query_hash.each_with_object({}) do |(key, value), query|
94
+ @query_hash.sort.each_with_object({}) do |(key, value), query|
77
95
  next if value.nil? || ((value.is_a?(Array) || value.is_a?(Hash)) && value.blank?)
78
96
 
79
97
  query[key] = KEYS_WITH_UNIQUE_VALUES.include?(key) ? value.values : value
@@ -82,7 +100,44 @@ class Cloudinary::Search
82
100
 
83
101
  def execute(options = {})
84
102
  options[:content_type] = :json
85
- uri = 'resources/search'
103
+ uri = "#{@endpoint}/search"
86
104
  Cloudinary::Api.call_api(:post, uri, to_h, options)
87
105
  end
106
+
107
+ # Creates a signed Search URL that can be used on the client side.
108
+ #
109
+ # @param [Integer] ttl The time to live in seconds.
110
+ # @param [String] next_cursor Starting position.
111
+ # @param [Hash] options Additional url delivery options.
112
+ #
113
+ # @return [String] The resulting Search URL
114
+ def to_url(ttl = nil, next_cursor = nil, options = {})
115
+ api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise(CloudinaryException, "Must supply api_secret")
116
+
117
+ ttl = ttl || @ttl
118
+ query = self.to_h
119
+
120
+ _next_cursor = query.delete(:next_cursor)
121
+ next_cursor = _next_cursor if next_cursor.nil?
122
+
123
+ b64query = Base64.urlsafe_encode64(JSON.generate(query))
124
+
125
+ prefix = Cloudinary::Utils.build_distribution_domain(options)
126
+
127
+ signature = Cloudinary::Utils.hash("#{ttl}#{b64query}#{api_secret}", :sha256, :hexdigest)
128
+
129
+ next_cursor = "/#{next_cursor}" if !next_cursor.nil? && !next_cursor.empty?
130
+
131
+ "#{prefix}/search/#{signature}/#{ttl}/#{b64query}#{next_cursor}"
132
+ end
133
+
134
+ # Sets the API endpoint.
135
+ #
136
+ # @param [String] endpoint the endpoint to set.
137
+ #
138
+ # @return [Cloudinary::Search]
139
+ def endpoint(endpoint)
140
+ @endpoint = endpoint
141
+ self
142
+ end
88
143
  end
@@ -0,0 +1,5 @@
1
+ # The Cloudinary API folders search method allows you fine control on filtering and retrieving information on all the
2
+ # folders in your cloud with the help of query expressions in a Lucene-like query language.
3
+ class Cloudinary::SearchFolders < Cloudinary::Search
4
+ ENDPOINT = 'folders'
5
+ end
@@ -47,6 +47,7 @@ class Cloudinary::Uploader
47
47
  :filename_override => options[:filename_override],
48
48
  :headers => build_custom_headers(options[:headers]),
49
49
  :image_metadata => Cloudinary::Utils.as_safe_bool(options[:image_metadata]),
50
+ :media_metadata => Cloudinary::Utils.as_safe_bool(options[:media_metadata]),
50
51
  :invalidate => Cloudinary::Utils.as_safe_bool(options[:invalidate]),
51
52
  :moderation => options[:moderation],
52
53
  :notification_url => options[:notification_url],
@@ -62,6 +63,7 @@ class Cloudinary::Uploader
62
63
  :responsive_breakpoints => Cloudinary::Utils.generate_responsive_breakpoints_string(options[:responsive_breakpoints]),
63
64
  :return_delete_token => Cloudinary::Utils.as_safe_bool(options[:return_delete_token]),
64
65
  :similarity_search => options[:similarity_search],
66
+ :visual_search => Cloudinary::Utils.as_safe_bool(options[:visual_search]),
65
67
  :tags => options[:tags] && Cloudinary::Utils.build_array(options[:tags]).join(","),
66
68
  :timestamp => (options[:timestamp] || Time.now.to_i),
67
69
  :transformation => Cloudinary::Utils.generate_transformation_string(options.clone),
@@ -108,26 +110,38 @@ class Cloudinary::Uploader
108
110
  public_id = public_id_or_options
109
111
  options = old_options
110
112
  end
113
+
114
+ options.merge(:public_id => public_id)
115
+
111
116
  if Cloudinary::Utils.is_remote?(file)
112
- return upload(file, options.merge(:public_id => public_id))
113
- elsif file.is_a?(Pathname) || !file.respond_to?(:read)
117
+ return upload(file, options)
118
+ end
119
+
120
+ if file.is_a?(Pathname) || !file.respond_to?(:read)
114
121
  filename = file
115
122
  file = File.open(file, "rb")
116
123
  else
117
124
  filename = "cloudinaryfile"
118
125
  end
119
126
 
127
+ chunk_size = options[:chunk_size] || 20_000_000
128
+
129
+ if file.size < chunk_size
130
+ return upload(file, options)
131
+ end
132
+
120
133
  filename = options[:filename] if options[:filename]
121
134
 
122
135
  unique_upload_id = Cloudinary::Utils.random_public_id
123
136
  upload = nil
124
137
  index = 0
125
- chunk_size = options[:chunk_size] || 20_000_000
138
+
126
139
  until file.eof?
127
140
  buffer = file.read(chunk_size)
128
141
  current_loc = index*chunk_size
129
142
  range = "bytes #{current_loc}-#{current_loc+buffer.size - 1}/#{file.size}"
130
- upload = upload_large_part(Cloudinary::Blob.new(buffer, :original_filename => filename), options.merge(:public_id => public_id, :unique_upload_id => unique_upload_id, :content_range => range))
143
+ upload = upload_large_part(Cloudinary::Blob.new(buffer, :original_filename => filename),
144
+ options.merge(:unique_upload_id => unique_upload_id, :content_range => range))
131
145
  index += 1
132
146
  end
133
147
  upload
@@ -524,18 +524,10 @@ class Cloudinary::Utils
524
524
  version = options.delete(:version)
525
525
  force_version = config_option_consume(options, :force_version, true)
526
526
  format = options.delete(:format)
527
- cloud_name = config_option_consume(options, :cloud_name) || raise(CloudinaryException, "Must supply cloud_name in tag or in configuration")
528
527
 
529
- secure = options.delete(:secure)
530
- ssl_detected = options.delete(:ssl_detected)
531
- secure = ssl_detected || Cloudinary.config.secure if secure.nil?
532
- private_cdn = config_option_consume(options, :private_cdn)
533
- secure_distribution = config_option_consume(options, :secure_distribution)
534
- cname = config_option_consume(options, :cname)
535
528
  shorten = config_option_consume(options, :shorten)
536
529
  force_remote = options.delete(:force_remote)
537
- cdn_subdomain = config_option_consume(options, :cdn_subdomain)
538
- secure_cdn_subdomain = config_option_consume(options, :secure_cdn_subdomain)
530
+
539
531
  sign_url = config_option_consume(options, :sign_url)
540
532
  secret = config_option_consume(options, :api_secret)
541
533
  sign_version = config_option_consume(options, :sign_version) # Deprecated behavior
@@ -558,7 +550,7 @@ class Cloudinary::Utils
558
550
  type = type.to_s unless type.nil?
559
551
  resource_type ||= "image"
560
552
  source = source.to_s
561
- if !force_remote
553
+ unless force_remote
562
554
  static_support = Cloudinary.config.static_file_support || Cloudinary.config.static_image_support
563
555
  return original_source if !static_support && type == "asset"
564
556
  return original_source if (type.nil? || type == "asset") && source.match(%r(^https?:/)i)
@@ -594,7 +586,9 @@ class Cloudinary::Utils
594
586
  signature = "s--#{signature[0, long_url_signature ? LONG_URL_SIGNATURE_LENGTH : SHORT_URL_SIGNATURE_LENGTH ]}--"
595
587
  end
596
588
 
597
- prefix = unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
589
+ options[:source] = source
590
+ prefix = build_distribution_domain(options)
591
+
598
592
  source = [prefix, resource_type, type, signature, transformation, version, source].reject(&:blank?).join("/")
599
593
  if sign_url && auth_token && !auth_token.empty?
600
594
  auth_token[:url] = URI.parse(source).path
@@ -707,6 +701,22 @@ class Cloudinary::Utils
707
701
  prefix
708
702
  end
709
703
 
704
+ def self.build_distribution_domain(options = {})
705
+ cloud_name = config_option_consume(options, :cloud_name) || raise(CloudinaryException, "Must supply cloud_name in tag or in configuration")
706
+
707
+ source = options.delete(:source)
708
+ secure = options.delete(:secure)
709
+ ssl_detected = options.delete(:ssl_detected)
710
+ secure = ssl_detected || Cloudinary.config.secure if secure.nil?
711
+ private_cdn = config_option_consume(options, :private_cdn)
712
+ secure_distribution = config_option_consume(options, :secure_distribution)
713
+ cname = config_option_consume(options, :cname)
714
+ cdn_subdomain = config_option_consume(options, :cdn_subdomain)
715
+ secure_cdn_subdomain = config_option_consume(options, :secure_cdn_subdomain)
716
+
717
+ unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
718
+ end
719
+
710
720
  # Creates a base URL for the cloudinary api
711
721
  #
712
722
  # @param [Object] path Resource name
@@ -1175,11 +1185,13 @@ class Cloudinary::Utils
1175
1185
  # @private
1176
1186
  def self.norm_range_value(value) # :nodoc:
1177
1187
  offset = /^#{offset_any_pattern}$/.match( value.to_s)
1188
+
1178
1189
  if offset
1179
- modifier = offset[5].present? ? 'p' : ''
1180
- value = "#{offset[1]}#{modifier}"
1190
+ modifier = offset[5].present? ? 'p' : ''
1191
+ "#{offset[1]}#{modifier}"
1192
+ else
1193
+ normalize_expression(value)
1181
1194
  end
1182
- value
1183
1195
  end
1184
1196
  private_class_method :norm_range_value
1185
1197
 
@@ -1371,6 +1383,4 @@ class Cloudinary::Utils
1371
1383
  algorithm = ALGORITHM_SIGNATURE[signature_algorithm] || raise("Unsupported algorithm '#{signature_algorithm}'")
1372
1384
  algorithm.public_send(hash_method, input)
1373
1385
  end
1374
-
1375
- private_class_method :hash
1376
1386
  end
@@ -1,4 +1,4 @@
1
1
  # Copyright Cloudinary
2
2
  module Cloudinary
3
- VERSION = "1.25.0"
3
+ VERSION = "1.27.0"
4
4
  end
data/lib/cloudinary.rb CHANGED
@@ -28,6 +28,7 @@ module Cloudinary
28
28
  autoload :Static, "cloudinary/static"
29
29
  autoload :CarrierWave, "cloudinary/carrier_wave"
30
30
  autoload :Search, "cloudinary/search"
31
+ autoload :SearchFolders, "cloudinary/search_folders"
31
32
 
32
33
  CF_SHARED_CDN = "d3jpl91pxevbkh.cloudfront.net"
33
34
  AKAMAI_SHARED_CDN = "res.cloudinary.com"
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.25.0
4
+ version: 1.27.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: 2023-01-04 00:00:00.000000000 Z
13
+ date: 2023-07-31 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: aws_cf_signer
@@ -235,6 +235,7 @@ files:
235
235
  - lib/cloudinary/railtie.rb
236
236
  - lib/cloudinary/responsive.rb
237
237
  - lib/cloudinary/search.rb
238
+ - lib/cloudinary/search_folders.rb
238
239
  - lib/cloudinary/static.rb
239
240
  - lib/cloudinary/uploader.rb
240
241
  - lib/cloudinary/utils.rb
@@ -276,8 +277,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
276
277
  - !ruby/object:Gem::Version
277
278
  version: '0'
278
279
  requirements: []
279
- rubyforge_project: cloudinary
280
- rubygems_version: 2.7.6
280
+ rubygems_version: 3.4.12
281
281
  signing_key:
282
282
  specification_version: 4
283
283
  summary: Client library for easily using the Cloudinary service