cloudinary 1.25.0 → 1.27.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: 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