cloudinary 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -22,23 +22,23 @@ To install the Cloudinary Ruby GEM, run:
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
 
25
- $ gem 'cloudinary'
25
+ gem 'cloudinary'
26
26
 
27
27
  Or in Rails 2.x, edit your `environment.rb` and add:
28
28
 
29
- $ config.gem 'cloudinary'
29
+ config.gem 'cloudinary'
30
30
 
31
31
  If you would like to use our optional integration module of image uploads with ActiveRecord using `CarrierWave`, install CarrierWave GEM:
32
32
 
33
33
  Rails 3.x: edit your `Gemfile` and run `bundle install`:
34
34
 
35
- $ gem 'carrierwave'
36
- $ gem 'cloudinary'
35
+ gem 'carrierwave'
36
+ gem 'cloudinary'
37
37
 
38
38
  Rails 2.x environment.rb:
39
39
 
40
- $ config.gem 'carrierwave', :version => '~> 0.4.10'
41
- $ config.gem 'cloudinary'
40
+ config.gem 'carrierwave', :version => '~> 0.4.10'
41
+ config.gem 'cloudinary'
42
42
 
43
43
 
44
44
  *Note: The CarrierWave GEM should be loaded before the Cloudinary GEM.*
@@ -92,6 +92,18 @@ It contains settings for each of your deployment environments. You can always ov
92
92
 
93
93
  You can [download your customized cloudinary.yml](https://cloudinary.com/console/cloudinary.yml) configuration file using our Management Console.
94
94
 
95
+ Passing the parameters manually looks like this:
96
+
97
+ ``` ruby
98
+ auth = {
99
+ cloud_name: "somename",
100
+ api_key: "1234567890",
101
+ api_secret: "FooBarBaz123"
102
+ }
103
+
104
+ Cloudinary::Uploader.upload("my_picture.jpg", auth)
105
+ ```
106
+
95
107
 
96
108
  ### Embedding and transforming images
97
109
 
@@ -163,12 +175,15 @@ Cloudinary's Ruby GEM includes an optional plugin for [CarrierWave](https://gith
163
175
 
164
176
  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).
165
177
 
178
+ ## Neo4j integration
179
+
180
+ Starting from version 1.1.1 Cloudinary's Ruby GEM supports the use of carrierwave with Neo4j.
181
+
166
182
  ### Samples
167
183
 
168
184
  You can find our simple and ready-to-use samples projects, along with documentation in the [samples folder](https://github.com/cloudinary/cloudinary_gem/tree/master/samples).
169
185
  Please consult with the [README file](https://github.com/cloudinary/cloudinary_gem/blob/master/samples/README.md), for usage and explanations.
170
186
 
171
-
172
187
  ## Additional resources ##########################################################
173
188
 
174
189
  Additional resources are available at:
@@ -193,3 +208,5 @@ Stay tuned for updates, tips and tutorials: [Blog](http://cloudinary.com/blog),
193
208
  ## License #######################################################################
194
209
 
195
210
  Released under the MIT license.
211
+
212
+
data/Rakefile CHANGED
@@ -1,4 +1,9 @@
1
1
  require 'bundler'
2
+ require 'fileutils'
3
+ require 'tmpdir'
4
+ require 'rest_client'
5
+ require 'json'
6
+ require 'rubygems/package'
2
7
 
3
8
  require 'rspec/core/rake_task'
4
9
  RSpec::Core::RakeTask.new(:spec)
@@ -6,24 +11,45 @@ task :default => :spec
6
11
 
7
12
  Bundler::GemHelper.install_tasks
8
13
 
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/tarball/master | tar zxvf - --strip=1"
11
- system "mkdir -p vendor/assets/javascripts; mv vendor/assets/js vendor/assets/javascripts/cloudinary"
12
- File.open("vendor/assets/javascripts/cloudinary/index.js", "w") do
13
- |f|
14
- f.puts "//= require ./jquery.ui.widget.js"
15
- f.puts "//= require ./jquery.iframe-transport.js"
16
- f.puts "//= require ./jquery.fileupload.js"
17
- f.puts "//= require ./jquery.cloudinary.js"
18
- end
19
- File.open("vendor/assets/javascripts/cloudinary/processing.js", "w") do
20
- |f|
21
- f.puts "//= require ./canvas-to-blob.min.js"
22
- f.puts "//= require ./load-image.min.js"
23
- f.puts "//= require ./jquery.fileupload-process.js"
24
- f.puts "//= require ./jquery.fileupload-image.js"
25
- f.puts "//= require ./jquery.fileupload-validate.js"
14
+ namespace :cloudinary do
15
+ desc "Fetch the latest JavaScript library files and create the JavaScript index files"
16
+ task :fetch_assets do
17
+ index_files = %w[jquery.ui.widget.js jquery.iframe-transport.js jquery.fileupload.js jquery.cloudinary.js]
18
+ processing_files = %w[canvas-to-blob.min.js load-image.all.min.js jquery.fileupload-process.js jquery.fileupload-image.js jquery.fileupload-validate.js]
19
+ files = index_files + processing_files
20
+
21
+ release = JSON(RestClient.get("https://api.github.com/repos/cloudinary/cloudinary_js/releases/latest"))
22
+
23
+ FileUtils.rm_rf 'vendor/assets'
24
+ html_folder = 'vendor/assets/html'
25
+ FileUtils.mkdir_p html_folder
26
+ js_folder = 'vendor/assets/javascripts/cloudinary'
27
+ FileUtils.mkdir_p js_folder
28
+
29
+ puts "Fetching cloudinary_js version #{release["tag_name"]}\n\n"
30
+ sio = StringIO.new(RestClient.get(release["tarball_url"]).body)
31
+ file =Zlib::GzipReader.new(sio)
32
+ tar = Gem::Package::TarReader.new(file)
33
+ tar.each_entry do |entry|
34
+ name = File.basename(entry.full_name)
35
+ if files.include? name
36
+ js_full_name = File.join(js_folder, name)
37
+ puts "Adding #{js_full_name}"
38
+ File.write js_full_name, entry.read
39
+ elsif name == 'cloudinary_cors.html'
40
+ html_full_name = File.join(html_folder, name)
41
+ puts "Adding #{html_full_name}"
42
+ File.write html_full_name, entry.read
43
+ end
44
+ end
45
+ puts "Creating 'index.js' and 'processing.js' files"
46
+ File.open("vendor/assets/javascripts/cloudinary/index.js", "w") do |f|
47
+ index_files.each { |name| f.puts "//= require ./#{name}" }
48
+ end
49
+ File.open("vendor/assets/javascripts/cloudinary/processing.js", "w") do |f|
50
+ processing_files.each { |name| f.puts "//= require ./#{name}" }
51
+ end
26
52
  end
27
- end
28
53
 
29
- task :build=>:fetch_assets
54
+ end
55
+ task :build => "cloudinary:fetch_assets"
@@ -68,9 +68,10 @@ class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
68
68
  column = uploader.model.send(:_mounter, uploader.mounted_as).send(:serialization_column)
69
69
  if defined?(ActiveRecord::Base) && uploader.model.is_a?(ActiveRecord::Base)
70
70
  primary_key = model_class.primary_key.to_sym
71
- if Rails.version >= "3.0"
71
+ if defined?(::ActiveRecord::VERSION::MAJOR) && ::ActiveRecord::VERSION::MAJOR > 2
72
72
  model_class.where(primary_key=>uploader.model.send(primary_key)).update_all(column=>name)
73
73
  else
74
+ # Removed since active record version 3.0.0
74
75
  model_class.update_all({column=>name}, {primary_key=>uploader.model.send(primary_key)})
75
76
  end
76
77
  uploader.model.send :write_attribute, column, name
@@ -83,6 +84,8 @@ class Cloudinary::CarrierWave::Storage < ::CarrierWave::Storage::Abstract
83
84
  else
84
85
  uploader.model.set(column, name)
85
86
  end
87
+ elsif defined?(Neo4j::VERSION) && Neo4j::VERSION.split(".").first.to_i >= 5
88
+ uploader.model.set(column, name)
86
89
  elsif defined?(Sequel::Model) && uploader.model.is_a?(Sequel::Model)
87
90
  # Sequel support
88
91
  uploader.model.this.update(column => name)
@@ -250,7 +250,7 @@ module CloudinaryHelper
250
250
  if !method_defined?(:image_tag)
251
251
  include ActionView::Helpers::AssetTagHelper
252
252
  end
253
- if defined?(Rails::version) && !Rails.version.start_with?('2') && Cloudinary.config.enhance_image_tag
253
+ if defined?(::Rails::VERSION::MAJOR) && ::Rails::VERSION::MAJOR > 2 && Cloudinary.config.enhance_image_tag
254
254
  alias_method_chain :image_tag, :cloudinary # defines image_tag_without_cloudinary
255
255
  alias_method_chain :image_path, :cloudinary # defines image_path_without_cloudinary
256
256
  else
@@ -316,7 +316,7 @@ if defined? ActionView::Helpers::AssetUrlHelper
316
316
  end
317
317
  end
318
318
 
319
- if defined?(Rails::version) && Rails.version.start_with?('2')
319
+ if defined?(::Rails::VERSION::MAJOR) && ::Rails::VERSION::MAJOR == 2
320
320
  ActionView::Base.send :include, ActionView::Helpers::AssetTagHelper
321
321
  ActionView::Base.send :include, CloudinaryHelper
322
322
  end
@@ -14,11 +14,9 @@ class Cloudinary::Utils
14
14
  if options.is_a?(Array)
15
15
  return options.map{|base_transformation| generate_transformation_string(base_transformation.clone)}.join("/")
16
16
  end
17
- # Symbolize keys
18
- options.keys.each do |key|
19
- options[(key.to_sym rescue key)] = options.delete(key)
20
- end
21
-
17
+
18
+ symbolize_keys!(options)
19
+
22
20
  responsive_width = config_option_consume(options, :responsive_width)
23
21
  size = options.delete(:size)
24
22
  options[:width], options[:height] = size.split("x") if size
@@ -70,6 +68,9 @@ class Cloudinary::Utils
70
68
  options[:start_offset], options[:end_offset] = split_range options.delete(:offset)
71
69
  end
72
70
 
71
+ overlay = process_layer(options.delete(:overlay))
72
+ underlay = process_layer(options.delete(:underlay))
73
+
73
74
  params = {
74
75
  :a => angle,
75
76
  :b => background,
@@ -80,7 +81,9 @@ class Cloudinary::Utils
80
81
  :e => effect,
81
82
  :fl => flags,
82
83
  :h => height,
84
+ :l => overlay,
83
85
  :t => named_transformation,
86
+ :u => underlay,
84
87
  :w => width
85
88
  }
86
89
  {
@@ -94,7 +97,6 @@ class Cloudinary::Utils
94
97
  :eo => :end_offset,
95
98
  :f => :fetch_format,
96
99
  :g => :gravity,
97
- :l => :overlay,
98
100
  :o => :opacity,
99
101
  :p => :prefix,
100
102
  :pg => :page,
@@ -102,7 +104,6 @@ class Cloudinary::Utils
102
104
  :r => :radius,
103
105
  :af => :audio_frequency,
104
106
  :so => :start_offset,
105
- :u => :underlay,
106
107
  :vc => :video_codec,
107
108
  :vs => :video_sampling,
108
109
  :x => :x,
@@ -137,6 +138,84 @@ class Cloudinary::Utils
137
138
  transformations.reject(&:blank?).join("/")
138
139
  end
139
140
 
141
+ # Parse layer options
142
+ # @return [string] layer transformation string
143
+ # @private
144
+ def self.process_layer(layer)
145
+ if layer.is_a? Hash
146
+ layer = symbolize_keys layer
147
+ public_id = layer[:public_id]
148
+ format = layer[:format]
149
+ resource_type = layer[:resource_type] || "image"
150
+ type = layer[:type] || "upload"
151
+ text = layer[:text]
152
+ text_style = nil
153
+ components = []
154
+
155
+ unless public_id.blank?
156
+ public_id = public_id.gsub("/", ":")
157
+ public_id = "#{public_id}.#{format}" unless format.nil?
158
+ end
159
+
160
+ if text.blank? && resource_type != "text"
161
+ if public_id.blank?
162
+ raise(CloudinaryException, "Must supply public_id for resource_type layer_parameter")
163
+ end
164
+ if resource_type == "subtitles"
165
+ text_style = text_style(layer)
166
+ end
167
+
168
+ else
169
+ resource_type = "text"
170
+ type = nil
171
+ # // type is ignored for text layers
172
+ text_style = text_style(layer)
173
+ unless text.blank?
174
+ unless public_id.blank? ^ text_style.blank?
175
+ raise(CloudinaryException, "Must supply either style parameters or a public_id when providing text parameter in a text overlay/underlay")
176
+ end
177
+ text = smart_escape smart_escape(text, %r"([,/])")
178
+
179
+ end
180
+ end
181
+ components.push(resource_type) if resource_type != "image"
182
+ components.push(type) if type != "upload"
183
+ components.push(text_style)
184
+ components.push(public_id)
185
+ components.push(text)
186
+ layer = components.reject(&:blank?).join(":")
187
+ end
188
+ layer
189
+ end
190
+ private_class_method :process_layer
191
+
192
+ LAYER_KEYWORD_PARAMS ={
193
+ :font_weight => "normal",
194
+ :font_style => "normal",
195
+ :text_decoration => "none",
196
+ :text_align => nil,
197
+ :stroke => "none"
198
+ }
199
+
200
+ def self.text_style(layer)
201
+ font_family = layer[:font_family]
202
+ font_size = layer[:font_size]
203
+ keywords = []
204
+ LAYER_KEYWORD_PARAMS.each do |attr, default_value|
205
+ attr_value = layer[attr] || default_value
206
+ keywords.push(attr_value) unless attr_value == default_value
207
+ end
208
+ letter_spacing = layer[:letter_spacing]
209
+ keywords.push("letter_spacing_#{letter_spacing}") unless letter_spacing.blank?
210
+ if !font_size.blank? || !font_family.blank? || !keywords.empty?
211
+ raise(CloudinaryException, "Must supply font_family for text in overlay/underlay") if font_family.blank?
212
+ raise(CloudinaryException, "Must supply font_size for text in overlay/underlay") if font_size.blank?
213
+ keywords.unshift(font_size)
214
+ keywords.unshift(font_family)
215
+ keywords.reject(&:blank?).join("_")
216
+ end
217
+ end
218
+
140
219
  def self.api_string_to_sign(params_to_sign)
141
220
  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
221
  end
@@ -151,7 +230,7 @@ class Cloudinary::Utils
151
230
 
152
231
  type = options.delete(:type)
153
232
 
154
- options[:fetch_format] ||= options.delete(:format) if type == :fetch
233
+ options[:fetch_format] ||= options.delete(:format) if type.to_s == "fetch"
155
234
  transformation = self.generate_transformation_string(options)
156
235
 
157
236
  resource_type = options.delete(:resource_type)
@@ -184,10 +263,11 @@ class Cloudinary::Utils
184
263
  type ||= source.storage_type
185
264
  source = format.blank? ? source.filename : source.full_public_id
186
265
  end
266
+ type = type.to_s unless type.nil?
187
267
  resource_type ||= "image"
188
268
  source = source.to_s
189
269
  if !force_remote
190
- return original_source if (type.nil? || type == :asset) && source.match(%r(^https?:/)i)
270
+ return original_source if (type.nil? || type == "asset") && source.match(%r(^https?:/)i)
191
271
  if source.start_with?("/")
192
272
  if source.start_with?("/images/")
193
273
  source = source.sub(%r(/images/), '')
@@ -196,11 +276,11 @@ class Cloudinary::Utils
196
276
  end
197
277
  end
198
278
  @metadata ||= defined?(Cloudinary::Static) ? Cloudinary::Static.metadata : {}
199
- if type == :asset && @metadata["images/#{source}"]
279
+ if type == "asset" && @metadata["images/#{source}"]
200
280
  return original_source if !Cloudinary.config.static_image_support
201
281
  source = @metadata["images/#{source}"]["public_id"]
202
282
  source += File.extname(original_source) if !format
203
- elsif type == :asset
283
+ elsif type == "asset"
204
284
  return original_source # requested asset, but no metadata - probably local file. return.
205
285
  end
206
286
  end
@@ -214,11 +294,16 @@ class Cloudinary::Utils
214
294
  transformation = transformation.gsub(%r(([^:])//), '\1/')
215
295
  if sign_url
216
296
  to_sign = [transformation, sign_version && version, source_to_sign].reject(&:blank?).join("/")
297
+ i = 0
298
+ while to_sign != CGI.unescape(to_sign) && i <10
299
+ to_sign = CGI.unescape(to_sign)
300
+ i = i + 1
301
+ end
217
302
  signature = 's--' + Base64.urlsafe_encode64(Digest::SHA1.digest(to_sign + secret))[0,8] + '--'
218
303
  end
219
304
 
220
305
  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("/")
306
+ source = [prefix, resource_type, type, signature, transformation, version, source].reject(&:blank?).join("/")
222
307
  end
223
308
 
224
309
  def self.finalize_source(source, format, url_suffix)
@@ -369,8 +454,8 @@ class Cloudinary::Utils
369
454
  end
370
455
 
371
456
  # Based on CGI::unescape. In addition does not escape / :
372
- def self.smart_escape(string)
373
- string.gsub(/([^a-zA-Z0-9_.\-\/:]+)/) do
457
+ def self.smart_escape(string, unsafe = /([^a-zA-Z0-9_.\-\/:]+)/)
458
+ string.gsub(unsafe) do
374
459
  '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
375
460
  end
376
461
  end
@@ -468,18 +553,60 @@ class Cloudinary::Utils
468
553
  value.nil? || value == "" || value == []
469
554
  end
470
555
 
556
+ def self.symbolize_keys(h)
557
+ new_h = Hash.new
558
+ if (h.respond_to? :keys)
559
+ h.keys.each do |key|
560
+ new_h[(key.to_sym rescue key)] = h[key]
561
+ end
562
+ end
563
+ new_h
564
+ end
565
+
566
+
567
+ def self.symbolize_keys!(h)
568
+ if (h.respond_to? :keys) && (h.respond_to? :delete)
569
+ h.keys.each do |key|
570
+ value = h.delete(key)
571
+ h[(key.to_sym rescue key)] = value
572
+ end
573
+ end
574
+ h
575
+ end
576
+
577
+
578
+ def self.deep_symbolize_keys(object)
579
+ case object
580
+ when Hash
581
+ result = {}
582
+ object.each do |key, value|
583
+ key = key.to_sym rescue key
584
+ result[key] = deep_symbolize_keys(value)
585
+ end
586
+ result
587
+ when Array
588
+ object.map{|e| deep_symbolize_keys(e)}
589
+ else
590
+ object
591
+ end
592
+ end
593
+
471
594
  private
595
+
472
596
  def self.number_pattern
473
597
  "([0-9]*)\\.([0-9]+)|([0-9]+)"
474
598
  end
599
+ private_class_method :number_pattern
475
600
 
476
601
  def self.offset_any_pattern
477
602
  "(#{number_pattern})([%pP])?"
478
603
  end
604
+ private_class_method :offset_any_pattern
479
605
 
480
606
  def self.offset_any_pattern_re
481
607
  /((([0-9]*)\.([0-9]+)|([0-9]+))([%pP])?)\.\.((([0-9]*)\.([0-9]+)|([0-9]+))([%pP])?)/
482
608
  end
609
+ private_class_method :offset_any_pattern_re
483
610
 
484
611
  # Split a range into the start and end values
485
612
  def self.split_range(range) # :nodoc:
@@ -494,10 +621,12 @@ class Cloudinary::Utils
494
621
  nil
495
622
  end
496
623
  end
624
+ private_class_method :split_range
497
625
 
498
626
  # Normalize an offset value
499
627
  # @param [String] value a decimal value which may have a 'p' or '%' postfix. E.g. '35%', '0.4p'
500
628
  # @return [Object|String] a normalized String of the input value if possible otherwise the value itself
629
+ # @private
501
630
  def self.norm_range_value(value) # :nodoc:
502
631
  offset = /^#{offset_any_pattern}$/.match( value.to_s)
503
632
  if offset
@@ -506,6 +635,7 @@ class Cloudinary::Utils
506
635
  end
507
636
  value
508
637
  end
638
+ private_class_method :norm_range_value
509
639
 
510
640
  # A video codec parameter can be either a String or a Hash.
511
641
  #
@@ -514,6 +644,7 @@ class Cloudinary::Utils
514
644
  # @return [String] <code><codec> : <profile> : [<level>]]</code> if a Hash was provided
515
645
  # or the param if a String was provided.
516
646
  # Returns NIL if param is not a Hash or String
647
+ # @private
517
648
  def self.process_video_params(param)
518
649
  case param
519
650
  when Hash
@@ -534,21 +665,6 @@ class Cloudinary::Utils
534
665
  nil
535
666
  end
536
667
  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
668
+ private_class_method :process_video_params
553
669
 
554
670
  end