paperclip-fix 4.3.7

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.
Files changed (197) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.hound.yml +1066 -0
  4. data/.rubocop.yml +1 -0
  5. data/.travis.yml +22 -0
  6. data/Appraisals +11 -0
  7. data/CONTRIBUTING.md +75 -0
  8. data/Gemfile +21 -0
  9. data/LICENSE +24 -0
  10. data/NEWS +420 -0
  11. data/README.md +979 -0
  12. data/RELEASING.md +17 -0
  13. data/Rakefile +44 -0
  14. data/UPGRADING +14 -0
  15. data/cucumber/paperclip_steps.rb +6 -0
  16. data/features/basic_integration.feature +80 -0
  17. data/features/migration.feature +94 -0
  18. data/features/rake_tasks.feature +62 -0
  19. data/features/step_definitions/attachment_steps.rb +110 -0
  20. data/features/step_definitions/html_steps.rb +15 -0
  21. data/features/step_definitions/rails_steps.rb +236 -0
  22. data/features/step_definitions/s3_steps.rb +14 -0
  23. data/features/step_definitions/web_steps.rb +107 -0
  24. data/features/support/env.rb +11 -0
  25. data/features/support/fakeweb.rb +13 -0
  26. data/features/support/file_helpers.rb +34 -0
  27. data/features/support/fixtures/boot_config.txt +15 -0
  28. data/features/support/fixtures/gemfile.txt +5 -0
  29. data/features/support/fixtures/preinitializer.txt +20 -0
  30. data/features/support/paths.rb +28 -0
  31. data/features/support/rails.rb +63 -0
  32. data/features/support/selectors.rb +19 -0
  33. data/gemfiles/3.2.gemfile +19 -0
  34. data/gemfiles/4.1.gemfile +19 -0
  35. data/gemfiles/4.2.gemfile +19 -0
  36. data/lib/generators/paperclip/USAGE +8 -0
  37. data/lib/generators/paperclip/paperclip_generator.rb +30 -0
  38. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  39. data/lib/paperclip/attachment.rb +608 -0
  40. data/lib/paperclip/attachment_registry.rb +59 -0
  41. data/lib/paperclip/callbacks.rb +40 -0
  42. data/lib/paperclip/content_type_detector.rb +79 -0
  43. data/lib/paperclip/deprecations.rb +42 -0
  44. data/lib/paperclip/errors.rb +32 -0
  45. data/lib/paperclip/file_command_content_type_detector.rb +30 -0
  46. data/lib/paperclip/filename_cleaner.rb +16 -0
  47. data/lib/paperclip/geometry.rb +158 -0
  48. data/lib/paperclip/geometry_detector_factory.rb +48 -0
  49. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  50. data/lib/paperclip/glue.rb +17 -0
  51. data/lib/paperclip/has_attached_file.rb +109 -0
  52. data/lib/paperclip/helpers.rb +56 -0
  53. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  54. data/lib/paperclip/interpolations.rb +197 -0
  55. data/lib/paperclip/io_adapters/abstract_adapter.rb +47 -0
  56. data/lib/paperclip/io_adapters/attachment_adapter.rb +36 -0
  57. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  58. data/lib/paperclip/io_adapters/empty_string_adapter.rb +18 -0
  59. data/lib/paperclip/io_adapters/file_adapter.rb +22 -0
  60. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +15 -0
  61. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -0
  62. data/lib/paperclip/io_adapters/nil_adapter.rb +34 -0
  63. data/lib/paperclip/io_adapters/registry.rb +32 -0
  64. data/lib/paperclip/io_adapters/stringio_adapter.rb +33 -0
  65. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +42 -0
  66. data/lib/paperclip/io_adapters/uri_adapter.rb +63 -0
  67. data/lib/paperclip/locales/de.yml +18 -0
  68. data/lib/paperclip/locales/en.yml +18 -0
  69. data/lib/paperclip/locales/es.yml +18 -0
  70. data/lib/paperclip/locales/ja.yml +18 -0
  71. data/lib/paperclip/locales/pt-BR.yml +18 -0
  72. data/lib/paperclip/locales/zh-CN.yml +18 -0
  73. data/lib/paperclip/locales/zh-HK.yml +18 -0
  74. data/lib/paperclip/locales/zh-TW.yml +18 -0
  75. data/lib/paperclip/logger.rb +21 -0
  76. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  77. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +100 -0
  78. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  79. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +96 -0
  80. data/lib/paperclip/matchers.rb +64 -0
  81. data/lib/paperclip/media_type_spoof_detector.rb +89 -0
  82. data/lib/paperclip/missing_attachment_styles.rb +79 -0
  83. data/lib/paperclip/processor.rb +48 -0
  84. data/lib/paperclip/processor_helpers.rb +50 -0
  85. data/lib/paperclip/rails_environment.rb +25 -0
  86. data/lib/paperclip/railtie.rb +31 -0
  87. data/lib/paperclip/schema.rb +83 -0
  88. data/lib/paperclip/storage/filesystem.rb +90 -0
  89. data/lib/paperclip/storage/fog.rb +241 -0
  90. data/lib/paperclip/storage/s3.rb +440 -0
  91. data/lib/paperclip/storage.rb +3 -0
  92. data/lib/paperclip/style.rb +109 -0
  93. data/lib/paperclip/tempfile.rb +43 -0
  94. data/lib/paperclip/tempfile_factory.rb +23 -0
  95. data/lib/paperclip/thumbnail.rb +121 -0
  96. data/lib/paperclip/url_generator.rb +72 -0
  97. data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
  98. data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
  99. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
  100. data/lib/paperclip/validators/attachment_presence_validator.rb +30 -0
  101. data/lib/paperclip/validators/attachment_size_validator.rb +115 -0
  102. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +27 -0
  103. data/lib/paperclip/validators.rb +74 -0
  104. data/lib/paperclip/version.rb +3 -0
  105. data/lib/paperclip.rb +213 -0
  106. data/lib/tasks/paperclip.rake +127 -0
  107. data/paperclip.gemspec +51 -0
  108. data/shoulda_macros/paperclip.rb +134 -0
  109. data/spec/database.yml +4 -0
  110. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  111. data/spec/paperclip/attachment_processing_spec.rb +82 -0
  112. data/spec/paperclip/attachment_registry_spec.rb +130 -0
  113. data/spec/paperclip/attachment_spec.rb +1494 -0
  114. data/spec/paperclip/content_type_detector_spec.rb +48 -0
  115. data/spec/paperclip/deprecations_spec.rb +65 -0
  116. data/spec/paperclip/file_command_content_type_detector_spec.rb +26 -0
  117. data/spec/paperclip/filename_cleaner_spec.rb +14 -0
  118. data/spec/paperclip/geometry_detector_spec.rb +39 -0
  119. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  120. data/spec/paperclip/geometry_spec.rb +255 -0
  121. data/spec/paperclip/glue_spec.rb +44 -0
  122. data/spec/paperclip/has_attached_file_spec.rb +142 -0
  123. data/spec/paperclip/integration_spec.rb +667 -0
  124. data/spec/paperclip/interpolations_spec.rb +262 -0
  125. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +78 -0
  126. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +139 -0
  127. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +83 -0
  128. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  129. data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
  130. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +104 -0
  131. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  132. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
  133. data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
  134. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  135. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  136. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +127 -0
  137. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  138. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +99 -0
  139. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  140. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  141. data/spec/paperclip/media_type_spoof_detector_spec.rb +79 -0
  142. data/spec/paperclip/meta_class_spec.rb +30 -0
  143. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +84 -0
  144. data/spec/paperclip/paperclip_spec.rb +222 -0
  145. data/spec/paperclip/plural_cache_spec.rb +37 -0
  146. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  147. data/spec/paperclip/processor_spec.rb +26 -0
  148. data/spec/paperclip/rails_environment_spec.rb +33 -0
  149. data/spec/paperclip/rake_spec.rb +103 -0
  150. data/spec/paperclip/schema_spec.rb +248 -0
  151. data/spec/paperclip/storage/filesystem_spec.rb +79 -0
  152. data/spec/paperclip/storage/fog_spec.rb +535 -0
  153. data/spec/paperclip/storage/s3_live_spec.rb +182 -0
  154. data/spec/paperclip/storage/s3_spec.rb +1526 -0
  155. data/spec/paperclip/style_spec.rb +255 -0
  156. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  157. data/spec/paperclip/thumbnail_spec.rb +500 -0
  158. data/spec/paperclip/url_generator_spec.rb +211 -0
  159. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  160. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +160 -0
  161. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  162. data/spec/paperclip/validators/attachment_size_validator_spec.rb +229 -0
  163. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +52 -0
  164. data/spec/paperclip/validators_spec.rb +164 -0
  165. data/spec/spec_helper.rb +43 -0
  166. data/spec/support/assertions.rb +71 -0
  167. data/spec/support/deprecations.rb +9 -0
  168. data/spec/support/fake_model.rb +25 -0
  169. data/spec/support/fake_rails.rb +12 -0
  170. data/spec/support/fixtures/12k.png +0 -0
  171. data/spec/support/fixtures/50x50.png +0 -0
  172. data/spec/support/fixtures/5k.png +0 -0
  173. data/spec/support/fixtures/animated +0 -0
  174. data/spec/support/fixtures/animated.gif +0 -0
  175. data/spec/support/fixtures/animated.unknown +0 -0
  176. data/spec/support/fixtures/bad.png +1 -0
  177. data/spec/support/fixtures/empty.html +1 -0
  178. data/spec/support/fixtures/empty.xlsx +0 -0
  179. data/spec/support/fixtures/fog.yml +8 -0
  180. data/spec/support/fixtures/rotated.jpg +0 -0
  181. data/spec/support/fixtures/s3.yml +8 -0
  182. data/spec/support/fixtures/spaced file.jpg +0 -0
  183. data/spec/support/fixtures/spaced file.png +0 -0
  184. data/spec/support/fixtures/text.txt +1 -0
  185. data/spec/support/fixtures/twopage.pdf +0 -0
  186. data/spec/support/fixtures/uppercase.PNG +0 -0
  187. data/spec/support/matchers/accept.rb +5 -0
  188. data/spec/support/matchers/exist.rb +5 -0
  189. data/spec/support/matchers/have_column.rb +23 -0
  190. data/spec/support/mock_attachment.rb +22 -0
  191. data/spec/support/mock_interpolator.rb +24 -0
  192. data/spec/support/mock_url_generator_builder.rb +27 -0
  193. data/spec/support/model_reconstruction.rb +60 -0
  194. data/spec/support/rails_helpers.rb +7 -0
  195. data/spec/support/test_data.rb +13 -0
  196. data/spec/support/version_helper.rb +9 -0
  197. metadata +606 -0
@@ -0,0 +1,79 @@
1
+ module Paperclip
2
+ class ContentTypeDetector
3
+ # The content-type detection strategy is as follows:
4
+ #
5
+ # 1. Blank/Empty files: If there's no filepath or the file is empty,
6
+ # provide a sensible default (application/octet-stream or inode/x-empty)
7
+ #
8
+ # 2. Calculated match: Return the first result that is found by both the
9
+ # `file` command and MIME::Types.
10
+ #
11
+ # 3. Standard types: Return the first standard (without an x- prefix) entry
12
+ # in MIME::Types
13
+ #
14
+ # 4. Experimental types: If there were no standard types in MIME::Types
15
+ # list, try to return the first experimental one
16
+ #
17
+ # 5. Raw `file` command: Just use the output of the `file` command raw, or
18
+ # a sensible default. This is cached from Step 2.
19
+
20
+ EMPTY_TYPE = "inode/x-empty"
21
+ SENSIBLE_DEFAULT = "application/octet-stream"
22
+
23
+ def initialize(filepath)
24
+ @filepath = filepath
25
+ end
26
+
27
+ # Returns a String describing the file's content type
28
+ def detect
29
+ if blank_name?
30
+ SENSIBLE_DEFAULT
31
+ elsif empty_file?
32
+ EMPTY_TYPE
33
+ elsif calculated_type_matches.any?
34
+ calculated_type_matches.first
35
+ else
36
+ type_from_file_contents || SENSIBLE_DEFAULT
37
+ end.to_s
38
+ end
39
+
40
+ private
41
+
42
+ def blank_name?
43
+ @filepath.nil? || @filepath.empty?
44
+ end
45
+
46
+ def empty_file?
47
+ File.exist?(@filepath) && File.size(@filepath) == 0
48
+ end
49
+
50
+ alias :empty? :empty_file?
51
+
52
+ def calculated_type_matches
53
+ possible_types.select do |content_type|
54
+ content_type == type_from_file_contents
55
+ end
56
+ end
57
+
58
+ def possible_types
59
+ MIME::Types.type_for(@filepath).collect(&:content_type)
60
+ end
61
+
62
+ def type_from_file_contents
63
+ type_from_mime_magic || type_from_file_command
64
+ rescue Errno::ENOENT => e
65
+ Paperclip.log("Error while determining content type: #{e}")
66
+ SENSIBLE_DEFAULT
67
+ end
68
+
69
+ def type_from_mime_magic
70
+ @type_from_mime_magic ||=
71
+ MimeMagic.by_magic(File.open(@filepath)).try(:type)
72
+ end
73
+
74
+ def type_from_file_command
75
+ @type_from_file_command ||=
76
+ FileCommandContentTypeDetector.new(@filepath).detect
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,42 @@
1
+ require "active_support/deprecation"
2
+
3
+ module Paperclip
4
+ class Deprecations
5
+ class << self
6
+ def check
7
+ warn_aws_sdk_v1 if aws_sdk_v1?
8
+ warn_outdated_rails if active_model_version < "4.2"
9
+ end
10
+
11
+ private
12
+
13
+ def active_model_version
14
+ ::ActiveModel::VERSION::STRING
15
+ end
16
+
17
+ def aws_sdk_v1?
18
+ defined?(::AWS) && aws_sdk_version < "2"
19
+ end
20
+
21
+ def warn_aws_sdk_v1
22
+ warn "[paperclip] [deprecation] AWS SDK v1 has been deprecated in " \
23
+ "paperclip 5. Please consider upgrading to AWS 2 before " \
24
+ "upgrading paperclip."
25
+ end
26
+
27
+ def warn_outdated_rails
28
+ warn "[paperclip] [deprecation] Rails 3.2 and 4.1 are unsupported as " \
29
+ "of Rails 5 release. Please upgrade to Rails 4.2 before " \
30
+ "upgrading paperclip."
31
+ end
32
+
33
+ def aws_sdk_version
34
+ ::AWS::VERSION
35
+ end
36
+
37
+ def warn(message)
38
+ ActiveSupport::Deprecation.warn(message)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,32 @@
1
+ module Paperclip
2
+ # A base error class for Paperclip. Most of the error that will be thrown
3
+ # from Paperclip will inherits from this class.
4
+ class Error < StandardError
5
+ end
6
+
7
+ module Errors
8
+ # Will be thrown when a storage method is not found.
9
+ class StorageMethodNotFound < Paperclip::Error
10
+ end
11
+
12
+ # Will be thrown when a command or executable is not found.
13
+ class CommandNotFoundError < Paperclip::Error
14
+ end
15
+
16
+ # Attachments require a content_type or file_name validator,
17
+ # or to have explicitly opted out of them.
18
+ class MissingRequiredValidatorError < Paperclip::Error
19
+ end
20
+
21
+ # Will be thrown when ImageMagic cannot determine the uploaded file's
22
+ # metadata, usually this would mean the file is not an image.
23
+ class NotIdentifiedByImageMagickError < Paperclip::Error
24
+ end
25
+
26
+ # Will be thrown if the interpolation is creating an infinite loop. If you
27
+ # are creating an interpolator which might cause an infinite loop, you
28
+ # should be throwing this error upon the infinite loop as well.
29
+ class InfiniteInterpolationError < Paperclip::Error
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ module Paperclip
2
+ class FileCommandContentTypeDetector
3
+ SENSIBLE_DEFAULT = "application/octet-stream"
4
+
5
+ def initialize(filename)
6
+ @filename = filename
7
+ end
8
+
9
+ def detect
10
+ type_from_file_command
11
+ end
12
+
13
+ private
14
+
15
+ def type_from_file_command
16
+ # On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
17
+ type = begin
18
+ Paperclip.run("file", "-b --mime :file", file: @filename)
19
+ rescue Cocaine::CommandLineError => e
20
+ Paperclip.log("Error while determining content type: #{e}")
21
+ SENSIBLE_DEFAULT
22
+ end
23
+
24
+ if type.nil? || type.match(/\(.*?\)/)
25
+ type = SENSIBLE_DEFAULT
26
+ end
27
+ type.split(/[:;\s]+/)[0]
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ module Paperclip
3
+ class FilenameCleaner
4
+ def initialize(invalid_character_regex)
5
+ @invalid_character_regex = invalid_character_regex
6
+ end
7
+
8
+ def call(filename)
9
+ if @invalid_character_regex
10
+ filename.gsub(@invalid_character_regex, "_")
11
+ else
12
+ filename
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,158 @@
1
+ module Paperclip
2
+
3
+ # Defines the geometry of an image.
4
+ class Geometry
5
+ attr_accessor :height, :width, :modifier
6
+
7
+ EXIF_ROTATED_ORIENTATION_VALUES = [5, 6, 7, 8]
8
+
9
+ # Gives a Geometry representing the given height and width
10
+ def initialize(width = nil, height = nil, modifier = nil)
11
+ if width.is_a?(Hash)
12
+ options = width
13
+ @height = options[:height].to_f
14
+ @width = options[:width].to_f
15
+ @modifier = options[:modifier]
16
+ @orientation = options[:orientation].to_i
17
+ else
18
+ @height = height.to_f
19
+ @width = width.to_f
20
+ @modifier = modifier
21
+ end
22
+ end
23
+
24
+ # Extracts the Geometry from a file (or path to a file)
25
+ def self.from_file(file)
26
+ GeometryDetector.new(file).make
27
+ end
28
+
29
+ # Extracts the Geometry from a "WxH,O" string
30
+ # Where W is the width, H is the height,
31
+ # and O is the EXIF orientation
32
+ def self.parse(string)
33
+ GeometryParser.new(string).make
34
+ end
35
+
36
+ # Swaps the height and width if necessary
37
+ def auto_orient
38
+ if EXIF_ROTATED_ORIENTATION_VALUES.include?(@orientation)
39
+ @height, @width = @width, @height
40
+ @orientation -= 4
41
+ end
42
+ end
43
+
44
+ # True if the dimensions represent a square
45
+ def square?
46
+ height == width
47
+ end
48
+
49
+ # True if the dimensions represent a horizontal rectangle
50
+ def horizontal?
51
+ height < width
52
+ end
53
+
54
+ # True if the dimensions represent a vertical rectangle
55
+ def vertical?
56
+ height > width
57
+ end
58
+
59
+ # The aspect ratio of the dimensions.
60
+ def aspect
61
+ width / height
62
+ end
63
+
64
+ # Returns the larger of the two dimensions
65
+ def larger
66
+ [height, width].max
67
+ end
68
+
69
+ # Returns the smaller of the two dimensions
70
+ def smaller
71
+ [height, width].min
72
+ end
73
+
74
+ # Returns the width and height in a format suitable to be passed to Geometry.parse
75
+ def to_s
76
+ s = ""
77
+ s << width.to_i.to_s if width > 0
78
+ s << "x#{height.to_i}" if height > 0
79
+ s << modifier.to_s
80
+ s
81
+ end
82
+
83
+ # Same as to_s
84
+ def inspect
85
+ to_s
86
+ end
87
+
88
+ # Returns the scaling and cropping geometries (in string-based ImageMagick format)
89
+ # neccessary to transform this Geometry into the Geometry given. If crop is true,
90
+ # then it is assumed the destination Geometry will be the exact final resolution.
91
+ # In this case, the source Geometry is scaled so that an image containing the
92
+ # destination Geometry would be completely filled by the source image, and any
93
+ # overhanging image would be cropped. Useful for square thumbnail images. The cropping
94
+ # is weighted at the center of the Geometry.
95
+ def transformation_to dst, crop = false
96
+ if crop
97
+ ratio = Geometry.new( dst.width / self.width, dst.height / self.height )
98
+ scale_geometry, scale = scaling(dst, ratio)
99
+ crop_geometry = cropping(dst, ratio, scale)
100
+ else
101
+ scale_geometry = dst.to_s
102
+ end
103
+
104
+ [ scale_geometry, crop_geometry ]
105
+ end
106
+
107
+ # resize to a new geometry
108
+ # @param geometry [String] the Paperclip geometry definition to resize to
109
+ # @example
110
+ # Paperclip::Geometry.new(150, 150).resize_to('50x50!')
111
+ # #=> Paperclip::Geometry(50, 50)
112
+ def resize_to(geometry)
113
+ new_geometry = Paperclip::Geometry.parse geometry
114
+ case new_geometry.modifier
115
+ when '!', '#'
116
+ new_geometry
117
+ when '>'
118
+ if new_geometry.width >= self.width && new_geometry.height >= self.height
119
+ self
120
+ else
121
+ scale_to new_geometry
122
+ end
123
+ when '<'
124
+ if new_geometry.width <= self.width || new_geometry.height <= self.height
125
+ self
126
+ else
127
+ scale_to new_geometry
128
+ end
129
+ else
130
+ scale_to new_geometry
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ def scaling dst, ratio
137
+ if ratio.horizontal? || ratio.square?
138
+ [ "%dx" % dst.width, ratio.width ]
139
+ else
140
+ [ "x%d" % dst.height, ratio.height ]
141
+ end
142
+ end
143
+
144
+ def cropping dst, ratio, scale
145
+ if ratio.horizontal? || ratio.square?
146
+ "%dx%d+%d+%d" % [ dst.width, dst.height, 0, (self.height * scale - dst.height) / 2 ]
147
+ else
148
+ "%dx%d+%d+%d" % [ dst.width, dst.height, (self.width * scale - dst.width) / 2, 0 ]
149
+ end
150
+ end
151
+
152
+ # scale to the requested geometry and preserve the aspect ratio
153
+ def scale_to(new_geometry)
154
+ scale = [new_geometry.width.to_f / self.width.to_f , new_geometry.height.to_f / self.height.to_f].min
155
+ Paperclip::Geometry.new((self.width * scale).round, (self.height * scale).round)
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,48 @@
1
+ module Paperclip
2
+ class GeometryDetector
3
+ def initialize(file)
4
+ @file = file
5
+ raise_if_blank_file
6
+ end
7
+
8
+ def make
9
+ geometry = GeometryParser.new(geometry_string.strip).make
10
+ geometry || raise(Errors::NotIdentifiedByImageMagickError.new)
11
+ end
12
+
13
+ private
14
+
15
+ def geometry_string
16
+ begin
17
+ orientation = Paperclip.options[:use_exif_orientation] ?
18
+ "%[exif:orientation]" : "1"
19
+ Paperclip.run(
20
+ "identify",
21
+ "-format '%wx%h,#{orientation}' :file", {
22
+ :file => "#{path}[0]"
23
+ }, {
24
+ :swallow_stderr => true
25
+ }
26
+ )
27
+ rescue Cocaine::ExitStatusError
28
+ ""
29
+ rescue Cocaine::CommandNotFoundError => e
30
+ raise_because_imagemagick_missing
31
+ end
32
+ end
33
+
34
+ def path
35
+ @file.respond_to?(:path) ? @file.path : @file
36
+ end
37
+
38
+ def raise_if_blank_file
39
+ if path.blank?
40
+ raise Errors::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")
41
+ end
42
+ end
43
+
44
+ def raise_because_imagemagick_missing
45
+ raise Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,31 @@
1
+ module Paperclip
2
+ class GeometryParser
3
+ FORMAT = /\b(\d*)x?(\d*)\b(?:,(\d?))?(\@\>|\>\@|[\>\<\#\@\%^!])?/i
4
+ def initialize(string)
5
+ @string = string
6
+ end
7
+
8
+ def make
9
+ if match
10
+ Geometry.new(
11
+ :height => @height,
12
+ :width => @width,
13
+ :modifier => @modifier,
14
+ :orientation => @orientation
15
+ )
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def match
22
+ if actual_match = @string && @string.match(FORMAT)
23
+ @width = actual_match[1]
24
+ @height = actual_match[2]
25
+ @orientation = actual_match[3]
26
+ @modifier = actual_match[4]
27
+ end
28
+ actual_match
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ require 'paperclip/callbacks'
2
+ require 'paperclip/validators'
3
+ require 'paperclip/schema'
4
+
5
+ module Paperclip
6
+ module Glue
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ base.send :include, Callbacks
10
+ base.send :include, Validators
11
+ base.send :include, Schema if defined? ActiveRecord
12
+
13
+ locale_path = Dir.glob(File.dirname(__FILE__) + "/locales/*.{rb,yml}")
14
+ I18n.load_path += locale_path unless I18n.load_path.include?(locale_path)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,109 @@
1
+ module Paperclip
2
+ class HasAttachedFile
3
+ def self.define_on(klass, name, options)
4
+ new(klass, name, options).define
5
+ end
6
+
7
+ def initialize(klass, name, options)
8
+ @klass = klass
9
+ @name = name
10
+ @options = options
11
+ end
12
+
13
+ def define
14
+ define_flush_errors
15
+ define_getters
16
+ define_setter
17
+ define_query
18
+ register_new_attachment
19
+ add_active_record_callbacks
20
+ add_paperclip_callbacks
21
+ add_required_validations
22
+ end
23
+
24
+ private
25
+
26
+ def define_flush_errors
27
+ @klass.send(:validates_each, @name) do |record, attr, value|
28
+ attachment = record.send(@name)
29
+ attachment.send(:flush_errors)
30
+ end
31
+ end
32
+
33
+ def define_getters
34
+ define_instance_getter
35
+ define_class_getter
36
+ end
37
+
38
+ def define_instance_getter
39
+ name = @name
40
+ options = @options
41
+
42
+ @klass.send :define_method, @name do |*args|
43
+ ivar = "@attachment_#{name}"
44
+ attachment = instance_variable_get(ivar)
45
+
46
+ if attachment.nil?
47
+ attachment = Attachment.new(name, self, options)
48
+ instance_variable_set(ivar, attachment)
49
+ end
50
+
51
+ if args.length > 0
52
+ attachment.to_s(args.first)
53
+ else
54
+ attachment
55
+ end
56
+ end
57
+ end
58
+
59
+ def define_class_getter
60
+ @klass.extend(ClassMethods)
61
+ end
62
+
63
+ def define_setter
64
+ name = @name
65
+ @klass.send :define_method, "#{@name}=" do |file|
66
+ send(name).assign(file)
67
+ end
68
+ end
69
+
70
+ def define_query
71
+ name = @name
72
+ @klass.send :define_method, "#{@name}?" do
73
+ send(name).file?
74
+ end
75
+ end
76
+
77
+ def register_new_attachment
78
+ Paperclip::AttachmentRegistry.register(@klass, @name, @options)
79
+ end
80
+
81
+ def add_required_validations
82
+ options = Paperclip::Attachment.default_options.deep_merge(@options)
83
+ if options[:validate_media_type] != false
84
+ name = @name
85
+ @klass.validates_media_type_spoof_detection name,
86
+ :if => ->(instance){ instance.send(name).dirty? }
87
+ end
88
+ end
89
+
90
+ def add_active_record_callbacks
91
+ name = @name
92
+ @klass.send(:after_save) { send(name).send(:save) }
93
+ @klass.send(:before_destroy) { send(name).send(:queue_all_for_delete) }
94
+ @klass.send(:after_commit, :on => :destroy) { send(name).send(:flush_deletes) }
95
+ end
96
+
97
+ def add_paperclip_callbacks
98
+ @klass.send(
99
+ :define_paperclip_callbacks,
100
+ :post_process, :"#{@name}_post_process")
101
+ end
102
+
103
+ module ClassMethods
104
+ def attachment_definitions
105
+ Paperclip::AttachmentRegistry.definitions_for(self)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,56 @@
1
+ module Paperclip
2
+ module Helpers
3
+ def configure
4
+ yield(self) if block_given?
5
+ end
6
+
7
+ def interpolates key, &block
8
+ Paperclip::Interpolations[key] = block
9
+ end
10
+
11
+ # The run method takes the name of a binary to run, the arguments to that binary
12
+ # and some options:
13
+ #
14
+ # :command_path -> A $PATH-like variable that defines where to look for the binary
15
+ # on the filesystem. Colon-separated, just like $PATH.
16
+ #
17
+ # :expected_outcodes -> An array of integers that defines the expected exit codes
18
+ # of the binary. Defaults to [0].
19
+ #
20
+ # :log_command -> Log the command being run when set to true (defaults to true).
21
+ # This will only log if logging in general is set to true as well.
22
+ #
23
+ # :swallow_stderr -> Set to true if you don't care what happens on STDERR.
24
+ #
25
+ def run(cmd, arguments = "", interpolation_values = {}, local_options = {})
26
+ command_path = options[:command_path]
27
+ Cocaine::CommandLine.path = [Cocaine::CommandLine.path, command_path].flatten.compact.uniq
28
+ if logging? && (options[:log_command] || local_options[:log_command])
29
+ local_options = local_options.merge(:logger => logger)
30
+ end
31
+ Cocaine::CommandLine.new(cmd, arguments, local_options).run(interpolation_values)
32
+ end
33
+
34
+ # Find all instances of the given Active Record model +klass+ with attachment +name+.
35
+ # This method is used by the refresh rake tasks.
36
+ def each_instance_with_attachment(klass, name)
37
+ class_for(klass).unscoped.where("#{name}_file_name IS NOT NULL").find_each do |instance|
38
+ yield(instance)
39
+ end
40
+ end
41
+
42
+ def class_for(class_name)
43
+ class_name.split('::').inject(Object) do |klass, partial_class_name|
44
+ if klass.const_defined?(partial_class_name)
45
+ klass.const_get(partial_class_name, false)
46
+ else
47
+ klass.const_missing(partial_class_name)
48
+ end
49
+ end
50
+ end
51
+
52
+ def reset_duplicate_clash_check!
53
+ @names_url = nil
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,18 @@
1
+ module Paperclip
2
+ module Interpolations
3
+ class PluralCache
4
+ def initialize
5
+ @symbol_cache = {}.compare_by_identity
6
+ @klass_cache = {}.compare_by_identity
7
+ end
8
+
9
+ def pluralize_symbol(symbol)
10
+ @symbol_cache[symbol] ||= symbol.to_s.downcase.pluralize
11
+ end
12
+
13
+ def underscore_and_pluralize_class(klass)
14
+ @klass_cache[klass] ||= klass.name.underscore.pluralize
15
+ end
16
+ end
17
+ end
18
+ end