cloudinary 1.9.1 → 1.10.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,17 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: aa2967c1b17b93efadf7db793ac1110b7bd411f6
4
- data.tar.gz: 79d2beeae5fe07afd1c4aaffb3bc17d442b2f418
2
+ !binary "U0hBMjU2":
3
+ metadata.gz: !binary |-
4
+ ODI0YWQ0Mjg5NGRkYjkxYjRkZTNkOTZhMTgzNzA5YmMyY2E2ZGVhMmZmN2Fj
5
+ NDRkYTNkNjI3ZDM5YjEwZDFkYg==
6
+ data.tar.gz: !binary |-
7
+ MGMyNDU5NTIxY2Q3M2UyMWM2ZjFmYWVkZjUwNGZmYWFmNmE0MWY0MWY2MTgz
8
+ ZTgyNzIyMjA0YjU1NjZhMWM3Yg==
5
9
  SHA512:
6
- metadata.gz: a46e2e84655389ac281378a44212964acd0f43f0ff66b9d7ef6fc0ce5cbf8a6b6467925dca57768174f0246780f844d6f4f08683fac95d46a49d3bee1d3ec18b
7
- data.tar.gz: 873cf3dbb2ad9296c31d0aacfbbacf66b210bad3220c73e27014f7c386295cad91ae1347fd4c4d5c1631d311bc88c8fe899404bc68aa91a6e12f37e7346028f5
10
+ metadata.gz: !binary |-
11
+ YWViMTg1MzFiOWI1ODkzYWYzNWQxMzUyZWUyMTUyNWIwODRiMDI5NWFjNmYy
12
+ YjVlNjc3NjUzN2E3ODgzNWRiNTEwZDVlZWFlYWQ2ODg5NTc3ZTcxNzIyOThl
13
+ YWY3MjlkYzkwNjk0MDY1YjI3ODJjM2RjY2FiMWYyOTMyODU4NzU=
14
+ data.tar.gz: !binary |-
15
+ MTQzOWE0OGZjZTg3MmI1YTJiOWQ2NzI1MmE4ZDQ2YWQxN2NhMThmMjMzNjJk
16
+ ZjUwZjNmYTc4MDA4ODBlM2Y0Y2FjOWNiOTc2NjI1YTg1M2EyOTg1MTNkMjg1
17
+ NTE2ZmQ2ZGEyMjk2YTE1MDM0NWM3ZGIyZDcxZDFjM2ZlNDJhNTk=
data/.gitignore CHANGED
@@ -54,4 +54,5 @@ bower.json
54
54
  # Ignore pow environment settings
55
55
  .powenv
56
56
 
57
+ !lib/
57
58
  cloudinary.yml
@@ -1,4 +1,36 @@
1
1
 
2
+ 1.10.0 / 2018-11-08
3
+ ===================
4
+
5
+ New functionality and features
6
+ ------------------------------
7
+
8
+ * Add the `custom_function` transformation parameter
9
+ * Add Picture and source tags
10
+ * Add `srcset` attribute to image tag
11
+ * Add support for overlays of type fetch
12
+ * Add breakpoints cache
13
+
14
+ Other Changes
15
+ -------------
16
+
17
+ * Add `update_version` script
18
+ * Fix transformations test
19
+ * Replace ruby list notation to support older ruby versions
20
+ * Refactor tests
21
+ * Replace REXML with Nokogiri
22
+ * Un-ignore the lib folder
23
+ * Ignore empty transformations when processing an array of transformations
24
+ * Restore configuration after each test
25
+ * Limit Rack version to fix compatibility issues with ruby 1.9.3
26
+ * Fix context escaping in call_context_api
27
+ * Fix uploadLarge to use X-Unique-Upload-Id
28
+ * Add test cases of OCR for upload and URL generation
29
+ * Add test case of conditional tags
30
+ * Fix expected result in cname with cdn_subdomain test
31
+ * Fix raw conversion test
32
+ * Raise exception when api-secret is missing in signed-url flow
33
+
2
34
  1.9.1 / 2018-03-06
3
35
  ==================
4
36
 
@@ -33,10 +33,11 @@ Gem::Specification.new do |s|
33
33
  elsif RUBY_VERSION >= "1.9"
34
34
  s.add_dependency "rest-client", '< 2.0'
35
35
  s.add_dependency 'json', '~> 1.8'
36
- s.add_development_dependency "actionpack", '< 5.0'
36
+ s.add_development_dependency "actionpack", '<5.0'
37
37
  s.add_development_dependency "simplecov"
38
38
  s.add_development_dependency "nokogiri", "<1.7.0"
39
39
  s.add_development_dependency "rubyzip", '<1.2.1'
40
+ s.add_development_dependency "rack", '<1.6.5'
40
41
  else
41
42
  s.add_dependency "i18n", "<0.7.0"
42
43
  s.add_dependency "rest-client", "<=1.6.8"
@@ -19,9 +19,9 @@ class Cloudinary::Api
19
19
  # This sets the instantiated self as the response Hash
20
20
  update Cloudinary::Api.parse_json_response response
21
21
 
22
- @rate_limit_allowed = response.headers[:x_featureratelimit_limit].to_i
23
- @rate_limit_reset_at = Time.parse(response.headers[:x_featureratelimit_reset])
24
- @rate_limit_remaining = response.headers[:x_featureratelimit_remaining].to_i
22
+ @rate_limit_allowed = response.headers[:x_featureratelimit_limit].to_i if response.headers[:x_featureratelimit_limit]
23
+ @rate_limit_reset_at = Time.parse(response.headers[:x_featureratelimit_reset]) if response.headers[:x_featureratelimit_reset]
24
+ @rate_limit_remaining = response.headers[:x_featureratelimit_remaining].to_i if response.headers[:x_featureratelimit_remaining]
25
25
  end
26
26
  end
27
27
  end
@@ -311,6 +311,18 @@ class Cloudinary::Api
311
311
  update_resources_access_mode(access_mode, :public_ids, public_ids, options)
312
312
  end
313
313
 
314
+ def self.get_breakpoints(public_id, options)
315
+ local_options = options.clone
316
+ base_transformation = Cloudinary::Utils.generate_transformation_string(local_options)
317
+ srcset = local_options[:srcset]
318
+ breakpoints = [:min_width, :max_width, :bytes_step, :max_images].map {|k| srcset[k]}.join('_')
319
+
320
+
321
+ local_options[:transformation] = [base_transformation, width: "auto:breakpoints_#{breakpoints}:json"]
322
+ json_url = Cloudinary::Utils.cloudinary_url public_id, local_options
323
+ call_json_api('GET', json_url, {}, 60, {})
324
+ end
325
+
314
326
  protected
315
327
 
316
328
  def self.call_api(method, uri, params, options)
@@ -330,24 +342,27 @@ class Cloudinary::Api
330
342
  else
331
343
  payload = params.reject { |k, v| v.nil? || v=="" }
332
344
  end
345
+ call_json_api(method, api_url, payload, timeout, headers)
346
+ end
347
+
348
+ def self.call_json_api(method, api_url, payload, timeout, headers)
333
349
  RestClient::Request.execute(:method => method, :url => api_url, :payload => payload, :timeout => timeout, :headers => headers) do
334
350
  |response, request, tmpresult|
335
351
  return Response.new(response) if response.code == 200
336
352
  exception_class = case response.code
337
- when 400 then BadRequest
338
- when 401 then AuthorizationRequired
339
- when 403 then NotAllowed
340
- when 404 then NotFound
341
- when 409 then AlreadyExists
342
- when 420 then RateLimited
343
- when 500 then GeneralError
344
- else raise GeneralError.new("Server returned unexpected status code - #{response.code} - #{response.body}")
345
- end
353
+ when 400 then BadRequest
354
+ when 401 then AuthorizationRequired
355
+ when 403 then NotAllowed
356
+ when 404 then NotFound
357
+ when 409 then AlreadyExists
358
+ when 420 then RateLimited
359
+ when 500 then GeneralError
360
+ else raise GeneralError.new("Server returned unexpected status code - #{response.code} - #{response.body}")
361
+ end
346
362
  json = parse_json_response(response)
347
363
  raise exception_class.new(json["error"]["message"])
348
364
  end
349
365
  end
350
-
351
366
  def self.parse_json_response(response)
352
367
  return Cloudinary::Utils.json_decode(response.body)
353
368
  rescue => e
@@ -0,0 +1,38 @@
1
+ require 'digest'
2
+
3
+ module Cloudinary
4
+ module Cache
5
+
6
+ class << self
7
+ attr_accessor :storage
8
+
9
+ def get(public_id, options)
10
+ if block_given?
11
+ storage.read(generate_cache_key(public_id, options)) {yield}
12
+ else
13
+ storage.read(generate_cache_key(public_id, options))
14
+ end
15
+ end
16
+
17
+ def set(public_id, options, value)
18
+ storage.write(generate_cache_key(public_id, options), value)
19
+ end
20
+
21
+ alias_method :fetch, :get
22
+
23
+ def flush_all
24
+ storage.clear
25
+ end
26
+
27
+ private
28
+
29
+ def generate_cache_key(public_id, options)
30
+ type = options[:type] || "upload"
31
+ resource_type = options[:resource_type] || "image"
32
+ transformation = Cloudinary::Utils.generate_transformation_string options.clone
33
+ format = options[:format]
34
+ Digest::SHA1.hexdigest [public_id, type, resource_type, transformation, format].reject(&:blank?).join('/')
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,31 @@
1
+ module Cloudinary::Cache
2
+ class BreakpointsCache
3
+ attr_accessor :adapter
4
+
5
+ def set(public_id, options, value)
6
+ upload_type, resource_type, transformation, format = options_to_parameters(options)
7
+ @adapter.set(public_id, upload_type, resource_type, transformation, format, value)
8
+
9
+ end
10
+
11
+ def fetch(public_id, options)
12
+ upload_type, resource_type, transformation, format = options_to_parameters(options)
13
+ @adapter.set(public_id, upload_type, resource_type, transformation, format, &Proc.new)
14
+
15
+ end
16
+
17
+ def get(public_id, options)
18
+ upload_type, resource_type, transformation, format = options_to_parameters(options)
19
+ @adapter.get(public_id, upload_type, resource_type, transformation, format)
20
+ end
21
+
22
+ def options_to_parameters(options)
23
+ options = Cloudinary::Utils.symbolize_keys options
24
+ transformation = Cloudinary::Utils.generate_transformation_string(options)
25
+ upload_type = options[:type] || 'upload'
26
+ resource_type = options[:resource_type] || 'image'
27
+ format = options[:format] || ""
28
+ [upload_type, resource_type, transformation, format]
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ require 'digest'
2
+ module Cloudinary::Cache
3
+ class KeyValueCacheAdapter < CacheAdapter
4
+ def get(public_id, type, resource_type, transformation, format)
5
+ key = generate_cache_key(public_id, type, resource_type, transformation, format)
6
+ @storage.get(key)
7
+ end
8
+
9
+ def set(public_id, type, resource_type, transformation, format, value)
10
+ key = generate_cache_key(public_id, type, resource_type, transformation, format)
11
+ @storage.set(key, value)
12
+ end
13
+
14
+ def flush_all()
15
+ @storage.flush_all()
16
+ end
17
+
18
+ private
19
+
20
+ def generate_cache_key(public_id, type, resource_type, transformation, format)
21
+ Digest::SHA1.hexdigest [public_id, type, resource_type, transformation, format].reject(&:blank?)
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ module Cloudinary::Cache
2
+ class RailsCacheAdapter < CacheAdapter
3
+
4
+ def flush_all
5
+ end
6
+
7
+ def get(public_id, type, resource_type, transformation, format)
8
+ key = generate_cache_key(public_id, type, resource_type, transformation, format)
9
+ Rails.cache.read(key)
10
+ end
11
+
12
+ def init
13
+ unless defined? Rails
14
+ raise CloudinaryException.new "Rails is required in order to use RailsCacheAdapter"
15
+ end
16
+ end
17
+
18
+ def set(public_id, type, resource_type, transformation, format, value)
19
+ key = generate_cache_key(public_id, type, resource_type, transformation, format)
20
+ Rails.cache.write(key, value)
21
+ end
22
+
23
+ def fetch(public_id, type, resource_type, transformation, format)
24
+ key = generate_cache_key(public_id, type, resource_type, transformation, format)
25
+ Rails.cache.fetch(key, &Proc.new)
26
+ end
27
+ private
28
+
29
+ def generate_cache_key(public_id, type, resource_type, transformation, format)
30
+ Digest::SHA1.hexdigest [public_id, type, resource_type, transformation, format].reject(&:blank?)
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module Cloudinary::Cache::Storage
2
+ class RailsCacheStorage
3
+
4
+ end
5
+ end
@@ -1,7 +1,11 @@
1
1
  require 'digest/md5'
2
2
  require 'cloudinary/video_helper'
3
+ require 'cloudinary/responsive'
3
4
 
4
5
  module CloudinaryHelper
6
+ include ActionView::Helpers::CaptureHelper
7
+ include Responsive
8
+
5
9
  CL_BLANK = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
6
10
 
7
11
  # Stand-in for Rails image_tag helper that accepts various options for transformations.
@@ -37,13 +41,45 @@ module CloudinaryHelper
37
41
 
38
42
  end
39
43
 
44
+
45
+ def cl_picture_tag(source, options = {}, sources =[])
46
+
47
+ options = options.clone
48
+ content_tag 'picture' do
49
+ sources.map do |source_def|
50
+ source_options = options.clone
51
+ source_options = Cloudinary::Utils.chain_transformation(source_options, source_def[:transformation])
52
+ source_options[:media] = source_def
53
+ cl_source_tag(source, source_options)
54
+ end.push(cl_image_tag(source, options))
55
+ .join('')
56
+ .html_safe
57
+ end
58
+ end
59
+
60
+ def cl_source_tag(source, options)
61
+ srcset_param = options.fetch(:srcset, {}).merge(Cloudinary.config.srcset || {})
62
+ attributes = options.fetch(:attributes, {}).clone
63
+ responsive_attributes = generate_image_responsive_attributes(source, attributes, srcset_param, options)
64
+ attributes = attributes.merge(responsive_attributes)
65
+ unless attributes.has_key? :srcset
66
+ attributes[:srcset] = Cloudinary::Utils.cloudinary_url(source, options)
67
+ end
68
+ media_attr = generate_media_attribute(options[:media])
69
+ attributes[:media] = media_attr unless media_attr.empty?
70
+ tag "source", attributes, true
71
+ end
72
+
73
+
40
74
  def cloudinary_tag(source, options = {})
41
75
  tag_options = options.clone
42
76
  tag_options[:width] = tag_options.delete(:html_width) if tag_options.include?(:html_width)
43
77
  tag_options[:height] = tag_options.delete(:html_height) if tag_options.include?(:html_height)
44
78
  tag_options[:size] = tag_options.delete(:html_size) if tag_options.include?(:html_size)
45
79
  tag_options[:border] = tag_options.delete(:html_border) if tag_options.include?(:html_border)
46
- source = cloudinary_url_internal(source, tag_options)
80
+ srcset_param = Cloudinary::Utils.config_option_consume(tag_options, :srcset, {})
81
+ src = cloudinary_url_internal(source, tag_options)
82
+ attributes = tag_options.delete(:attributes) || {}
47
83
 
48
84
  responsive_placeholder = Cloudinary::Utils.config_option_consume(tag_options, :responsive_placeholder)
49
85
  client_hints = Cloudinary::Utils.config_option_consume(tag_options, :client_hints)
@@ -51,15 +87,21 @@ module CloudinaryHelper
51
87
  hidpi = tag_options.delete(:hidpi)
52
88
  responsive = tag_options.delete(:responsive)
53
89
  if !client_hints && (hidpi || responsive)
54
- tag_options["data-src"] = source
55
- source = nil
90
+ tag_options["data-src"] = src
91
+ src = nil
56
92
  extra_class = responsive ? "cld-responsive" : "cld-hidpi"
57
93
  tag_options[:class] = [tag_options[:class], extra_class].compact.join(" ")
58
94
  responsive_placeholder = CL_BLANK if responsive_placeholder == "blank"
59
95
  tag_options[:src] = responsive_placeholder
60
96
  end
97
+ responsive_attrs = generate_image_responsive_attributes(source, attributes, srcset_param, options)
98
+ unless responsive_attrs.empty?
99
+ tag_options.delete(:width)
100
+ tag_options.delete(:height)
101
+ tag_options.merge! responsive_attrs
102
+ end
61
103
  if block_given?
62
- yield(source,tag_options)
104
+ yield(src,tag_options)
63
105
  else
64
106
  tag('div', tag_options)
65
107
  end
@@ -281,6 +323,7 @@ module CloudinaryHelper
281
323
  end
282
324
 
283
325
  private
326
+
284
327
  def cloudinary_url_internal(source, options = {})
285
328
  options[:ssl_detected] = request.ssl? if defined?(request) && request && request.respond_to?(:ssl?)
286
329
  if defined?(CarrierWave::Uploader::Base) && source.is_a?(CarrierWave::Uploader::Base)
@@ -0,0 +1,111 @@
1
+ module Responsive
2
+ # Calculate breakpoints for the given configuration
3
+ # @private
4
+ def generate_breakpoints(srcset)
5
+ return srcset[:breakpoints] if srcset[:breakpoints].is_a? Array
6
+ min_width, max_width, max_images = [:min_width, :max_width, :max_images].map {|k| srcset[k]}
7
+ unless [min_width, max_width, max_images].all? {|a| a.is_a? Numeric}
8
+ throw 'Either (min_width, max_width, max_images) or breakpoints must be provided to the image srcset attribute'
9
+ end
10
+ if min_width > max_width
11
+ throw 'min_width must be less than max_width'
12
+ end
13
+
14
+ if max_images <= 0
15
+ throw 'max_images must be a positive integer'
16
+ elsif max_images === 1
17
+ min_width = max_width
18
+ end
19
+ step_size = ((max_width - min_width).to_f / [max_images - 1, 1].max).ceil
20
+ current = min_width
21
+ breakpoints = []
22
+ while current < max_width do
23
+ breakpoints.push(current)
24
+ current += step_size
25
+ end
26
+ breakpoints.push(max_width)
27
+ end
28
+
29
+ # Generate the srcset and sizes attributes
30
+ # @private
31
+ def generate_image_responsive_attributes(public_id, attributes = {}, srcset_data = {}, options = {})
32
+ # Create both srcset and sizes here to avoid fetching breakpoints twice
33
+
34
+ responsive_attributes = {}
35
+ generate_srcset = !attributes[:srcset]
36
+
37
+ if srcset_data.empty?
38
+ return responsive_attributes
39
+ elsif srcset_data.is_a? String
40
+ responsive_attributes[:srcset] = srcset_data
41
+ return responsive_attributes
42
+ end
43
+
44
+ generate_sizes = !attributes[:sizes] && srcset_data[:sizes]
45
+
46
+ if generate_srcset || generate_sizes
47
+ breakpoints = get_or_generate_breakpoints(public_id, srcset_data, options)
48
+
49
+ if generate_srcset
50
+ transformation = srcset_data[:transformation]
51
+ srcset_attr = generate_srcset_attribute(public_id, breakpoints, transformation, options)
52
+ responsive_attributes[:srcset] = srcset_attr unless srcset_attr.empty?
53
+ end
54
+
55
+ if generate_sizes
56
+ sizes_attr = generate_sizes_attribute(breakpoints)
57
+ responsive_attributes[:sizes] = sizes_attr unless sizes_attr.empty?
58
+ end
59
+ end
60
+
61
+ responsive_attributes
62
+ end
63
+
64
+ # If cache is enabled, get the breakpoints from the cache. If the values were not found in the cache,
65
+ # or cache is not enabled, generate the values.
66
+ # @private
67
+ def get_or_generate_breakpoints(public_id, srcset, options)
68
+ if srcset[:use_cache]
69
+ Cloudinary::Cache.get(public_id, options) || []
70
+ else
71
+ generate_breakpoints(srcset)
72
+ end
73
+ end
74
+
75
+ # Generate a resource URL scaled to the given width
76
+ # @private
77
+ def generate_scaled_url(public_id, width, transformation={}, options={})
78
+ config_params = Cloudinary::Utils.extract_config_params(options)
79
+ transformation ||= options
80
+ config_params[:raw_transformation] = Cloudinary::Utils.generate_transformation_string(
81
+ [transformation.clone, {:crop => 'scale', :width => width}].reject(&:blank?))
82
+ config_params.delete :width
83
+ config_params.delete :height
84
+ Cloudinary::Utils.cloudinary_url public_id, config_params
85
+ end
86
+
87
+ # Generate srcset attribute value of the HTML img tag
88
+ # @private
89
+ def generate_srcset_attribute(public_id, breakpoints, transformation={}, options={})
90
+ options = options.clone
91
+ Cloudinary::Utils.patch_fetch_format(options)
92
+ breakpoints.map{|width| "#{generate_scaled_url(public_id, width, transformation, options)} #{width}w"}.join(', ').html_safe
93
+ end
94
+
95
+ # Generate media attribute value of the HTML img tag
96
+ # @private
97
+ def generate_media_attribute(options)
98
+ options ||= {}
99
+ [:min_width, :max_width]
100
+ .select {|name| options[name]}
101
+ .map {|name| "(#{name.to_s.tr('_', '-')}: #{options[name]}px)"}
102
+ .join(' and ').html_safe
103
+ end
104
+
105
+
106
+ # Generate the sizes attribute
107
+ # @private
108
+ def generate_sizes_attribute(breakpoints)
109
+ breakpoints.map{|width| "(max-width: #{width}px) #{width}px"}.join(', ')
110
+ end
111
+ end