carrierwave 0.11.2 → 3.0.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of carrierwave might be problematic. Click here for more details.

Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +452 -178
  3. data/lib/carrierwave/compatibility/paperclip.rb +4 -4
  4. data/lib/carrierwave/downloader/base.rb +101 -0
  5. data/lib/carrierwave/downloader/remote_file.rb +68 -0
  6. data/lib/carrierwave/error.rb +1 -0
  7. data/lib/carrierwave/locale/en.yml +11 -5
  8. data/lib/carrierwave/mount.rb +212 -182
  9. data/lib/carrierwave/mounter.rb +255 -0
  10. data/lib/carrierwave/orm/activerecord.rb +22 -33
  11. data/lib/carrierwave/processing/mini_magick.rb +140 -84
  12. data/lib/carrierwave/processing/rmagick.rb +72 -21
  13. data/lib/carrierwave/processing/vips.rb +284 -0
  14. data/lib/carrierwave/processing.rb +1 -1
  15. data/lib/carrierwave/sanitized_file.rb +83 -84
  16. data/lib/carrierwave/storage/abstract.rb +16 -3
  17. data/lib/carrierwave/storage/file.rb +71 -3
  18. data/lib/carrierwave/storage/fog.rb +215 -57
  19. data/lib/carrierwave/storage.rb +1 -9
  20. data/lib/carrierwave/test/matchers.rb +88 -19
  21. data/lib/carrierwave/uploader/cache.rb +75 -45
  22. data/lib/carrierwave/uploader/callbacks.rb +1 -3
  23. data/lib/carrierwave/uploader/configuration.rb +80 -16
  24. data/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
  25. data/lib/carrierwave/uploader/content_type_denylist.rb +62 -0
  26. data/lib/carrierwave/uploader/default_url.rb +3 -5
  27. data/lib/carrierwave/uploader/dimension.rb +66 -0
  28. data/lib/carrierwave/uploader/download.rb +4 -74
  29. data/lib/carrierwave/uploader/extension_allowlist.rb +63 -0
  30. data/lib/carrierwave/uploader/extension_denylist.rb +64 -0
  31. data/lib/carrierwave/uploader/file_size.rb +43 -0
  32. data/lib/carrierwave/uploader/mountable.rb +13 -8
  33. data/lib/carrierwave/uploader/processing.rb +48 -13
  34. data/lib/carrierwave/uploader/proxy.rb +20 -9
  35. data/lib/carrierwave/uploader/remove.rb +0 -2
  36. data/lib/carrierwave/uploader/serialization.rb +2 -4
  37. data/lib/carrierwave/uploader/store.rb +59 -28
  38. data/lib/carrierwave/uploader/url.rb +8 -7
  39. data/lib/carrierwave/uploader/versions.rb +170 -122
  40. data/lib/carrierwave/uploader.rb +12 -10
  41. data/lib/carrierwave/utilities/file_name.rb +47 -0
  42. data/lib/carrierwave/utilities/uri.rb +14 -12
  43. data/lib/carrierwave/utilities.rb +1 -3
  44. data/lib/carrierwave/validations/active_model.rb +7 -11
  45. data/lib/carrierwave/version.rb +1 -1
  46. data/lib/carrierwave.rb +39 -21
  47. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +5 -9
  48. data/lib/generators/uploader_generator.rb +3 -3
  49. metadata +132 -80
  50. data/lib/carrierwave/locale/cs.yml +0 -11
  51. data/lib/carrierwave/locale/de.yml +0 -11
  52. data/lib/carrierwave/locale/el.yml +0 -11
  53. data/lib/carrierwave/locale/es.yml +0 -11
  54. data/lib/carrierwave/locale/fr.yml +0 -11
  55. data/lib/carrierwave/locale/ja.yml +0 -11
  56. data/lib/carrierwave/locale/nb.yml +0 -11
  57. data/lib/carrierwave/locale/nl.yml +0 -11
  58. data/lib/carrierwave/locale/pl.yml +0 -11
  59. data/lib/carrierwave/locale/pt-BR.yml +0 -11
  60. data/lib/carrierwave/locale/pt-PT.yml +0 -11
  61. data/lib/carrierwave/locale/ru.yml +0 -11
  62. data/lib/carrierwave/locale/sk.yml +0 -11
  63. data/lib/carrierwave/locale/tr.yml +0 -11
  64. data/lib/carrierwave/processing/mime_types.rb +0 -74
  65. data/lib/carrierwave/uploader/content_type_blacklist.rb +0 -48
  66. data/lib/carrierwave/uploader/content_type_whitelist.rb +0 -48
  67. data/lib/carrierwave/uploader/extension_blacklist.rb +0 -47
  68. data/lib/carrierwave/uploader/extension_whitelist.rb +0 -49
  69. data/lib/carrierwave/utilities/deprecation.rb +0 -18
@@ -0,0 +1,62 @@
1
+ module CarrierWave
2
+ module Uploader
3
+ module ContentTypeAllowlist
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before :cache, :check_content_type_allowlist!
8
+ end
9
+
10
+ ##
11
+ # Override this method in your uploader to provide an allowlist of files content types
12
+ # which are allowed to be uploaded.
13
+ # Not only strings but Regexp are allowed as well.
14
+ #
15
+ # === Returns
16
+ #
17
+ # [NilClass, String, Regexp, Array[String, Regexp]] an allowlist of content types which are allowed to be uploaded
18
+ #
19
+ # === Examples
20
+ #
21
+ # def content_type_allowlist
22
+ # %w(text/json application/json)
23
+ # end
24
+ #
25
+ # Basically the same, but using a Regexp:
26
+ #
27
+ # def content_type_allowlist
28
+ # [/(text|application)\/json/]
29
+ # end
30
+ #
31
+ def content_type_allowlist
32
+ end
33
+
34
+ private
35
+
36
+ def check_content_type_allowlist!(new_file)
37
+ allowlist = content_type_allowlist
38
+ if !allowlist && respond_to?(:content_type_whitelist) && content_type_whitelist
39
+ ActiveSupport::Deprecation.warn "#content_type_whitelist is deprecated, use #content_type_allowlist instead." unless instance_variable_defined?(:@content_type_whitelist_warned)
40
+ @content_type_whitelist_warned = true
41
+ allowlist = content_type_whitelist
42
+ end
43
+
44
+ return unless allowlist
45
+
46
+ content_type = new_file.content_type
47
+ if !allowlisted_content_type?(allowlist, content_type)
48
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.content_type_allowlist_error", content_type: content_type,
49
+ allowed_types: Array(allowlist).join(", "), default: :"errors.messages.content_type_whitelist_error")
50
+ end
51
+ end
52
+
53
+ def allowlisted_content_type?(allowlist, content_type)
54
+ Array(allowlist).any? do |item|
55
+ item = Regexp.quote(item) if item.class != Regexp
56
+ content_type =~ /#{item}/
57
+ end
58
+ end
59
+
60
+ end # ContentTypeAllowlist
61
+ end # Uploader
62
+ end # CarrierWave
@@ -0,0 +1,62 @@
1
+ module CarrierWave
2
+ module Uploader
3
+ module ContentTypeDenylist
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before :cache, :check_content_type_denylist!
8
+ end
9
+
10
+ ##
11
+ # Override this method in your uploader to provide a denylist of files content types
12
+ # which are not allowed to be uploaded.
13
+ # Not only strings but Regexp are allowed as well.
14
+ #
15
+ # === Returns
16
+ #
17
+ # [NilClass, String, Regexp, Array[String, Regexp]] a denylist of content types which are not allowed to be uploaded
18
+ #
19
+ # === Examples
20
+ #
21
+ # def content_type_denylist
22
+ # %w(text/json application/json)
23
+ # end
24
+ #
25
+ # Basically the same, but using a Regexp:
26
+ #
27
+ # def content_type_denylist
28
+ # [/(text|application)\/json/]
29
+ # end
30
+ #
31
+ def content_type_denylist
32
+ end
33
+
34
+ private
35
+
36
+ def check_content_type_denylist!(new_file)
37
+ denylist = content_type_denylist
38
+ if !denylist && respond_to?(:content_type_blacklist) && content_type_blacklist
39
+ ActiveSupport::Deprecation.warn "#content_type_blacklist is deprecated, use #content_type_denylist instead." unless instance_variable_defined?(:@content_type_blacklist_warned)
40
+ @content_type_blacklist_warned = true
41
+ denylist = content_type_blacklist
42
+ end
43
+
44
+ return unless denylist
45
+
46
+ ActiveSupport::Deprecation.warn "Use of #content_type_denylist is deprecated for the security reason, use #content_type_allowlist instead to explicitly state what are safe to accept" unless instance_variable_defined?(:@content_type_denylist_warned)
47
+ @content_type_denylist_warned = true
48
+
49
+ content_type = new_file.content_type
50
+ if denylisted_content_type?(denylist, content_type)
51
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.content_type_denylist_error",
52
+ content_type: content_type, default: :"errors.messages.content_type_blacklist_error")
53
+ end
54
+ end
55
+
56
+ def denylisted_content_type?(denylist, content_type)
57
+ Array(denylist).any? { |item| content_type =~ /#{item}/ }
58
+ end
59
+
60
+ end # ContentTypeDenylist
61
+ end # Uploader
62
+ end # CarrierWave
@@ -1,19 +1,17 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Uploader
5
3
  module DefaultUrl
6
4
 
7
5
  def url(*args)
8
- super || default_url
6
+ super || default_url(*args)
9
7
  end
10
8
 
11
9
  ##
12
10
  # Override this method in your uploader to provide a default url
13
11
  # in case no file has been cached/stored yet.
14
12
  #
15
- def default_url; end
13
+ def default_url(*args); end
16
14
 
17
15
  end # DefaultPath
18
16
  end # Uploader
19
- end # CarrierWave
17
+ end # CarrierWave
@@ -0,0 +1,66 @@
1
+ require 'active_support'
2
+
3
+ module CarrierWave
4
+ module Uploader
5
+ module Dimension
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ before :cache, :check_dimensions!
10
+ end
11
+
12
+ ##
13
+ # Override this method in your uploader to provide a Range of width which
14
+ # are allowed to be uploaded.
15
+ # === Returns
16
+ #
17
+ # [NilClass, Range] a width range which are permitted to be uploaded
18
+ #
19
+ # === Examples
20
+ #
21
+ # def width_range
22
+ # 1000..2000
23
+ # end
24
+ #
25
+ def width_range; end
26
+
27
+ ##
28
+ # Override this method in your uploader to provide a Range of height which
29
+ # are allowed to be uploaded.
30
+ # === Returns
31
+ #
32
+ # [NilClass, Range] a height range which are permitted to be uploaded
33
+ #
34
+ # === Examples
35
+ #
36
+ # def height_range
37
+ # 1000..
38
+ # end
39
+ #
40
+ def height_range; end
41
+
42
+ private
43
+
44
+ def check_dimensions!(new_file)
45
+ # NOTE: Skip the check for resized images
46
+ return if version_name.present?
47
+ return unless width_range || height_range
48
+
49
+ unless respond_to?(:width) || respond_to?(:height)
50
+ raise 'You need to include one of CarrierWave::MiniMagick, CarrierWave::RMagick, or CarrierWave::Vips to perform image dimension validation'
51
+ end
52
+
53
+ if width_range&.begin && width < width_range.begin
54
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.min_width_error", :min_width => ActiveSupport::NumberHelper.number_to_delimited(width_range.begin))
55
+ elsif width_range&.end && width > width_range.end
56
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.max_width_error", :max_width => ActiveSupport::NumberHelper.number_to_delimited(width_range.end))
57
+ elsif height_range&.begin && height < height_range.begin
58
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.min_height_error", :min_height => ActiveSupport::NumberHelper.number_to_delimited(height_range.begin))
59
+ elsif height_range&.end && height > height_range.end
60
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.max_height_error", :max_height => ActiveSupport::NumberHelper.number_to_delimited(height_range.end))
61
+ end
62
+ end
63
+
64
+ end # Dimension
65
+ end # Uploader
66
+ end # CarrierWave
@@ -1,7 +1,3 @@
1
- # encoding: utf-8
2
-
3
- require 'open-uri'
4
-
5
1
  module CarrierWave
6
2
  module Uploader
7
3
  module Download
@@ -11,84 +7,18 @@ module CarrierWave
11
7
  include CarrierWave::Uploader::Configuration
12
8
  include CarrierWave::Uploader::Cache
13
9
 
14
- class RemoteFile
15
- def initialize(uri)
16
- @uri = uri
17
- end
18
-
19
- def original_filename
20
- filename = filename_from_header || File.basename(file.base_uri.path)
21
- mime_type = MIME::Types[file.content_type].first
22
- unless File.extname(filename).present? || mime_type.blank?
23
- filename = "#{filename}.#{mime_type.extensions.first}"
24
- end
25
- filename
26
- end
27
-
28
- def respond_to?(*args)
29
- super or file.respond_to?(*args)
30
- end
31
-
32
- def http?
33
- @uri.scheme =~ /^https?$/
34
- end
35
-
36
- private
37
-
38
- def file
39
- if @file.blank?
40
- @file = Kernel.open(@uri.to_s)
41
- @file = @file.is_a?(String) ? StringIO.new(@file) : @file
42
- end
43
- @file
44
-
45
- rescue Exception => e
46
- raise CarrierWave::DownloadError, "could not download file: #{e.message}"
47
- end
48
-
49
- def filename_from_header
50
- if file.meta.include? 'content-disposition'
51
- match = file.meta['content-disposition'].match(/filename="?([^"]+)/)
52
- return match[1] unless match.nil? || match[1].empty?
53
- end
54
- end
55
-
56
- def method_missing(*args, &block)
57
- file.send(*args, &block)
58
- end
59
- end
60
-
61
10
  ##
62
- # Caches the file by downloading it from the given URL.
11
+ # Caches the file by downloading it from the given URL, using downloader.
63
12
  #
64
13
  # === Parameters
65
14
  #
66
15
  # [url (String)] The URL where the remote file is stored
16
+ # [remote_headers (Hash)] Request headers
67
17
  #
68
- def download!(uri)
69
- processed_uri = process_uri(uri)
70
- file = RemoteFile.new(processed_uri)
71
- raise CarrierWave::DownloadError, "trying to download a file which is not served over HTTP" unless file.http?
18
+ def download!(uri, remote_headers = {})
19
+ file = downloader.new(self).download(uri, remote_headers)
72
20
  cache!(file)
73
21
  end
74
-
75
- ##
76
- # Processes the given URL by parsing and escaping it. Public to allow overriding.
77
- #
78
- # === Parameters
79
- #
80
- # [url (String)] The URL where the remote file is stored
81
- #
82
- def process_uri(uri)
83
- URI.parse(uri)
84
- rescue URI::InvalidURIError
85
- uri_parts = uri.split('?')
86
- # regexp from Ruby's URI::Parser#regexp[:UNSAFE], with [] specifically removed
87
- encoded_uri = URI.encode(uri_parts.shift, /[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,]/)
88
- encoded_uri << '?' << URI.encode(uri_parts.join('?')) if uri_parts.any?
89
- URI.parse(encoded_uri) rescue raise CarrierWave::DownloadError, "couldn't parse URL"
90
- end
91
-
92
22
  end # Download
93
23
  end # Uploader
94
24
  end # CarrierWave
@@ -0,0 +1,63 @@
1
+ module CarrierWave
2
+ module Uploader
3
+ module ExtensionAllowlist
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before :cache, :check_extension_allowlist!
8
+ end
9
+
10
+ ##
11
+ # Override this method in your uploader to provide an allowlist of extensions which
12
+ # are allowed to be uploaded. Compares the file's extension case insensitive.
13
+ # Furthermore, not only strings but Regexp are allowed as well.
14
+ #
15
+ # When using a Regexp in the allowlist, `\A` and `\z` are automatically added to
16
+ # the Regexp expression, also case insensitive.
17
+ #
18
+ # === Returns
19
+ #
20
+ # [NilClass, String, Regexp, Array[String, Regexp]] an allowlist of extensions which are allowed to be uploaded
21
+ #
22
+ # === Examples
23
+ #
24
+ # def extension_allowlist
25
+ # %w(jpg jpeg gif png)
26
+ # end
27
+ #
28
+ # Basically the same, but using a Regexp:
29
+ #
30
+ # def extension_allowlist
31
+ # [/jpe?g/, 'gif', 'png']
32
+ # end
33
+ #
34
+ def extension_allowlist
35
+ end
36
+
37
+ private
38
+
39
+ def check_extension_allowlist!(new_file)
40
+ allowlist = extension_allowlist
41
+ if !allowlist && respond_to?(:extension_whitelist) && extension_whitelist
42
+ ActiveSupport::Deprecation.warn "#extension_whitelist is deprecated, use #extension_allowlist instead." unless instance_variable_defined?(:@extension_whitelist_warned)
43
+ @extension_whitelist_warned = true
44
+ allowlist = extension_whitelist
45
+ end
46
+
47
+ return unless allowlist
48
+
49
+ extension = new_file.extension.to_s
50
+ if !allowlisted_extension?(allowlist, extension)
51
+ # Look for whitelist first, then fallback to allowlist
52
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.extension_allowlist_error", extension: new_file.extension.inspect,
53
+ allowed_types: Array(allowlist).join(", "), default: :"errors.messages.extension_whitelist_error")
54
+ end
55
+ end
56
+
57
+ def allowlisted_extension?(allowlist, extension)
58
+ downcase_extension = extension.downcase
59
+ Array(allowlist).any? { |item| downcase_extension =~ /\A#{item}\z/i }
60
+ end
61
+ end # ExtensionAllowlist
62
+ end # Uploader
63
+ end # CarrierWave
@@ -0,0 +1,64 @@
1
+ module CarrierWave
2
+ module Uploader
3
+ module ExtensionDenylist
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before :cache, :check_extension_denylist!
8
+ end
9
+
10
+ ##
11
+ # Override this method in your uploader to provide a denylist of extensions which
12
+ # are prohibited to be uploaded. Compares the file's extension case insensitive.
13
+ # Furthermore, not only strings but Regexp are allowed as well.
14
+ #
15
+ # When using a Regexp in the denylist, `\A` and `\z` are automatically added to
16
+ # the Regexp expression, also case insensitive.
17
+ #
18
+ # === Returns
19
+
20
+ # [NilClass, String, Regexp, Array[String, Regexp]] a deny list of extensions which are prohibited to be uploaded
21
+ #
22
+ # === Examples
23
+ #
24
+ # def extension_denylist
25
+ # %w(swf tiff)
26
+ # end
27
+ #
28
+ # Basically the same, but using a Regexp:
29
+ #
30
+ # def extension_denylist
31
+ # [/swf/, 'tiff']
32
+ # end
33
+ #
34
+ def extension_denylist
35
+ end
36
+
37
+ private
38
+
39
+ def check_extension_denylist!(new_file)
40
+ denylist = extension_denylist
41
+ if !denylist && respond_to?(:extension_blacklist) && extension_blacklist
42
+ ActiveSupport::Deprecation.warn "#extension_blacklist is deprecated, use #extension_denylist instead." unless instance_variable_defined?(:@extension_blacklist_warned)
43
+ @extension_blacklist_warned = true
44
+ denylist = extension_blacklist
45
+ end
46
+
47
+ return unless denylist
48
+
49
+ ActiveSupport::Deprecation.warn "Use of #extension_denylist is deprecated for the security reason, use #extension_allowlist instead to explicitly state what are safe to accept" unless instance_variable_defined?(:@extension_denylist_warned)
50
+ @extension_denylist_warned = true
51
+
52
+ extension = new_file.extension.to_s
53
+ if denylisted_extension?(denylist, extension)
54
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.extension_denylist_error", extension: new_file.extension.inspect,
55
+ prohibited_types: Array(extension_denylist).join(", "), default: :"errors.messages.extension_blacklist_error")
56
+ end
57
+ end
58
+
59
+ def denylisted_extension?(denylist, extension)
60
+ Array(denylist).any? { |item| extension =~ /\A#{item}\z/i }
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,43 @@
1
+ require 'active_support'
2
+
3
+ module CarrierWave
4
+ module Uploader
5
+ module FileSize
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ before :cache, :check_size!
10
+ end
11
+
12
+ ##
13
+ # Override this method in your uploader to provide a Range of Size which
14
+ # are allowed to be uploaded.
15
+ # === Returns
16
+ #
17
+ # [NilClass, Range] a size range (in bytes) which are permitted to be uploaded
18
+ #
19
+ # === Examples
20
+ #
21
+ # def size_range
22
+ # 3256...5748
23
+ # end
24
+ #
25
+ def size_range; end
26
+
27
+ private
28
+
29
+ def check_size!(new_file)
30
+ size = new_file.size
31
+ expected_size_range = size_range
32
+ if expected_size_range.is_a?(::Range)
33
+ if size < expected_size_range.min
34
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.min_size_error", :min_size => ActiveSupport::NumberHelper.number_to_human_size(expected_size_range.min))
35
+ elsif size > expected_size_range.max
36
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.max_size_error", :max_size => ActiveSupport::NumberHelper.number_to_human_size(expected_size_range.max))
37
+ end
38
+ end
39
+ end
40
+
41
+ end # FileSize
42
+ end # Uploader
43
+ end # CarrierWave
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Uploader
5
3
  module Mountable
@@ -7,13 +5,14 @@ module CarrierWave
7
5
  attr_reader :model, :mounted_as
8
6
 
9
7
  ##
10
- # If a model is given as the first parameter, it will be stored in the uploader, and
11
- # available throught +#model+. Likewise, mounted_as stores the name of the column
12
- # where this instance of the uploader is mounted. These values can then be used inside
13
- # your uploader.
8
+ # If a model is given as the first parameter, it will be stored in the
9
+ # uploader, and available through +#model+. Likewise, mounted_as stores
10
+ # the name of the column where this instance of the uploader is mounted.
11
+ # These values can then be used inside your uploader.
14
12
  #
15
- # If you do not wish to mount your uploaders with the ORM extensions in -more then you
16
- # can override this method inside your uploader. Just be sure to call +super+
13
+ # If you do not wish to mount your uploaders with the ORM extensions in
14
+ # -more then you can override this method inside your uploader. Just be
15
+ # sure to call +super+
17
16
  #
18
17
  # === Parameters
19
18
  #
@@ -34,6 +33,12 @@ module CarrierWave
34
33
  @mounted_as = mounted_as
35
34
  end
36
35
 
36
+ ##
37
+ # Returns array index of given uploader within currently mounted uploaders
38
+ #
39
+ def index
40
+ model.__send__(:_mounter, mounted_as).uploaders.index(self)
41
+ end
37
42
  end # Mountable
38
43
  end # Uploader
39
44
  end # CarrierWave
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Uploader
5
3
  module Processing
@@ -11,7 +9,7 @@ module CarrierWave
11
9
  class_attribute :processors, :instance_writer => false
12
10
  self.processors = []
13
11
 
14
- after :cache, :process!
12
+ before :cache, :process!
15
13
  end
16
14
 
17
15
  module ClassMethods
@@ -20,7 +18,7 @@ module CarrierWave
20
18
  # Adds a processor callback which applies operations as a file is uploaded.
21
19
  # The argument may be the name of any method of the uploader, expressed as a symbol,
22
20
  # or a list of such methods, or a hash where the key is a method and the value is
23
- # an array of arguments to call the method with
21
+ # an array of arguments to call the method with. Also accepts an :if or :unless condition
24
22
  #
25
23
  # === Parameters
26
24
  #
@@ -33,6 +31,7 @@ module CarrierWave
33
31
  # process :sepiatone, :vignette
34
32
  # process :scale => [200, 200]
35
33
  # process :scale => [200, 200], :if => :image?
34
+ # process :scale => [200, 200], :unless => :disallowed_image_type?
36
35
  # process :sepiatone, :if => :image?
37
36
  #
38
37
  # def sepiatone
@@ -51,6 +50,10 @@ module CarrierWave
51
50
  # ...
52
51
  # end
53
52
  #
53
+ # def disallowed_image_type?
54
+ # ...
55
+ # end
56
+ #
54
57
  # end
55
58
  #
56
59
  def process(*args)
@@ -59,12 +62,17 @@ module CarrierWave
59
62
  hash.merge!(arg)
60
63
  end
61
64
 
62
- condition = new_processors.delete(:if)
65
+ condition_type = new_processors.keys.detect { |key| [:if, :unless].include?(key) }
66
+ condition = new_processors.delete(:if) || new_processors.delete(:unless)
63
67
  new_processors.each do |processor, processor_args|
64
- self.processors += [[processor, processor_args, condition]]
68
+ self.processors += [[processor, processor_args, condition, condition_type]]
69
+
70
+ if processor == :convert
71
+ # Treat :convert specially, since it should trigger the file extension change
72
+ force_extension processor_args
73
+ end
65
74
  end
66
75
  end
67
-
68
76
  end # ClassMethods
69
77
 
70
78
  ##
@@ -73,18 +81,45 @@ module CarrierWave
73
81
  def process!(new_file=nil)
74
82
  return unless enable_processing
75
83
 
76
- self.class.processors.each do |method, args, condition|
77
- if(condition)
78
- if condition.respond_to?(:call)
79
- next unless condition.call(self, :args => args, :method => method, :file => new_file)
84
+ with_callbacks(:process, new_file) do
85
+ self.class.processors.each do |method, args, condition, condition_type|
86
+ if condition && condition_type == :if
87
+ if condition.respond_to?(:call)
88
+ next unless condition.call(self, :args => args, :method => method, :file => new_file)
89
+ else
90
+ next unless self.send(condition, new_file)
91
+ end
92
+ elsif condition && condition_type == :unless
93
+ if condition.respond_to?(:call)
94
+ next if condition.call(self, :args => args, :method => method, :file => new_file)
95
+ elsif self.send(condition, new_file)
96
+ next
97
+ end
98
+ end
99
+
100
+ if args.is_a? Array
101
+ kwargs, args = args.partition { |arg| arg.is_a? Hash }
102
+ end
103
+
104
+ if kwargs.present?
105
+ kwargs = kwargs.reduce(:merge)
106
+ self.send(method, *args, **kwargs)
80
107
  else
81
- next unless self.send(condition, new_file)
108
+ self.send(method, *args)
82
109
  end
83
110
  end
84
- self.send(method, *args)
85
111
  end
86
112
  end
87
113
 
114
+ private
115
+
116
+ def forcing_extension(filename)
117
+ if force_extension && filename
118
+ Pathname.new(filename).sub_ext(".#{force_extension.to_s.delete_prefix('.')}").to_s
119
+ else
120
+ filename
121
+ end
122
+ end
88
123
  end # Processing
89
124
  end # Uploader
90
125
  end # CarrierWave