cloudinary 1.9.1 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
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