cloudinary 1.26.0 → 1.28.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: 9bb33366a739a067a66cbcaf66534e48663e726d57f4d4b3657c44ec43f092e7
4
- data.tar.gz: 0dd05a5beabf06fc8d1914c7375fa7a6d37fce532edca0021919e13d9b28d318
3
+ metadata.gz: 97b754051e806086f5dfb98479292bb84bed48968c242d8c55dfa073a8d90e52
4
+ data.tar.gz: 6a6484d454b0b074fa177b68e90bb62a100c90aa3f4b3ab33dad9acd64d7ec4d
5
5
  SHA512:
6
- metadata.gz: 64cb7b27cd9909da6fbbaaf98777d4f05046098a6adb1572998323fd7517bf30c73095844683c83da59808f5945c93e90f1e2c685e06ff156b682fe92e902878
7
- data.tar.gz: f098206a8c9f6c73609ad2415cfc90f8359c678461d0bb22f5164aba57111fe55129eed6e98767c6242387ee6294bf4139b034848a1ff9b792cf1dba9fa3ef77
6
+ metadata.gz: dd822c6ec7144df3f7ab863d834802c486bc31ef5051cafbb9d331279a0f3092906990c211ccf1802515d53d51f64e71fd1bd73acc8a2f368c22bddd90ea3a38
7
+ data.tar.gz: cc2299facddd71188d08c0f934ad8483bc0c9414364699e3f84a20eee489f0883adca4ba838a9534d417e11580ff194761d0d26cc978079c9e3b2f42cceeaa48
data/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ 1.28.0 / 2023-11-06
2
+ ==================
3
+
4
+ New functionality and features
5
+ ------------------------------
6
+
7
+ * Add support for `image_file` parameter in `visual_search` Admin API
8
+ * Add support for `on_success` upload parameter
9
+
10
+ Other Changes
11
+ -------------
12
+
13
+ * Replace `update_all` to `update_column` in CarrierWave storage
14
+
15
+ 1.27.0 / 2023-07-31
16
+ ==================
17
+
18
+ New functionality and features
19
+ ------------------------------
20
+
21
+ * Add support for `visual_search` Admin API
22
+ * Add support for Search URL
23
+ * Add support for `SearchFolders` API
24
+ * Add support for `media_metadata` API parameter
25
+
1
26
  1.26.0 / 2023-06-01
2
27
  ==================
3
28
 
@@ -207,6 +207,20 @@ 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, :image_file)
220
+ params[:image_file] = Cloudinary::Utils.handle_file_param(params[:image_file]) if params.has_key?(:image_file)
221
+ call_api(:post, uri, params, options)
222
+ end
223
+
210
224
  # Returns the details of the specified asset and all its derived assets.
211
225
  #
212
226
  # Note that if you only need details about the original asset,
@@ -1293,6 +1307,7 @@ class Cloudinary::Api
1293
1307
  :faces,
1294
1308
  :quality_analysis,
1295
1309
  :image_metadata,
1310
+ :media_metadata,
1296
1311
  :phash,
1297
1312
  :pages,
1298
1313
  :cinemagraph_analysis,
@@ -82,14 +82,7 @@ class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
82
82
  end
83
83
 
84
84
  if defined?(ActiveRecord::Base) && uploader.model.is_a?(ActiveRecord::Base)
85
- primary_key = model_class.primary_key.to_sym
86
- if defined?(::ActiveRecord::VERSION::MAJOR) && ::ActiveRecord::VERSION::MAJOR > 2
87
- model_class.where(primary_key=>uploader.model.send(primary_key)).update_all(column=>name)
88
- else
89
- # Removed since active record version 3.0.0
90
- model_class.update_all({column=>name}, {primary_key=>uploader.model.send(primary_key)})
91
- end
92
- uploader.model.send :write_attribute, column, name
85
+ uploader.model.update_column(column, name)
93
86
  elsif defined?(Mongoid::Document) && uploader.model.is_a?(Mongoid::Document)
94
87
  # Mongoid support
95
88
  if Mongoid::VERSION.split(".").first.to_i >= 4
@@ -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
@@ -40,6 +40,7 @@ class Cloudinary::Uploader
40
40
  :eager_notification_url => options[:eager_notification_url],
41
41
  :exif => Cloudinary::Utils.as_safe_bool(options[:exif]),
42
42
  :eval => options[:eval],
43
+ :on_success => options[:on_success],
43
44
  :face_coordinates => Cloudinary::Utils.encode_double_array(options[:face_coordinates]),
44
45
  :faces => Cloudinary::Utils.as_safe_bool(options[:faces]),
45
46
  :folder => options[:folder],
@@ -47,6 +48,7 @@ class Cloudinary::Uploader
47
48
  :filename_override => options[:filename_override],
48
49
  :headers => build_custom_headers(options[:headers]),
49
50
  :image_metadata => Cloudinary::Utils.as_safe_bool(options[:image_metadata]),
51
+ :media_metadata => Cloudinary::Utils.as_safe_bool(options[:media_metadata]),
50
52
  :invalidate => Cloudinary::Utils.as_safe_bool(options[:invalidate]),
51
53
  :moderation => options[:moderation],
52
54
  :notification_url => options[:notification_url],
@@ -62,6 +64,7 @@ class Cloudinary::Uploader
62
64
  :responsive_breakpoints => Cloudinary::Utils.generate_responsive_breakpoints_string(options[:responsive_breakpoints]),
63
65
  :return_delete_token => Cloudinary::Utils.as_safe_bool(options[:return_delete_token]),
64
66
  :similarity_search => options[:similarity_search],
67
+ :visual_search => Cloudinary::Utils.as_safe_bool(options[:visual_search]),
65
68
  :tags => options[:tags] && Cloudinary::Utils.build_array(options[:tags]).join(","),
66
69
  :timestamp => (options[:timestamp] || Time.now.to_i),
67
70
  :transformation => Cloudinary::Utils.generate_transformation_string(options.clone),
@@ -85,16 +88,7 @@ class Cloudinary::Uploader
85
88
  def self.upload(file, options={})
86
89
  call_api("upload", options) do
87
90
  params = build_upload_params(options)
88
- if file.is_a?(Pathname)
89
- params[:file] = File.open(file, "rb")
90
- elsif file.is_a?(StringIO)
91
- file.rewind
92
- params[:file] = Cloudinary::Blob.new(file.read, options)
93
- elsif file.respond_to?(:read) || Cloudinary::Utils.is_remote?(file)
94
- params[:file] = file
95
- else
96
- params[:file] = File.open(file, "rb")
97
- end
91
+ params[:file] = Cloudinary::Utils.handle_file_param(file, options)
98
92
  [params, [:file]]
99
93
  end
100
94
  end
@@ -151,11 +145,7 @@ class Cloudinary::Uploader
151
145
  options[:resource_type] ||= :raw
152
146
  call_api("upload", options) do
153
147
  params = build_upload_params(options)
154
- if file.is_a?(Pathname) || !file.respond_to?(:read)
155
- params[:file] = File.open(file, "rb")
156
- else
157
- params[:file] = file
158
- end
148
+ params[:file] = Cloudinary::Utils.handle_file_param(file, options)
159
149
  [params, [:file]]
160
150
  end
161
151
  end
@@ -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
@@ -1285,6 +1295,27 @@ class Cloudinary::Utils
1285
1295
  }
1286
1296
  end
1287
1297
 
1298
+ # Handles file parameter.
1299
+ #
1300
+ # @param [Pathname, StringIO, File, String, int, _ToPath] file
1301
+ # @return [StringIO, File] A File object.
1302
+ #
1303
+ # @private
1304
+ def self.handle_file_param(file, options = {})
1305
+ if file.is_a?(Pathname)
1306
+ return File.open(file, "rb")
1307
+ elsif file.is_a?(Cloudinary::Blob)
1308
+ return file
1309
+ elsif file.is_a?(StringIO)
1310
+ file.rewind
1311
+ return Cloudinary::Blob.new(file.read, options)
1312
+ elsif file.respond_to?(:read) || Cloudinary::Utils.is_remote?(file)
1313
+ return file
1314
+ end
1315
+
1316
+ File.open(file, "rb")
1317
+ end
1318
+
1288
1319
  # The returned url should allow downloading the backedup asset based on the version and asset id
1289
1320
  #
1290
1321
  # asset and version id are returned with resource(<PUBLIC_ID1>, { versions: true })
@@ -1373,6 +1404,4 @@ class Cloudinary::Utils
1373
1404
  algorithm = ALGORITHM_SIGNATURE[signature_algorithm] || raise("Unsupported algorithm '#{signature_algorithm}'")
1374
1405
  algorithm.public_send(hash_method, input)
1375
1406
  end
1376
-
1377
- private_class_method :hash
1378
1407
  end
@@ -1,4 +1,4 @@
1
1
  # Copyright Cloudinary
2
2
  module Cloudinary
3
- VERSION = "1.26.0"
3
+ VERSION = "1.28.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.26.0
4
+ version: 1.28.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-06-01 00:00:00.000000000 Z
13
+ date: 2023-11-06 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