cloudinary 1.0.85 → 1.1.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 +5 -13
- data/.gitignore +5 -5
- data/CHANGELOG.md +13 -0
- data/README.md +8 -5
- data/Rakefile +1 -1
- data/cloudinary.gemspec +1 -1
- data/lib/cloudinary/carrier_wave.rb +18 -4
- data/lib/cloudinary/carrier_wave/process.rb +1 -1
- data/lib/cloudinary/carrier_wave/storage.rb +11 -17
- data/lib/cloudinary/utils.rb +554 -551
- data/lib/cloudinary/version.rb +1 -1
- data/vendor/assets/javascripts/cloudinary/jquery.cloudinary.js +149 -46
- metadata +21 -21
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
MjhkM2NkNzk1YTExOWQ0ZjdjMTJlNWIzOTE2YzZhMjE2NWZhNDM5OQ==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ed522de3b73b3847f23d46ee3ec165e7cdf20037
|
4
|
+
data.tar.gz: 2bda2d2079cf979dd6622e23ec4187dbcf56be34
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
OWIyZmY1MDI4NzlhODIzZmFmNWJkZDQyYTAyOWZmNzc0MzFjMWRlZDUyZGY5
|
11
|
-
ZmYwYjQ5ODFiODM0Y2VlNjViN2JjOTJmODI2ODBiNzk4NWViMjk=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
NTE4NDkyMGU0ZDk3MDNiNjA0OWMwZDZjNTU1OThjNjI3OTQ3Zjg1ZTkxM2U2
|
14
|
-
MGFjODMxOTBiNDgyYmMyYTdmZmM1Y2FkMWM0ZGZmOWI2MjM3NjUzY2E4ZDhk
|
15
|
-
YTc5OGY1OWIzNTIzYjcwYzRmNmMzZThiZjcwMTI4MTQyOTU3NmY=
|
6
|
+
metadata.gz: d75f033f6b0a02263f626b9f2f2c57de96fcf773eb455079fb647bf50c447f8280138eeb93298ba67a4d22f34c42f1f5ca29af1eb108dbe303fc19efd93b90ac
|
7
|
+
data.tar.gz: c815b8e4af15df396cc425ab505608c577c7f2616d76101041bf9c41f1561aea69fd7676a14294ef7df418d86c2375e044db6c7e6630b4d95b48c5f300e8fa30
|
data/.gitignore
CHANGED
@@ -3,9 +3,9 @@
|
|
3
3
|
/.config
|
4
4
|
capybara-*.html
|
5
5
|
.rspec
|
6
|
-
log
|
7
|
-
db/*.sqlite3
|
8
|
-
db/*.sqlite3-journal
|
6
|
+
/log
|
7
|
+
/db/*.sqlite3
|
8
|
+
/db/*.sqlite3-journal
|
9
9
|
/public/system
|
10
10
|
/coverage/
|
11
11
|
/InstalledFiles
|
@@ -14,7 +14,7 @@ db/*.sqlite3-journal
|
|
14
14
|
/test/tmp/
|
15
15
|
/spec/tmp
|
16
16
|
/test/version_tmp/
|
17
|
-
tmp
|
17
|
+
/tmp
|
18
18
|
**.orig
|
19
19
|
rerun.txt
|
20
20
|
pickle-email-*.html
|
@@ -49,7 +49,7 @@ Gemfile.lock
|
|
49
49
|
.rvmrc
|
50
50
|
|
51
51
|
# if using bower-rails ignore default bower_components path bower.json files
|
52
|
-
vendor/assets/bower_components
|
52
|
+
/vendor/assets/bower_components
|
53
53
|
*.bowerrc
|
54
54
|
bower.json
|
55
55
|
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
# Version 1.1.0 - 2015-04-21
|
2
|
+
* Pull request #136 - Update `process.rb` to ensure name value is an array.
|
3
|
+
* CarrierWave
|
4
|
+
* Store `resource_type` and `type` (aka `storage_type`) in carrierwave column for better support of non-image resource types and non-upload types
|
5
|
+
* only pass format to Cloudinary when explicitly requested
|
6
|
+
* Support disabling new extended identifier format
|
7
|
+
* Use upload endpoint instead of upload_chunked
|
8
|
+
* Remove `symoblize_keys` monkey patching
|
9
|
+
* Update Rspec dependency.
|
10
|
+
* Fix markup in the readme file.
|
11
|
+
* Add `.gitignore` to each sample project. Add files required for testing.
|
12
|
+
* Fix changelog format (missing newline)
|
13
|
+
|
1
14
|
# Version 1.0.85 - 2015-04-08
|
2
15
|
* Remove symoblize_keys intrusive implementation.
|
3
16
|
* Use upload API endpoint instead of upload_chunked.
|
data/README.md
CHANGED
@@ -12,13 +12,13 @@ Cloudinary provides URL and HTTP based APIs that can be easily integrated with a
|
|
12
12
|
For Ruby on Rails, Cloudinary provides a GEM for simplifying the integration even further.
|
13
13
|
|
14
14
|
## Getting started guide
|
15
|
-
 **Take a look at our [Getting started guide of Ruby on Rails](http://cloudinary.com/documentation/rails_integration#getting_started_guide)**.
|
15
|
+
 **Take a look at our [Getting started guide of Ruby on Rails](http://cloudinary.com/documentation/rails_integration#getting_started_guide)**.
|
16
16
|
|
17
17
|
## Setup ######################################################################
|
18
18
|
|
19
19
|
To install the Cloudinary Ruby GEM, run:
|
20
20
|
|
21
|
-
|
21
|
+
$ gem install cloudinary
|
22
22
|
|
23
23
|
If you use Rails 3.x or higher, edit your `Gemfile`, add the following line and run `bundle install`
|
24
24
|
|
@@ -117,7 +117,7 @@ Same goes for Twitter:
|
|
117
117
|
|
118
118
|
twitter_name_profile_image_tag("billclinton.jpg")
|
119
119
|
|
120
|
-
 **See [our documentation](http://cloudinary.com/documentation/rails_image_manipulation) for more information about displaying and transforming images in Rails**.
|
120
|
+
 **See [our documentation](http://cloudinary.com/documentation/rails_image_manipulation) for more information about displaying and transforming images in Rails**.
|
121
121
|
|
122
122
|
|
123
123
|
|
@@ -144,11 +144,14 @@ You can also specify your own public ID:
|
|
144
144
|
http://res.cloudinary.com/demo/image/upload/sample_remote.jpg
|
145
145
|
|
146
146
|
|
147
|
-
 **See [our documentation](http://cloudinary.com/documentation/rails_image_upload) for plenty more options of uploading to the cloud from your Ruby code or directly from the browser**.
|
147
|
+
 **See [our documentation](http://cloudinary.com/documentation/rails_image_upload) for plenty more options of uploading to the cloud from your Ruby code or directly from the browser**.
|
148
148
|
|
149
149
|
|
150
150
|
### CarrierWave Integration
|
151
151
|
|
152
|
+
**Note:** Starting from version 1.1.0 the CarrierWave database format has changed to include the resource type and storage type. The new functionality
|
153
|
+
is backward compatible with the previous format. To use the old format override `use_extended_identifier?` in the Uploader and return `false`.
|
154
|
+
|
152
155
|
Cloudinary's Ruby GEM includes an optional plugin for [CarrierWave](https://github.com/jnicklas/carrierwave). If you already use CarrierWave, simply include `Cloudinary::CarrierWave` to switch to cloud storage and image processing in the cloud.
|
153
156
|
|
154
157
|
class PictureUploader < CarrierWave::Uploader::Base
|
@@ -156,7 +159,7 @@ Cloudinary's Ruby GEM includes an optional plugin for [CarrierWave](https://gith
|
|
156
159
|
...
|
157
160
|
end
|
158
161
|
|
159
|
-
 **For more details on CarrierWave integration see [our documentation](http://cloudinary.com/documentation/rails_carrierwave)**.
|
162
|
+
 **For more details on CarrierWave integration see [our documentation](http://cloudinary.com/documentation/rails_carrierwave)**.
|
160
163
|
|
161
164
|
We also published an interesting blog post about [Ruby on Rails image uploads with CarrierWave and Cloudinary](http://cloudinary.com/blog/ruby_on_rails_image_uploads_with_carrierwave_and_cloudinary).
|
162
165
|
|
data/Rakefile
CHANGED
@@ -7,7 +7,7 @@ task :default => :spec
|
|
7
7
|
Bundler::GemHelper.install_tasks
|
8
8
|
|
9
9
|
task :fetch_assets do
|
10
|
-
system "/bin/rm -rf vendor/assets; mkdir -p vendor/assets; cd vendor/assets; curl -L https://github.com/cloudinary/cloudinary_js/
|
10
|
+
system "/bin/rm -rf vendor/assets; mkdir -p vendor/assets; cd vendor/assets; curl -L https://github.com/cloudinary/cloudinary_js/tarball/master | tar zxvf - --strip=1"
|
11
11
|
system "mkdir -p vendor/assets/javascripts; mv vendor/assets/js vendor/assets/javascripts/cloudinary"
|
12
12
|
File.open("vendor/assets/javascripts/cloudinary/index.js", "w") do
|
13
13
|
|f|
|
data/cloudinary.gemspec
CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.require_paths = ["lib"]
|
22
22
|
|
23
23
|
s.add_dependency "aws_cf_signer"
|
24
|
-
s.add_development_dependency "rspec", '>=2
|
24
|
+
s.add_development_dependency "rspec", '>=3.2'
|
25
25
|
s.add_development_dependency "rspec-rails"
|
26
26
|
|
27
27
|
if RUBY_VERSION > "1.9"
|
@@ -135,6 +135,11 @@ module Cloudinary::CarrierWave
|
|
135
135
|
def auto_rename_preloaded?
|
136
136
|
true
|
137
137
|
end
|
138
|
+
|
139
|
+
# Use extended identifier format that includes resource type and storage type.
|
140
|
+
def use_extended_identifier?
|
141
|
+
true
|
142
|
+
end
|
138
143
|
|
139
144
|
class CloudinaryFile
|
140
145
|
attr_reader :identifier, :public_id, :filename, :format, :version, :storage_type, :resource_type
|
@@ -142,7 +147,12 @@ module Cloudinary::CarrierWave
|
|
142
147
|
@uploader = uploader
|
143
148
|
@identifier = identifier
|
144
149
|
|
145
|
-
if @identifier.match(%r(^v([0-9]+)
|
150
|
+
if @identifier.match(%r(^(image|raw|video)/(upload|private|authenticated)(?:/v([0-9]+))?/(.*)))
|
151
|
+
@resource_type = $1
|
152
|
+
@storage_type = $2
|
153
|
+
@version = $3.presence
|
154
|
+
@filename = $4
|
155
|
+
elsif @identifier.match(%r(^v([0-9]+)/(.*)))
|
146
156
|
@version = $1
|
147
157
|
@filename = $2
|
148
158
|
else
|
@@ -150,8 +160,8 @@ module Cloudinary::CarrierWave
|
|
150
160
|
@version = nil
|
151
161
|
end
|
152
162
|
|
153
|
-
@storage_type
|
154
|
-
@resource_type
|
163
|
+
@storage_type ||= uploader.class.storage_type
|
164
|
+
@resource_type ||= Cloudinary::Utils.resource_type_for_format(@filename)
|
155
165
|
@public_id, @format = Cloudinary::PreloadedFile.split_format(@filename)
|
156
166
|
end
|
157
167
|
|
@@ -180,8 +190,12 @@ module Cloudinary::CarrierWave
|
|
180
190
|
"png"
|
181
191
|
end
|
182
192
|
|
193
|
+
def storage_type
|
194
|
+
@file.respond_to?(:storage_type) ? @file.storage_type : self.class.storage_type
|
195
|
+
end
|
196
|
+
|
183
197
|
def resource_type
|
184
|
-
Cloudinary::Utils.resource_type_for_format(requested_format || original_filename || default_format)
|
198
|
+
@file.respond_to?(:resource_type) ? @file.resource_type : Cloudinary::Utils.resource_type_for_format(requested_format || original_filename || default_format)
|
185
199
|
end
|
186
200
|
|
187
201
|
# For the given methods - versions should call the main uploader method
|
@@ -11,11 +11,11 @@ class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
|
|
11
11
|
@stored_version = file.version
|
12
12
|
uploader.rename(nil, true)
|
13
13
|
else
|
14
|
-
store_cloudinary_identifier(file.version, file.filename)
|
14
|
+
store_cloudinary_identifier(file.version, file.filename, file.resource_type, file.type)
|
15
15
|
end
|
16
|
-
return
|
16
|
+
return # Nothing to do
|
17
17
|
when Cloudinary::CarrierWave::CloudinaryFile, Cloudinary::CarrierWave::StoredFile
|
18
|
-
return
|
18
|
+
return # Nothing to do
|
19
19
|
when Cloudinary::CarrierWave::RemoteFile
|
20
20
|
data = file.uri.to_s
|
21
21
|
else
|
@@ -26,7 +26,7 @@ class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
|
|
26
26
|
# This is the toplevel, need to upload the actual file.
|
27
27
|
params = uploader.transformation.dup
|
28
28
|
params[:return_error] = true
|
29
|
-
params[:format] = uploader.
|
29
|
+
params[:format] = uploader.requested_format
|
30
30
|
params[:public_id] = uploader.my_public_id
|
31
31
|
uploader.versions.values.each(&:tags) # Validate no tags in versions
|
32
32
|
params[:tags] = uploader.tags if uploader.tags
|
@@ -42,7 +42,7 @@ class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
|
|
42
42
|
|
43
43
|
if uploader.metadata["version"]
|
44
44
|
filename = [uploader.metadata["public_id"], uploader.metadata["format"]].reject(&:blank?).join(".")
|
45
|
-
store_cloudinary_identifier(uploader.metadata["version"], filename)
|
45
|
+
store_cloudinary_identifier(uploader.metadata["version"], filename, uploader.metadata["resource_type"], uploader.metadata["type"])
|
46
46
|
end
|
47
47
|
# Will throw an exception on error
|
48
48
|
else
|
@@ -52,24 +52,18 @@ class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
|
|
52
52
|
nil
|
53
53
|
end
|
54
54
|
|
55
|
-
# @deprecated For backward compatibility
|
56
|
-
def store_cloudinary_version(version)
|
57
|
-
if identifier.match(%r(^(v[0-9]+)/(.*)))
|
58
|
-
filename = $2
|
59
|
-
else
|
60
|
-
filename = identifier
|
61
|
-
end
|
62
|
-
|
63
|
-
store_cloudinary_identifier(version, filename)
|
64
|
-
end
|
65
|
-
|
66
55
|
# Updates the model mounter identifier with version information.
|
67
56
|
#
|
68
57
|
# Carrierwave uses hooks when integrating with ORMs so it's important to
|
69
58
|
# update the identifier in a way that does not trigger hooks again or else
|
70
59
|
# you'll get stuck in a loop.
|
71
|
-
def store_cloudinary_identifier(version, filename)
|
60
|
+
def store_cloudinary_identifier(version, filename, resource_type=nil, type=nil)
|
72
61
|
name = "v#{version}/#{filename}"
|
62
|
+
if uploader.use_extended_identifier?
|
63
|
+
resource_type ||= uploader.resource_type || "image"
|
64
|
+
type ||= uploader.storage_type || "upload"
|
65
|
+
name = "#{resource_type}/#{type}/#{name}"
|
66
|
+
end
|
73
67
|
model_class = uploader.model.class
|
74
68
|
column = uploader.model.send(:_mounter, uploader.mounted_as).send(:serialization_column)
|
75
69
|
if defined?(ActiveRecord::Base) && uploader.model.is_a?(ActiveRecord::Base)
|
data/lib/cloudinary/utils.rb
CHANGED
@@ -1,551 +1,554 @@
|
|
1
|
-
# Copyright Cloudinary
|
2
|
-
require 'digest/sha1'
|
3
|
-
require 'zlib'
|
4
|
-
require 'uri'
|
5
|
-
require 'aws_cf_signer'
|
6
|
-
|
7
|
-
class Cloudinary::Utils
|
8
|
-
# @deprecated Use Cloudinary::SHARED_CDN
|
9
|
-
SHARED_CDN = Cloudinary::SHARED_CDN
|
10
|
-
DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION = {:width => :auto, :crop => :limit}
|
11
|
-
|
12
|
-
# Warning: options are being destructively updated!
|
13
|
-
def self.generate_transformation_string(options={})
|
14
|
-
if options.is_a?(Array)
|
15
|
-
return options.map{|base_transformation| generate_transformation_string(base_transformation.clone)}.join("/")
|
16
|
-
end
|
17
|
-
# Symbolize keys
|
18
|
-
options.keys.each do |key|
|
19
|
-
options[(key.to_sym rescue key)] = options.delete(key)
|
20
|
-
end
|
21
|
-
|
22
|
-
responsive_width = config_option_consume(options, :responsive_width)
|
23
|
-
size = options.delete(:size)
|
24
|
-
options[:width], options[:height] = size.split("x") if size
|
25
|
-
width = options[:width]
|
26
|
-
width = width.to_s if width.is_a?(Symbol)
|
27
|
-
height = options[:height]
|
28
|
-
has_layer = options[:overlay].present? || options[:underlay].present?
|
29
|
-
|
30
|
-
crop = options.delete(:crop)
|
31
|
-
angle = build_array(options.delete(:angle)).join(".")
|
32
|
-
|
33
|
-
no_html_sizes = has_layer || angle.present? || crop.to_s == "fit" || crop.to_s == "limit" || crop.to_s == "lfill"
|
34
|
-
options.delete(:width) if width && (width.to_f < 1 || no_html_sizes || width == "auto" || responsive_width)
|
35
|
-
options.delete(:height) if height && (height.to_f < 1 || no_html_sizes || responsive_width)
|
36
|
-
|
37
|
-
width=height=nil if crop.nil? && !has_layer && width != "auto"
|
38
|
-
|
39
|
-
background = options.delete(:background)
|
40
|
-
background = background.sub(/^#/, 'rgb:') if background
|
41
|
-
|
42
|
-
color = options.delete(:color)
|
43
|
-
color = color.sub(/^#/, 'rgb:') if color
|
44
|
-
|
45
|
-
base_transformations = build_array(options.delete(:transformation))
|
46
|
-
if base_transformations.any?{|base_transformation| base_transformation.is_a?(Hash)}
|
47
|
-
base_transformations = base_transformations.map do
|
48
|
-
|base_transformation|
|
49
|
-
base_transformation.is_a?(Hash) ? generate_transformation_string(base_transformation.clone) : generate_transformation_string(:transformation=>base_transformation)
|
50
|
-
end
|
51
|
-
else
|
52
|
-
named_transformation = base_transformations.join(".")
|
53
|
-
base_transformations = []
|
54
|
-
end
|
55
|
-
|
56
|
-
effect = options.delete(:effect)
|
57
|
-
effect = Array(effect).flatten.join(":") if effect.is_a?(Array) || effect.is_a?(Hash)
|
58
|
-
|
59
|
-
border = options.delete(:border)
|
60
|
-
if border.is_a?(Hash)
|
61
|
-
border = "#{border[:width] || 2}px_solid_#{(border[:color] || "black").sub(/^#/, 'rgb:')}"
|
62
|
-
elsif border.to_s =~ /^\d+$/ # fallback to html border attribute
|
63
|
-
options[:border] = border
|
64
|
-
border = nil
|
65
|
-
end
|
66
|
-
flags = build_array(options.delete(:flags)).join(".")
|
67
|
-
dpr = config_option_consume(options, :dpr)
|
68
|
-
|
69
|
-
if options.include? :offset
|
70
|
-
options[:start_offset], options[:end_offset] = split_range options.delete(:offset)
|
71
|
-
end
|
72
|
-
|
73
|
-
params = {
|
74
|
-
:a => angle,
|
75
|
-
:b => background,
|
76
|
-
:bo => border,
|
77
|
-
:c => crop,
|
78
|
-
:co => color,
|
79
|
-
:dpr => dpr,
|
80
|
-
:e => effect,
|
81
|
-
:fl => flags,
|
82
|
-
:h => height,
|
83
|
-
:t => named_transformation,
|
84
|
-
:w => width
|
85
|
-
}
|
86
|
-
{
|
87
|
-
:ac => :audio_codec,
|
88
|
-
:br => :bit_rate,
|
89
|
-
:cs => :color_space,
|
90
|
-
:d => :default_image,
|
91
|
-
:dl => :delay,
|
92
|
-
:dn => :density,
|
93
|
-
:du => :duration,
|
94
|
-
:eo => :end_offset,
|
95
|
-
:f => :fetch_format,
|
96
|
-
:g => :gravity,
|
97
|
-
:l => :overlay,
|
98
|
-
:o => :opacity,
|
99
|
-
:p => :prefix,
|
100
|
-
:pg => :page,
|
101
|
-
:q => :quality,
|
102
|
-
:r => :radius,
|
103
|
-
:af => :audio_frequency,
|
104
|
-
:so => :start_offset,
|
105
|
-
:u => :underlay,
|
106
|
-
:vc => :video_codec,
|
107
|
-
:vs => :video_sampling,
|
108
|
-
:x => :x,
|
109
|
-
:y => :y,
|
110
|
-
:z => :zoom
|
111
|
-
}.each do
|
112
|
-
|param, option|
|
113
|
-
params[param] = options.delete(option)
|
114
|
-
end
|
115
|
-
|
116
|
-
params[:vc] = process_video_params params[:vc] if params[:vc].present?
|
117
|
-
[:so, :eo, :du].each do |range_value|
|
118
|
-
params[range_value] = norm_range_value params[range_value] if params[range_value].present?
|
119
|
-
end
|
120
|
-
|
121
|
-
transformation = params.reject{|_k,v| v.blank?}.map{|k,v| "#{k}_#{v}"}.sort.join(",")
|
122
|
-
raw_transformation = options.delete(:raw_transformation)
|
123
|
-
transformation = [transformation, raw_transformation].reject(&:blank?).join(",")
|
124
|
-
transformations = base_transformations << transformation
|
125
|
-
if responsive_width
|
126
|
-
responsive_width_transformation = Cloudinary.config.responsive_width_transformation || DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION
|
127
|
-
transformations << generate_transformation_string(responsive_width_transformation.clone)
|
128
|
-
end
|
129
|
-
|
130
|
-
if width.to_s == "auto" || responsive_width
|
131
|
-
options[:responsive] = true
|
132
|
-
end
|
133
|
-
if dpr.to_s == "auto"
|
134
|
-
options[:hidpi] = true
|
135
|
-
end
|
136
|
-
|
137
|
-
transformations.reject(&:blank?).join("/")
|
138
|
-
end
|
139
|
-
|
140
|
-
def self.api_string_to_sign(params_to_sign)
|
141
|
-
params_to_sign.map{|k,v| [k.to_s, v.is_a?(Array) ? v.join(",") : v]}.reject{|k,v| v.nil? || v == ""}.sort_by(&:first).map{|k,v| "#{k}=#{v}"}.join("&")
|
142
|
-
end
|
143
|
-
|
144
|
-
def self.api_sign_request(params_to_sign, api_secret)
|
145
|
-
to_sign = api_string_to_sign(params_to_sign)
|
146
|
-
Digest::SHA1.hexdigest("#{to_sign}#{api_secret}")
|
147
|
-
end
|
148
|
-
|
149
|
-
# Warning: options are being destructively updated!
|
150
|
-
def self.unsigned_download_url(source, options = {})
|
151
|
-
|
152
|
-
type = options.delete(:type)
|
153
|
-
|
154
|
-
options[:fetch_format] ||= options.delete(:format) if type == :fetch
|
155
|
-
transformation = self.generate_transformation_string(options)
|
156
|
-
|
157
|
-
resource_type = options.delete(:resource_type)
|
158
|
-
version = options.delete(:version)
|
159
|
-
format = options.delete(:format)
|
160
|
-
cloud_name = config_option_consume(options, :cloud_name) || raise(CloudinaryException, "Must supply cloud_name in tag or in configuration")
|
161
|
-
|
162
|
-
secure = options.delete(:secure)
|
163
|
-
ssl_detected = options.delete(:ssl_detected)
|
164
|
-
secure = ssl_detected || Cloudinary.config.secure if secure.nil?
|
165
|
-
private_cdn = config_option_consume(options, :private_cdn)
|
166
|
-
secure_distribution = config_option_consume(options, :secure_distribution)
|
167
|
-
cname = config_option_consume(options, :cname)
|
168
|
-
shorten = config_option_consume(options, :shorten)
|
169
|
-
force_remote = options.delete(:force_remote)
|
170
|
-
cdn_subdomain = config_option_consume(options, :cdn_subdomain)
|
171
|
-
secure_cdn_subdomain = config_option_consume(options, :secure_cdn_subdomain)
|
172
|
-
sign_url = config_option_consume(options, :sign_url)
|
173
|
-
secret = config_option_consume(options, :api_secret)
|
174
|
-
sign_version = config_option_consume(options, :sign_version) # Deprecated behavior
|
175
|
-
url_suffix = options.delete(:url_suffix)
|
176
|
-
use_root_path = config_option_consume(options, :use_root_path)
|
177
|
-
|
178
|
-
raise(CloudinaryException, "URL Suffix only supported in private CDN") if url_suffix.present? and not private_cdn
|
179
|
-
|
180
|
-
original_source = source
|
181
|
-
return original_source if source.blank?
|
182
|
-
if defined?(CarrierWave::Uploader::Base) && source.is_a?(CarrierWave::Uploader::Base)
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
source = smart_escape(
|
228
|
-
source_to_sign = source
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
source = "#{source}
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
resource_type = "
|
249
|
-
type = nil
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
#
|
273
|
-
#
|
274
|
-
# if
|
275
|
-
#
|
276
|
-
# if cdn_domain is true uses
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
prefix = "
|
297
|
-
|
298
|
-
|
299
|
-
prefix = "http://#{
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
params
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
:
|
328
|
-
:
|
329
|
-
:
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
signer
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
when
|
449
|
-
when
|
450
|
-
when
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
#
|
511
|
-
#
|
512
|
-
#
|
513
|
-
#
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
end
|
1
|
+
# Copyright Cloudinary
|
2
|
+
require 'digest/sha1'
|
3
|
+
require 'zlib'
|
4
|
+
require 'uri'
|
5
|
+
require 'aws_cf_signer'
|
6
|
+
|
7
|
+
class Cloudinary::Utils
|
8
|
+
# @deprecated Use Cloudinary::SHARED_CDN
|
9
|
+
SHARED_CDN = Cloudinary::SHARED_CDN
|
10
|
+
DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION = {:width => :auto, :crop => :limit}
|
11
|
+
|
12
|
+
# Warning: options are being destructively updated!
|
13
|
+
def self.generate_transformation_string(options={})
|
14
|
+
if options.is_a?(Array)
|
15
|
+
return options.map{|base_transformation| generate_transformation_string(base_transformation.clone)}.join("/")
|
16
|
+
end
|
17
|
+
# Symbolize keys
|
18
|
+
options.keys.each do |key|
|
19
|
+
options[(key.to_sym rescue key)] = options.delete(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
responsive_width = config_option_consume(options, :responsive_width)
|
23
|
+
size = options.delete(:size)
|
24
|
+
options[:width], options[:height] = size.split("x") if size
|
25
|
+
width = options[:width]
|
26
|
+
width = width.to_s if width.is_a?(Symbol)
|
27
|
+
height = options[:height]
|
28
|
+
has_layer = options[:overlay].present? || options[:underlay].present?
|
29
|
+
|
30
|
+
crop = options.delete(:crop)
|
31
|
+
angle = build_array(options.delete(:angle)).join(".")
|
32
|
+
|
33
|
+
no_html_sizes = has_layer || angle.present? || crop.to_s == "fit" || crop.to_s == "limit" || crop.to_s == "lfill"
|
34
|
+
options.delete(:width) if width && (width.to_f < 1 || no_html_sizes || width == "auto" || responsive_width)
|
35
|
+
options.delete(:height) if height && (height.to_f < 1 || no_html_sizes || responsive_width)
|
36
|
+
|
37
|
+
width=height=nil if crop.nil? && !has_layer && width != "auto"
|
38
|
+
|
39
|
+
background = options.delete(:background)
|
40
|
+
background = background.sub(/^#/, 'rgb:') if background
|
41
|
+
|
42
|
+
color = options.delete(:color)
|
43
|
+
color = color.sub(/^#/, 'rgb:') if color
|
44
|
+
|
45
|
+
base_transformations = build_array(options.delete(:transformation))
|
46
|
+
if base_transformations.any?{|base_transformation| base_transformation.is_a?(Hash)}
|
47
|
+
base_transformations = base_transformations.map do
|
48
|
+
|base_transformation|
|
49
|
+
base_transformation.is_a?(Hash) ? generate_transformation_string(base_transformation.clone) : generate_transformation_string(:transformation=>base_transformation)
|
50
|
+
end
|
51
|
+
else
|
52
|
+
named_transformation = base_transformations.join(".")
|
53
|
+
base_transformations = []
|
54
|
+
end
|
55
|
+
|
56
|
+
effect = options.delete(:effect)
|
57
|
+
effect = Array(effect).flatten.join(":") if effect.is_a?(Array) || effect.is_a?(Hash)
|
58
|
+
|
59
|
+
border = options.delete(:border)
|
60
|
+
if border.is_a?(Hash)
|
61
|
+
border = "#{border[:width] || 2}px_solid_#{(border[:color] || "black").sub(/^#/, 'rgb:')}"
|
62
|
+
elsif border.to_s =~ /^\d+$/ # fallback to html border attribute
|
63
|
+
options[:border] = border
|
64
|
+
border = nil
|
65
|
+
end
|
66
|
+
flags = build_array(options.delete(:flags)).join(".")
|
67
|
+
dpr = config_option_consume(options, :dpr)
|
68
|
+
|
69
|
+
if options.include? :offset
|
70
|
+
options[:start_offset], options[:end_offset] = split_range options.delete(:offset)
|
71
|
+
end
|
72
|
+
|
73
|
+
params = {
|
74
|
+
:a => angle,
|
75
|
+
:b => background,
|
76
|
+
:bo => border,
|
77
|
+
:c => crop,
|
78
|
+
:co => color,
|
79
|
+
:dpr => dpr,
|
80
|
+
:e => effect,
|
81
|
+
:fl => flags,
|
82
|
+
:h => height,
|
83
|
+
:t => named_transformation,
|
84
|
+
:w => width
|
85
|
+
}
|
86
|
+
{
|
87
|
+
:ac => :audio_codec,
|
88
|
+
:br => :bit_rate,
|
89
|
+
:cs => :color_space,
|
90
|
+
:d => :default_image,
|
91
|
+
:dl => :delay,
|
92
|
+
:dn => :density,
|
93
|
+
:du => :duration,
|
94
|
+
:eo => :end_offset,
|
95
|
+
:f => :fetch_format,
|
96
|
+
:g => :gravity,
|
97
|
+
:l => :overlay,
|
98
|
+
:o => :opacity,
|
99
|
+
:p => :prefix,
|
100
|
+
:pg => :page,
|
101
|
+
:q => :quality,
|
102
|
+
:r => :radius,
|
103
|
+
:af => :audio_frequency,
|
104
|
+
:so => :start_offset,
|
105
|
+
:u => :underlay,
|
106
|
+
:vc => :video_codec,
|
107
|
+
:vs => :video_sampling,
|
108
|
+
:x => :x,
|
109
|
+
:y => :y,
|
110
|
+
:z => :zoom
|
111
|
+
}.each do
|
112
|
+
|param, option|
|
113
|
+
params[param] = options.delete(option)
|
114
|
+
end
|
115
|
+
|
116
|
+
params[:vc] = process_video_params params[:vc] if params[:vc].present?
|
117
|
+
[:so, :eo, :du].each do |range_value|
|
118
|
+
params[range_value] = norm_range_value params[range_value] if params[range_value].present?
|
119
|
+
end
|
120
|
+
|
121
|
+
transformation = params.reject{|_k,v| v.blank?}.map{|k,v| "#{k}_#{v}"}.sort.join(",")
|
122
|
+
raw_transformation = options.delete(:raw_transformation)
|
123
|
+
transformation = [transformation, raw_transformation].reject(&:blank?).join(",")
|
124
|
+
transformations = base_transformations << transformation
|
125
|
+
if responsive_width
|
126
|
+
responsive_width_transformation = Cloudinary.config.responsive_width_transformation || DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION
|
127
|
+
transformations << generate_transformation_string(responsive_width_transformation.clone)
|
128
|
+
end
|
129
|
+
|
130
|
+
if width.to_s == "auto" || responsive_width
|
131
|
+
options[:responsive] = true
|
132
|
+
end
|
133
|
+
if dpr.to_s == "auto"
|
134
|
+
options[:hidpi] = true
|
135
|
+
end
|
136
|
+
|
137
|
+
transformations.reject(&:blank?).join("/")
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.api_string_to_sign(params_to_sign)
|
141
|
+
params_to_sign.map{|k,v| [k.to_s, v.is_a?(Array) ? v.join(",") : v]}.reject{|k,v| v.nil? || v == ""}.sort_by(&:first).map{|k,v| "#{k}=#{v}"}.join("&")
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.api_sign_request(params_to_sign, api_secret)
|
145
|
+
to_sign = api_string_to_sign(params_to_sign)
|
146
|
+
Digest::SHA1.hexdigest("#{to_sign}#{api_secret}")
|
147
|
+
end
|
148
|
+
|
149
|
+
# Warning: options are being destructively updated!
|
150
|
+
def self.unsigned_download_url(source, options = {})
|
151
|
+
|
152
|
+
type = options.delete(:type)
|
153
|
+
|
154
|
+
options[:fetch_format] ||= options.delete(:format) if type == :fetch
|
155
|
+
transformation = self.generate_transformation_string(options)
|
156
|
+
|
157
|
+
resource_type = options.delete(:resource_type)
|
158
|
+
version = options.delete(:version)
|
159
|
+
format = options.delete(:format)
|
160
|
+
cloud_name = config_option_consume(options, :cloud_name) || raise(CloudinaryException, "Must supply cloud_name in tag or in configuration")
|
161
|
+
|
162
|
+
secure = options.delete(:secure)
|
163
|
+
ssl_detected = options.delete(:ssl_detected)
|
164
|
+
secure = ssl_detected || Cloudinary.config.secure if secure.nil?
|
165
|
+
private_cdn = config_option_consume(options, :private_cdn)
|
166
|
+
secure_distribution = config_option_consume(options, :secure_distribution)
|
167
|
+
cname = config_option_consume(options, :cname)
|
168
|
+
shorten = config_option_consume(options, :shorten)
|
169
|
+
force_remote = options.delete(:force_remote)
|
170
|
+
cdn_subdomain = config_option_consume(options, :cdn_subdomain)
|
171
|
+
secure_cdn_subdomain = config_option_consume(options, :secure_cdn_subdomain)
|
172
|
+
sign_url = config_option_consume(options, :sign_url)
|
173
|
+
secret = config_option_consume(options, :api_secret)
|
174
|
+
sign_version = config_option_consume(options, :sign_version) # Deprecated behavior
|
175
|
+
url_suffix = options.delete(:url_suffix)
|
176
|
+
use_root_path = config_option_consume(options, :use_root_path)
|
177
|
+
|
178
|
+
raise(CloudinaryException, "URL Suffix only supported in private CDN") if url_suffix.present? and not private_cdn
|
179
|
+
|
180
|
+
original_source = source
|
181
|
+
return original_source if source.blank?
|
182
|
+
if defined?(CarrierWave::Uploader::Base) && source.is_a?(CarrierWave::Uploader::Base)
|
183
|
+
resource_type ||= source.resource_type
|
184
|
+
type ||= source.storage_type
|
185
|
+
source = format.blank? ? source.filename : source.full_public_id
|
186
|
+
end
|
187
|
+
resource_type ||= "image"
|
188
|
+
source = source.to_s
|
189
|
+
if !force_remote
|
190
|
+
return original_source if (type.nil? || type == :asset) && source.match(%r(^https?:/)i)
|
191
|
+
if source.start_with?("/")
|
192
|
+
if source.start_with?("/images/")
|
193
|
+
source = source.sub(%r(/images/), '')
|
194
|
+
else
|
195
|
+
return original_source
|
196
|
+
end
|
197
|
+
end
|
198
|
+
@metadata ||= defined?(Cloudinary::Static) ? Cloudinary::Static.metadata : {}
|
199
|
+
if type == :asset && @metadata["images/#{source}"]
|
200
|
+
return original_source if !Cloudinary.config.static_image_support
|
201
|
+
source = @metadata["images/#{source}"]["public_id"]
|
202
|
+
source += File.extname(original_source) if !format
|
203
|
+
elsif type == :asset
|
204
|
+
return original_source # requested asset, but no metadata - probably local file. return.
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
resource_type, type = finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten)
|
209
|
+
source, source_to_sign = finalize_source(source, format, url_suffix)
|
210
|
+
|
211
|
+
version ||= 1 if source_to_sign.include?("/") and !source_to_sign.match(/^v[0-9]+/) and !source_to_sign.match(/^https?:\//)
|
212
|
+
version &&= "v#{version}"
|
213
|
+
|
214
|
+
transformation = transformation.gsub(%r(([^:])//), '\1/')
|
215
|
+
if sign_url
|
216
|
+
to_sign = [transformation, sign_version && version, source_to_sign].reject(&:blank?).join("/")
|
217
|
+
signature = 's--' + Base64.urlsafe_encode64(Digest::SHA1.digest(to_sign + secret))[0,8] + '--'
|
218
|
+
end
|
219
|
+
|
220
|
+
prefix = unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
|
221
|
+
source = [prefix, resource_type, type, signature, transformation, version, source].reject(&:blank?).join("/")
|
222
|
+
end
|
223
|
+
|
224
|
+
def self.finalize_source(source, format, url_suffix)
|
225
|
+
source = source.gsub(%r(([^:])//), '\1/')
|
226
|
+
if source.match(%r(^https?:/)i)
|
227
|
+
source = smart_escape(source)
|
228
|
+
source_to_sign = source
|
229
|
+
else
|
230
|
+
source = smart_escape(URI.decode(source))
|
231
|
+
source_to_sign = source
|
232
|
+
unless url_suffix.blank?
|
233
|
+
raise(CloudinaryException, "url_suffix should not include . or /") if url_suffix.match(%r([\./]))
|
234
|
+
source = "#{source}/#{url_suffix}"
|
235
|
+
end
|
236
|
+
if !format.blank?
|
237
|
+
source = "#{source}.#{format}"
|
238
|
+
source_to_sign = "#{source_to_sign}.#{format}"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
[source, source_to_sign]
|
242
|
+
end
|
243
|
+
|
244
|
+
def self.finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten)
|
245
|
+
type ||= :upload
|
246
|
+
if !url_suffix.blank?
|
247
|
+
if resource_type.to_s == "image" && type.to_s == "upload"
|
248
|
+
resource_type = "images"
|
249
|
+
type = nil
|
250
|
+
elsif resource_type.to_s == "raw" && type.to_s == "upload"
|
251
|
+
resource_type = "files"
|
252
|
+
type = nil
|
253
|
+
else
|
254
|
+
raise(CloudinaryException, "URL Suffix only supported for image/upload and raw/upload")
|
255
|
+
end
|
256
|
+
end
|
257
|
+
if use_root_path
|
258
|
+
if (resource_type.to_s == "image" && type.to_s == "upload") || (resource_type.to_s == "images" && type.blank?)
|
259
|
+
resource_type = nil
|
260
|
+
type = nil
|
261
|
+
else
|
262
|
+
raise(CloudinaryException, "Root path only supported for image/upload")
|
263
|
+
end
|
264
|
+
end
|
265
|
+
if shorten && resource_type.to_s == "image" && type.to_s == "upload"
|
266
|
+
resource_type = "iu"
|
267
|
+
type = nil
|
268
|
+
end
|
269
|
+
[resource_type, type]
|
270
|
+
end
|
271
|
+
|
272
|
+
# cdn_subdomain and secure_cdn_subdomain
|
273
|
+
# 1) Customers in shared distribution (e.g. res.cloudinary.com)
|
274
|
+
# if cdn_domain is true uses res-[1-5].cloudinary.com for both http and https. Setting secure_cdn_subdomain to false disables this for https.
|
275
|
+
# 2) Customers with private cdn
|
276
|
+
# if cdn_domain is true uses cloudname-res-[1-5].cloudinary.com for http
|
277
|
+
# if secure_cdn_domain is true uses cloudname-res-[1-5].cloudinary.com for https (please contact support if you require this)
|
278
|
+
# 3) Customers with cname
|
279
|
+
# if cdn_domain is true uses a[1-5].cname for http. For https, uses the same naming scheme as 1 for shared distribution and as 2 for private distribution.
|
280
|
+
def self.unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
|
281
|
+
return "/res#{cloud_name}" if cloud_name.start_with?("/") # For development
|
282
|
+
|
283
|
+
shared_domain = !private_cdn
|
284
|
+
|
285
|
+
if secure
|
286
|
+
if secure_distribution.nil? || secure_distribution == Cloudinary::OLD_AKAMAI_SHARED_CDN
|
287
|
+
secure_distribution = private_cdn ? "#{cloud_name}-res.cloudinary.com" : Cloudinary::SHARED_CDN
|
288
|
+
end
|
289
|
+
shared_domain ||= secure_distribution == Cloudinary::SHARED_CDN
|
290
|
+
secure_cdn_subdomain = cdn_subdomain if secure_cdn_subdomain.nil? && shared_domain
|
291
|
+
|
292
|
+
if secure_cdn_subdomain
|
293
|
+
secure_distribution = secure_distribution.gsub('res.cloudinary.com', "res-#{(Zlib::crc32(source) % 5) + 1}.cloudinary.com")
|
294
|
+
end
|
295
|
+
|
296
|
+
prefix = "https://#{secure_distribution}"
|
297
|
+
elsif cname
|
298
|
+
subdomain = cdn_subdomain ? "a#{(Zlib::crc32(source) % 5) + 1}." : ""
|
299
|
+
prefix = "http://#{subdomain}#{cname}"
|
300
|
+
else
|
301
|
+
host = [private_cdn ? "#{cloud_name}-" : "", "res", cdn_subdomain ? "-#{(Zlib::crc32(source) % 5) + 1}" : "", ".cloudinary.com"].join
|
302
|
+
prefix = "http://#{host}"
|
303
|
+
end
|
304
|
+
prefix += "/#{cloud_name}" if shared_domain
|
305
|
+
|
306
|
+
prefix
|
307
|
+
end
|
308
|
+
|
309
|
+
def self.cloudinary_api_url(action = 'upload', options = {})
|
310
|
+
cloudinary = options[:upload_prefix] || Cloudinary.config.upload_prefix || "https://api.cloudinary.com"
|
311
|
+
cloud_name = options[:cloud_name] || Cloudinary.config.cloud_name || raise(CloudinaryException, "Must supply cloud_name")
|
312
|
+
resource_type = options[:resource_type] || "image"
|
313
|
+
return [cloudinary, "v1_1", cloud_name, resource_type, action].join("/")
|
314
|
+
end
|
315
|
+
|
316
|
+
def self.sign_request(params, options={})
|
317
|
+
api_key = options[:api_key] || Cloudinary.config.api_key || raise(CloudinaryException, "Must supply api_key")
|
318
|
+
api_secret = options[:api_secret] || Cloudinary.config.api_secret || raise(CloudinaryException, "Must supply api_secret")
|
319
|
+
params = params.reject{|k, v| self.safe_blank?(v)}
|
320
|
+
params[:signature] = Cloudinary::Utils.api_sign_request(params, api_secret)
|
321
|
+
params[:api_key] = api_key
|
322
|
+
params
|
323
|
+
end
|
324
|
+
|
325
|
+
def self.private_download_url(public_id, format, options = {})
|
326
|
+
cloudinary_params = sign_request({
|
327
|
+
:timestamp=>Time.now.to_i,
|
328
|
+
:public_id=>public_id,
|
329
|
+
:format=>format,
|
330
|
+
:type=>options[:type],
|
331
|
+
:attachment=>options[:attachment],
|
332
|
+
:expires_at=>options[:expires_at] && options[:expires_at].to_i
|
333
|
+
}, options)
|
334
|
+
|
335
|
+
return Cloudinary::Utils.cloudinary_api_url("download", options) + "?" + cloudinary_params.to_query
|
336
|
+
end
|
337
|
+
|
338
|
+
def self.zip_download_url(tag, options = {})
|
339
|
+
cloudinary_params = sign_request({:timestamp=>Time.now.to_i, :tag=>tag, :transformation=>generate_transformation_string(options)}, options)
|
340
|
+
return Cloudinary::Utils.cloudinary_api_url("download_tag.zip", options) + "?" + cloudinary_params.to_query
|
341
|
+
end
|
342
|
+
|
343
|
+
def self.signed_download_url(public_id, options = {})
|
344
|
+
aws_private_key_path = options[:aws_private_key_path] || Cloudinary.config.aws_private_key_path || raise(CloudinaryException, "Must supply aws_private_key_path")
|
345
|
+
aws_key_pair_id = options[:aws_key_pair_id] || Cloudinary.config.aws_key_pair_id || raise(CloudinaryException, "Must supply aws_key_pair_id")
|
346
|
+
authenticated_distribution = options[:authenticated_distribution] || Cloudinary.config.authenticated_distribution || raise(CloudinaryException, "Must supply authenticated_distribution")
|
347
|
+
@signers ||= Hash.new{|h,k| path, id = k; h[k] = AwsCfSigner.new(path, id)}
|
348
|
+
signer = @signers[[aws_private_key_path, aws_key_pair_id]]
|
349
|
+
url = Cloudinary::Utils.unsigned_download_url(public_id, {:type=>:authenticated}.merge(options).merge(:secure=>true, :secure_distribution=>authenticated_distribution, :private_cdn=>true))
|
350
|
+
expires_at = options[:expires_at] || (Time.now+3600)
|
351
|
+
signer.sign(url, :ending => expires_at)
|
352
|
+
end
|
353
|
+
|
354
|
+
def self.cloudinary_url(public_id, options = {})
|
355
|
+
if options[:type].to_s == 'authenticated' && !options[:sign_url]
|
356
|
+
result = signed_download_url(public_id, options)
|
357
|
+
else
|
358
|
+
result = unsigned_download_url(public_id, options)
|
359
|
+
end
|
360
|
+
return result
|
361
|
+
end
|
362
|
+
|
363
|
+
def self.asset_file_name(path)
|
364
|
+
data = Cloudinary.app_root.join(path).read(:mode=>"rb")
|
365
|
+
ext = path.extname
|
366
|
+
md5 = Digest::MD5.hexdigest(data)
|
367
|
+
public_id = "#{path.basename(ext)}-#{md5}"
|
368
|
+
"#{public_id}#{ext}"
|
369
|
+
end
|
370
|
+
|
371
|
+
# Based on CGI::unescape. In addition does not escape / :
|
372
|
+
def self.smart_escape(string)
|
373
|
+
string.gsub(/([^a-zA-Z0-9_.\-\/:]+)/) do
|
374
|
+
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def self.random_public_id
|
379
|
+
sr = defined?(ActiveSupport::SecureRandom) ? ActiveSupport::SecureRandom : SecureRandom
|
380
|
+
sr.base64(20).downcase.gsub(/[^a-z0-9]/, "").sub(/^[0-9]+/, '')[0,20]
|
381
|
+
end
|
382
|
+
|
383
|
+
def self.signed_preloaded_image(result)
|
384
|
+
"#{result["resource_type"]}/#{result["type"] || "upload"}/v#{result["version"]}/#{[result["public_id"], result["format"]].reject(&:blank?).join(".")}##{result["signature"]}"
|
385
|
+
end
|
386
|
+
|
387
|
+
@@json_decode = false
|
388
|
+
def self.json_decode(str)
|
389
|
+
if !@@json_decode
|
390
|
+
@@json_decode = true
|
391
|
+
begin
|
392
|
+
require 'json'
|
393
|
+
rescue LoadError
|
394
|
+
begin
|
395
|
+
require 'active_support/json'
|
396
|
+
rescue LoadError
|
397
|
+
raise LoadError, "Please add the json gem or active_support to your Gemfile"
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
defined?(JSON) ? JSON.parse(str) : ActiveSupport::JSON.decode(str)
|
402
|
+
end
|
403
|
+
|
404
|
+
def self.build_array(array)
|
405
|
+
case array
|
406
|
+
when Array then array
|
407
|
+
when nil then []
|
408
|
+
else [array]
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
def self.encode_hash(hash)
|
413
|
+
case hash
|
414
|
+
when Hash then hash.map{|k,v| "#{k}=#{v}"}.join("|")
|
415
|
+
when nil then ""
|
416
|
+
else hash
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
def self.encode_double_array(array)
|
421
|
+
array = build_array(array)
|
422
|
+
if array.length > 0 && array[0].is_a?(Array)
|
423
|
+
return array.map{|a| build_array(a).join(",")}.join("|")
|
424
|
+
else
|
425
|
+
return array.join(",")
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
IMAGE_FORMATS = %w(bmp png tif tiff jpg jpeg gif pdf ico eps jpc jp2 psd)
|
430
|
+
|
431
|
+
def self.supported_image_format?(format)
|
432
|
+
format = format.to_s.downcase
|
433
|
+
extension = format =~ /\./ ? format.split('.').last : format
|
434
|
+
IMAGE_FORMATS.include?(extension)
|
435
|
+
end
|
436
|
+
|
437
|
+
def self.resource_type_for_format(format)
|
438
|
+
self.supported_image_format?(format) ? 'image' : 'raw'
|
439
|
+
end
|
440
|
+
|
441
|
+
def self.config_option_consume(options, option_name, default_value = nil)
|
442
|
+
return options.delete(option_name) if options.include?(option_name)
|
443
|
+
return Cloudinary.config.send(option_name) || default_value
|
444
|
+
end
|
445
|
+
|
446
|
+
def self.as_bool(value)
|
447
|
+
case value
|
448
|
+
when nil then nil
|
449
|
+
when String then value.downcase == "true" || value == "1"
|
450
|
+
when TrueClass then true
|
451
|
+
when FalseClass then false
|
452
|
+
when Fixnum then value != 0
|
453
|
+
when Symbol then value == :true
|
454
|
+
else
|
455
|
+
raise "Invalid boolean value #{value} of type #{value.class}"
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
def self.as_safe_bool(value)
|
460
|
+
case as_bool(value)
|
461
|
+
when nil then nil
|
462
|
+
when TrueClass then 1
|
463
|
+
when FalseClass then 0
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
def self.safe_blank?(value)
|
468
|
+
value.nil? || value == "" || value == []
|
469
|
+
end
|
470
|
+
|
471
|
+
private
|
472
|
+
def self.number_pattern
|
473
|
+
"([0-9]*)\\.([0-9]+)|([0-9]+)"
|
474
|
+
end
|
475
|
+
|
476
|
+
def self.offset_any_pattern
|
477
|
+
"(#{number_pattern})([%pP])?"
|
478
|
+
end
|
479
|
+
|
480
|
+
def self.offset_any_pattern_re
|
481
|
+
/((([0-9]*)\.([0-9]+)|([0-9]+))([%pP])?)\.\.((([0-9]*)\.([0-9]+)|([0-9]+))([%pP])?)/
|
482
|
+
end
|
483
|
+
|
484
|
+
# Split a range into the start and end values
|
485
|
+
def self.split_range(range) # :nodoc:
|
486
|
+
case range
|
487
|
+
when Range
|
488
|
+
[range.first, range.last]
|
489
|
+
when String
|
490
|
+
range.split ".." if offset_any_pattern_re =~ range
|
491
|
+
when Array
|
492
|
+
[range.first, range.last]
|
493
|
+
else
|
494
|
+
nil
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
# Normalize an offset value
|
499
|
+
# @param [String] value a decimal value which may have a 'p' or '%' postfix. E.g. '35%', '0.4p'
|
500
|
+
# @return [Object|String] a normalized String of the input value if possible otherwise the value itself
|
501
|
+
def self.norm_range_value(value) # :nodoc:
|
502
|
+
offset = /^#{offset_any_pattern}$/.match( value.to_s)
|
503
|
+
if offset
|
504
|
+
modifier = offset[5].present? ? 'p' : ''
|
505
|
+
value = "#{offset[1]}#{modifier}"
|
506
|
+
end
|
507
|
+
value
|
508
|
+
end
|
509
|
+
|
510
|
+
# A video codec parameter can be either a String or a Hash.
|
511
|
+
#
|
512
|
+
# @param [Object] param <code>vc_<codec>[ : <profile> : [<level>]]</code>
|
513
|
+
# or <code>{ codec: 'h264', profile: 'basic', level: '3.1' }</code>
|
514
|
+
# @return [String] <code><codec> : <profile> : [<level>]]</code> if a Hash was provided
|
515
|
+
# or the param if a String was provided.
|
516
|
+
# Returns NIL if param is not a Hash or String
|
517
|
+
def self.process_video_params(param)
|
518
|
+
case param
|
519
|
+
when Hash
|
520
|
+
video = ""
|
521
|
+
if param.has_key? :codec
|
522
|
+
video = param[:codec]
|
523
|
+
if param.has_key? :profile
|
524
|
+
video.concat ":" + param[:profile]
|
525
|
+
if param.has_key? :level
|
526
|
+
video.concat ":" + param[:level]
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
video
|
531
|
+
when String
|
532
|
+
param
|
533
|
+
else
|
534
|
+
nil
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
def self.deep_symbolize_keys(object)
|
539
|
+
case object
|
540
|
+
when Hash
|
541
|
+
result = {}
|
542
|
+
object.each do |key, value|
|
543
|
+
key = key.to_sym rescue key
|
544
|
+
result[key] = deep_symbolize_keys(value)
|
545
|
+
end
|
546
|
+
result
|
547
|
+
when Array
|
548
|
+
object.map{|e| deep_symbolize_keys(e)}
|
549
|
+
else
|
550
|
+
object
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
end
|