jr-paperclip 7.3.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 +7 -0
- data/.github/FUNDING.yml +3 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/.github/ISSUE_TEMPLATE/custom.md +10 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/reviewdog.yml +23 -0
- data/.github/workflows/test.yml +46 -0
- data/.gitignore +19 -0
- data/.qlty/.gitignore +7 -0
- data/.qlty/qlty.toml +89 -0
- data/.rubocop.yml +1060 -0
- data/Appraisals +29 -0
- data/CONTRIBUTING.md +85 -0
- data/Gemfile +17 -0
- data/LICENSE +25 -0
- data/NEWS +567 -0
- data/README.md +1083 -0
- data/RELEASING.md +17 -0
- data/Rakefile +52 -0
- data/bin/console +11 -0
- data/features/basic_integration.feature +85 -0
- data/features/migration.feature +29 -0
- data/features/rake_tasks.feature +62 -0
- data/features/step_definitions/attachment_steps.rb +121 -0
- data/features/step_definitions/html_steps.rb +15 -0
- data/features/step_definitions/rails_steps.rb +271 -0
- data/features/step_definitions/s3_steps.rb +16 -0
- data/features/step_definitions/web_steps.rb +106 -0
- data/features/support/env.rb +12 -0
- data/features/support/file_helpers.rb +34 -0
- data/features/support/fixtures/boot_config.txt +15 -0
- data/features/support/fixtures/gemfile.txt +5 -0
- data/features/support/fixtures/preinitializer.txt +20 -0
- data/features/support/paths.rb +28 -0
- data/features/support/rails.rb +39 -0
- data/features/support/selectors.rb +19 -0
- data/features/support/webmock_setup.rb +8 -0
- data/gemfiles/7.0.gemfile +20 -0
- data/gemfiles/7.1.gemfile +20 -0
- data/gemfiles/7.2.gemfile +20 -0
- data/gemfiles/8.0.gemfile +20 -0
- data/gemfiles/8.1.gemfile +20 -0
- data/lib/generators/paperclip/USAGE +8 -0
- data/lib/generators/paperclip/paperclip_generator.rb +36 -0
- data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
- data/lib/jr-paperclip.rb +1 -0
- data/lib/paperclip/attachment.rb +634 -0
- data/lib/paperclip/attachment_registry.rb +60 -0
- data/lib/paperclip/callbacks.rb +42 -0
- data/lib/paperclip/content_type_detector.rb +85 -0
- data/lib/paperclip/errors.rb +34 -0
- data/lib/paperclip/file_command_content_type_detector.rb +28 -0
- data/lib/paperclip/filename_cleaner.rb +15 -0
- data/lib/paperclip/geometry.rb +157 -0
- data/lib/paperclip/geometry_detector_factory.rb +45 -0
- data/lib/paperclip/geometry_parser_factory.rb +31 -0
- data/lib/paperclip/glue.rb +18 -0
- data/lib/paperclip/has_attached_file.rb +116 -0
- data/lib/paperclip/helpers.rb +60 -0
- data/lib/paperclip/interpolations/plural_cache.rb +18 -0
- data/lib/paperclip/interpolations.rb +205 -0
- data/lib/paperclip/io_adapters/abstract_adapter.rb +75 -0
- data/lib/paperclip/io_adapters/attachment_adapter.rb +56 -0
- data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
- data/lib/paperclip/io_adapters/empty_string_adapter.rb +19 -0
- data/lib/paperclip/io_adapters/file_adapter.rb +26 -0
- data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
- data/lib/paperclip/io_adapters/identity_adapter.rb +17 -0
- data/lib/paperclip/io_adapters/nil_adapter.rb +37 -0
- data/lib/paperclip/io_adapters/registry.rb +36 -0
- data/lib/paperclip/io_adapters/stringio_adapter.rb +36 -0
- data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +44 -0
- data/lib/paperclip/io_adapters/uri_adapter.rb +78 -0
- data/lib/paperclip/locales/en.yml +18 -0
- data/lib/paperclip/locales/fr.yml +18 -0
- data/lib/paperclip/locales/gd.yml +20 -0
- data/lib/paperclip/logger.rb +21 -0
- data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
- data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +101 -0
- data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
- data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +97 -0
- data/lib/paperclip/matchers.rb +64 -0
- data/lib/paperclip/media_type_spoof_detector.rb +93 -0
- data/lib/paperclip/missing_attachment_styles.rb +84 -0
- data/lib/paperclip/processor.rb +56 -0
- data/lib/paperclip/processor_helpers.rb +52 -0
- data/lib/paperclip/rails_environment.rb +21 -0
- data/lib/paperclip/railtie.rb +31 -0
- data/lib/paperclip/schema.rb +104 -0
- data/lib/paperclip/storage/filesystem.rb +99 -0
- data/lib/paperclip/storage/fog.rb +262 -0
- data/lib/paperclip/storage/s3.rb +497 -0
- data/lib/paperclip/storage.rb +3 -0
- data/lib/paperclip/style.rb +106 -0
- data/lib/paperclip/tempfile.rb +42 -0
- data/lib/paperclip/tempfile_factory.rb +22 -0
- data/lib/paperclip/thumbnail.rb +131 -0
- data/lib/paperclip/url_generator.rb +83 -0
- data/lib/paperclip/validators/attachment_content_type_validator.rb +95 -0
- data/lib/paperclip/validators/attachment_file_name_validator.rb +82 -0
- data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +28 -0
- data/lib/paperclip/validators/attachment_presence_validator.rb +28 -0
- data/lib/paperclip/validators/attachment_size_validator.rb +126 -0
- data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +29 -0
- data/lib/paperclip/validators.rb +73 -0
- data/lib/paperclip/version.rb +3 -0
- data/lib/paperclip.rb +215 -0
- data/lib/tasks/paperclip.rake +140 -0
- data/paperclip.gemspec +51 -0
- data/shoulda_macros/paperclip.rb +134 -0
- data/spec/database.yml +4 -0
- data/spec/paperclip/attachment_definitions_spec.rb +13 -0
- data/spec/paperclip/attachment_processing_spec.rb +79 -0
- data/spec/paperclip/attachment_registry_spec.rb +158 -0
- data/spec/paperclip/attachment_spec.rb +1617 -0
- data/spec/paperclip/content_type_detector_spec.rb +58 -0
- data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
- data/spec/paperclip/filename_cleaner_spec.rb +13 -0
- data/spec/paperclip/geometry_detector_spec.rb +47 -0
- data/spec/paperclip/geometry_parser_spec.rb +73 -0
- data/spec/paperclip/geometry_spec.rb +267 -0
- data/spec/paperclip/glue_spec.rb +63 -0
- data/spec/paperclip/has_attached_file_spec.rb +78 -0
- data/spec/paperclip/integration_spec.rb +702 -0
- data/spec/paperclip/interpolations_spec.rb +270 -0
- data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +160 -0
- data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +167 -0
- data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +88 -0
- data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
- data/spec/paperclip/io_adapters/file_adapter_spec.rb +134 -0
- data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +142 -0
- data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
- data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
- data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
- data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
- data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
- data/spec/paperclip/io_adapters/uri_adapter_spec.rb +231 -0
- data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
- data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +108 -0
- data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
- data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
- data/spec/paperclip/media_type_spoof_detector_spec.rb +126 -0
- data/spec/paperclip/meta_class_spec.rb +30 -0
- data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +88 -0
- data/spec/paperclip/paperclip_spec.rb +196 -0
- data/spec/paperclip/plural_cache_spec.rb +37 -0
- data/spec/paperclip/processor_helpers_spec.rb +57 -0
- data/spec/paperclip/processor_spec.rb +26 -0
- data/spec/paperclip/rails_environment_spec.rb +30 -0
- data/spec/paperclip/rake_spec.rb +103 -0
- data/spec/paperclip/schema_spec.rb +298 -0
- data/spec/paperclip/storage/filesystem_spec.rb +102 -0
- data/spec/paperclip/storage/fog_spec.rb +606 -0
- data/spec/paperclip/storage/s3_live_spec.rb +188 -0
- data/spec/paperclip/storage/s3_spec.rb +1974 -0
- data/spec/paperclip/style_spec.rb +251 -0
- data/spec/paperclip/tempfile_factory_spec.rb +33 -0
- data/spec/paperclip/tempfile_spec.rb +35 -0
- data/spec/paperclip/thumbnail_spec.rb +504 -0
- data/spec/paperclip/url_generator_spec.rb +231 -0
- data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +410 -0
- data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +249 -0
- data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
- data/spec/paperclip/validators/attachment_size_validator_spec.rb +325 -0
- data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +48 -0
- data/spec/paperclip/validators_spec.rb +179 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/support/assertions.rb +84 -0
- data/spec/support/fake_model.rb +24 -0
- data/spec/support/fake_rails.rb +12 -0
- data/spec/support/fixtures/12k.png +0 -0
- data/spec/support/fixtures/50x50.png +0 -0
- data/spec/support/fixtures/5k.png +0 -0
- data/spec/support/fixtures/animated +0 -0
- data/spec/support/fixtures/animated.gif +0 -0
- data/spec/support/fixtures/animated.unknown +0 -0
- data/spec/support/fixtures/aws_s3.yml +13 -0
- data/spec/support/fixtures/bad.png +1 -0
- data/spec/support/fixtures/empty.html +1 -0
- data/spec/support/fixtures/empty.xlsx +0 -0
- data/spec/support/fixtures/fog.yml +8 -0
- data/spec/support/fixtures/rotated.jpg +0 -0
- data/spec/support/fixtures/s3.yml +8 -0
- data/spec/support/fixtures/sample.xlsm +0 -0
- data/spec/support/fixtures/spaced file.jpg +0 -0
- data/spec/support/fixtures/spaced file.png +0 -0
- data/spec/support/fixtures/text.txt +1 -0
- data/spec/support/fixtures/twopage.pdf +0 -0
- data/spec/support/fixtures/uppercase.PNG +0 -0
- data/spec/support/matchers/accept.rb +5 -0
- data/spec/support/matchers/exist.rb +5 -0
- data/spec/support/matchers/have_column.rb +23 -0
- data/spec/support/mock_attachment.rb +24 -0
- data/spec/support/mock_interpolator.rb +24 -0
- data/spec/support/mock_url_generator_builder.rb +26 -0
- data/spec/support/model_reconstruction.rb +72 -0
- data/spec/support/reporting.rb +11 -0
- data/spec/support/test_data.rb +13 -0
- data/spec/support/version_helper.rb +9 -0
- metadata +702 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Paperclip
|
|
2
|
+
module Callbacks
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.extend(Defining)
|
|
5
|
+
base.send(:include, Running)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module Defining
|
|
9
|
+
def define_paperclip_callbacks(*callbacks)
|
|
10
|
+
define_callbacks(*[callbacks, { terminator: hasta_la_vista_baby }].flatten)
|
|
11
|
+
callbacks.each do |callback|
|
|
12
|
+
eval <<-end_callbacks
|
|
13
|
+
def before_#{callback}(*args, &blk)
|
|
14
|
+
set_callback(:#{callback}, :before, *args, &blk)
|
|
15
|
+
end
|
|
16
|
+
def after_#{callback}(*args, &blk)
|
|
17
|
+
set_callback(:#{callback}, :after, *args, &blk)
|
|
18
|
+
end
|
|
19
|
+
end_callbacks
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def hasta_la_vista_baby
|
|
26
|
+
lambda do |_, result|
|
|
27
|
+
if result.respond_to?(:call)
|
|
28
|
+
result.call == false
|
|
29
|
+
else
|
|
30
|
+
result == false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
module Running
|
|
37
|
+
def run_paperclip_callbacks(callback, &block)
|
|
38
|
+
run_callbacks(callback, &block)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
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_marcel || 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_marcel
|
|
70
|
+
return @type_from_marcel if defined? @type_from_marcel
|
|
71
|
+
|
|
72
|
+
@type_from_marcel = Marcel::MimeType.for Pathname.new(@filepath),
|
|
73
|
+
name: @filepath
|
|
74
|
+
# Marcel::MineType returns 'application/octet-stream' if it can't find
|
|
75
|
+
# a valid type.
|
|
76
|
+
@type_from_marcel = nil if @type_from_marcel == SENSIBLE_DEFAULT
|
|
77
|
+
@type_from_marcel
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def type_from_file_command
|
|
81
|
+
@type_from_file_command ||=
|
|
82
|
+
FileCommandContentTypeDetector.new(@filepath).detect
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
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. If you are
|
|
23
|
+
# consistently receiving this error on PDFs make sure that you have
|
|
24
|
+
# installed Ghostscript.
|
|
25
|
+
class NotIdentifiedByImageMagickError < Paperclip::Error
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Will be thrown if the interpolation is creating an infinite loop. If you
|
|
29
|
+
# are creating an interpolator which might cause an infinite loop, you
|
|
30
|
+
# should be throwing this error upon the infinite loop as well.
|
|
31
|
+
class InfiniteInterpolationError < Paperclip::Error
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
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 Terrapin::CommandLineError => e
|
|
20
|
+
Paperclip.log("Error while determining content type: #{e}")
|
|
21
|
+
SENSIBLE_DEFAULT
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
type = SENSIBLE_DEFAULT if type.nil? || type.match(/\(.*?\)/)
|
|
25
|
+
type.split(/[:;\s]+/)[0]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Paperclip
|
|
2
|
+
class FilenameCleaner
|
|
3
|
+
def initialize(invalid_character_regex)
|
|
4
|
+
@invalid_character_regex = invalid_character_regex
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def call(filename)
|
|
8
|
+
if @invalid_character_regex
|
|
9
|
+
filename.gsub(@invalid_character_regex, "_")
|
|
10
|
+
else
|
|
11
|
+
filename
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
module Paperclip
|
|
2
|
+
# Defines the geometry of an image.
|
|
3
|
+
class Geometry
|
|
4
|
+
attr_accessor :height, :width, :modifier
|
|
5
|
+
|
|
6
|
+
EXIF_ROTATED_ORIENTATION_VALUES = [5, 6, 7, 8].freeze
|
|
7
|
+
|
|
8
|
+
# Gives a Geometry representing the given height and width
|
|
9
|
+
def initialize(width = nil, height = nil, modifier = nil)
|
|
10
|
+
if width.is_a?(Hash)
|
|
11
|
+
options = width
|
|
12
|
+
@height = options[:height].to_f
|
|
13
|
+
@width = options[:width].to_f
|
|
14
|
+
@modifier = options[:modifier]
|
|
15
|
+
@orientation = options[:orientation].to_i
|
|
16
|
+
else
|
|
17
|
+
@height = height.to_f
|
|
18
|
+
@width = width.to_f
|
|
19
|
+
@modifier = modifier
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Extracts the Geometry from a file (or path to a file)
|
|
24
|
+
def self.from_file(file)
|
|
25
|
+
GeometryDetector.new(file).make
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Extracts the Geometry from a "WxH,O" string
|
|
29
|
+
# Where W is the width, H is the height,
|
|
30
|
+
# and O is the EXIF orientation
|
|
31
|
+
def self.parse(string)
|
|
32
|
+
GeometryParser.new(string).make
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Swaps the height and width if necessary
|
|
36
|
+
def auto_orient
|
|
37
|
+
if EXIF_ROTATED_ORIENTATION_VALUES.include?(@orientation)
|
|
38
|
+
@height, @width = @width, @height
|
|
39
|
+
@orientation -= 4
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# True if the dimensions represent a square
|
|
44
|
+
def square?
|
|
45
|
+
height == width
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# True if the dimensions represent a horizontal rectangle
|
|
49
|
+
def horizontal?
|
|
50
|
+
height < width
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# True if the dimensions represent a vertical rectangle
|
|
54
|
+
def vertical?
|
|
55
|
+
height > width
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The aspect ratio of the dimensions.
|
|
59
|
+
def aspect
|
|
60
|
+
width / height
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns the larger of the two dimensions
|
|
64
|
+
def larger
|
|
65
|
+
[height, width].max
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns the smaller of the two dimensions
|
|
69
|
+
def smaller
|
|
70
|
+
[height, width].min
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Returns the width and height in a format suitable to be passed to Geometry.parse
|
|
74
|
+
def to_s
|
|
75
|
+
s = String.new
|
|
76
|
+
s << width.to_i.to_s if width > 0
|
|
77
|
+
s << "x#{height.to_i}" if height > 0
|
|
78
|
+
s << modifier.to_s
|
|
79
|
+
s
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Same as to_s
|
|
83
|
+
def inspect
|
|
84
|
+
to_s
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Returns the scaling and cropping geometries (in string-based ImageMagick format)
|
|
88
|
+
# neccessary to transform this Geometry into the Geometry given. If crop is true,
|
|
89
|
+
# then it is assumed the destination Geometry will be the exact final resolution.
|
|
90
|
+
# In this case, the source Geometry is scaled so that an image containing the
|
|
91
|
+
# destination Geometry would be completely filled by the source image, and any
|
|
92
|
+
# overhanging image would be cropped. Useful for square thumbnail images. The cropping
|
|
93
|
+
# is weighted at the center of the Geometry.
|
|
94
|
+
def transformation_to(dst, crop = false)
|
|
95
|
+
if crop
|
|
96
|
+
ratio = Geometry.new(dst.width / width, dst.height / height)
|
|
97
|
+
scale_geometry, scale = scaling(dst, ratio)
|
|
98
|
+
crop_geometry = cropping(dst, ratio, scale)
|
|
99
|
+
else
|
|
100
|
+
scale_geometry = dst.to_s
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
[scale_geometry, crop_geometry]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# resize to a new geometry
|
|
107
|
+
# @param geometry [String] the Paperclip geometry definition to resize to
|
|
108
|
+
# @example
|
|
109
|
+
# Paperclip::Geometry.new(150, 150).resize_to('50x50!')
|
|
110
|
+
# #=> Paperclip::Geometry(50, 50)
|
|
111
|
+
def resize_to(geometry)
|
|
112
|
+
new_geometry = Paperclip::Geometry.parse geometry
|
|
113
|
+
case new_geometry.modifier
|
|
114
|
+
when "!", "#"
|
|
115
|
+
new_geometry
|
|
116
|
+
when ">"
|
|
117
|
+
if new_geometry.width >= width && new_geometry.height >= height
|
|
118
|
+
self
|
|
119
|
+
else
|
|
120
|
+
scale_to new_geometry
|
|
121
|
+
end
|
|
122
|
+
when "<"
|
|
123
|
+
if new_geometry.width <= width || new_geometry.height <= height
|
|
124
|
+
self
|
|
125
|
+
else
|
|
126
|
+
scale_to new_geometry
|
|
127
|
+
end
|
|
128
|
+
else
|
|
129
|
+
scale_to new_geometry
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def scaling(dst, ratio)
|
|
136
|
+
if ratio.horizontal? || ratio.square?
|
|
137
|
+
["%dx" % dst.width, ratio.width]
|
|
138
|
+
else
|
|
139
|
+
["x%d" % dst.height, ratio.height]
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def cropping(dst, ratio, scale)
|
|
144
|
+
if ratio.horizontal? || ratio.square?
|
|
145
|
+
"%dx%d+%d+%d" % [dst.width, dst.height, 0, (height * scale - dst.height) / 2]
|
|
146
|
+
else
|
|
147
|
+
"%dx%d+%d+%d" % [dst.width, dst.height, (width * scale - dst.width) / 2, 0]
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# scale to the requested geometry and preserve the aspect ratio
|
|
152
|
+
def scale_to(new_geometry)
|
|
153
|
+
scale = [new_geometry.width.to_f / width.to_f, new_geometry.height.to_f / height.to_f].min
|
|
154
|
+
Paperclip::Geometry.new((width * scale).round, (height * scale).round)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
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("Could not identify image size"))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def geometry_string
|
|
16
|
+
orientation = Paperclip.options[:use_exif_orientation] ?
|
|
17
|
+
"%[exif:orientation]" : "1"
|
|
18
|
+
Paperclip.run(
|
|
19
|
+
Paperclip.options[:is_windows] ? "magick identify" : "identify",
|
|
20
|
+
"-format '%wx%h,#{orientation}' :file", {
|
|
21
|
+
file: "#{path}[0]"
|
|
22
|
+
},
|
|
23
|
+
swallow_stderr: true
|
|
24
|
+
)
|
|
25
|
+
rescue Terrapin::ExitStatusError
|
|
26
|
+
""
|
|
27
|
+
rescue Terrapin::CommandNotFoundError => e
|
|
28
|
+
raise_because_imagemagick_missing
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def path
|
|
32
|
+
@file.respond_to?(:path) ? @file.path : @file
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def raise_if_blank_file
|
|
36
|
+
if path.blank?
|
|
37
|
+
raise Errors::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def raise_because_imagemagick_missing
|
|
42
|
+
raise Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Paperclip
|
|
2
|
+
class GeometryParser
|
|
3
|
+
FORMAT = /\b(\d*)x?(\d*)\b(?:,(\d?))?(\@\>|\>\@|[\>\<\#\@\%^!])?/i.freeze
|
|
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,18 @@
|
|
|
1
|
+
require "paperclip/callbacks"
|
|
2
|
+
require "paperclip/validators"
|
|
3
|
+
require "paperclip/schema"
|
|
4
|
+
|
|
5
|
+
module Paperclip
|
|
6
|
+
module Glue
|
|
7
|
+
LOCALE_PATHS = Dir.glob("#{File.dirname(__FILE__)}/locales/*.{rb,yml}")
|
|
8
|
+
|
|
9
|
+
def self.included(base)
|
|
10
|
+
base.extend ClassMethods
|
|
11
|
+
base.send :include, Callbacks
|
|
12
|
+
base.send :include, Validators
|
|
13
|
+
base.send :include, Schema if defined? ActiveRecord::Base
|
|
14
|
+
|
|
15
|
+
I18n.load_path += LOCALE_PATHS unless (LOCALE_PATHS - I18n.load_path).empty?
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
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.empty?
|
|
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
|
+
if @klass.respond_to?(:after_commit)
|
|
95
|
+
@klass.send(:after_commit, on: :destroy) do
|
|
96
|
+
send(name).send(:flush_deletes)
|
|
97
|
+
end
|
|
98
|
+
else
|
|
99
|
+
@klass.send(:after_destroy) { send(name).send(:flush_deletes) }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def add_paperclip_callbacks
|
|
104
|
+
@klass.send(
|
|
105
|
+
:define_paperclip_callbacks,
|
|
106
|
+
:post_process, :"#{@name}_post_process", :"#{@name}_validate"
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
module ClassMethods
|
|
111
|
+
def attachment_definitions
|
|
112
|
+
Paperclip::AttachmentRegistry.definitions_for(self)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
|
12
|
+
# to that binary, the values to interpolate and some local options.
|
|
13
|
+
#
|
|
14
|
+
# :cmd -> The name of a binary to run.
|
|
15
|
+
#
|
|
16
|
+
# :arguments -> The command line arguments to that binary.
|
|
17
|
+
#
|
|
18
|
+
# :interpolation_values -> Values to be interpolated into the arguments.
|
|
19
|
+
#
|
|
20
|
+
# :local_options -> The options to be used by Cocain::CommandLine.
|
|
21
|
+
# These could be: runner
|
|
22
|
+
# logger
|
|
23
|
+
# swallow_stderr
|
|
24
|
+
# expected_outcodes
|
|
25
|
+
# environment
|
|
26
|
+
# runner_options
|
|
27
|
+
#
|
|
28
|
+
def run(cmd, arguments = "", interpolation_values = {}, local_options = {})
|
|
29
|
+
command_path = options[:command_path]
|
|
30
|
+
terrapin_path_array = Terrapin::CommandLine.path.try(:split, Terrapin::OS.path_separator)
|
|
31
|
+
Terrapin::CommandLine.path = [terrapin_path_array, command_path].flatten.compact.uniq
|
|
32
|
+
if logging? && (options[:log_command] || local_options[:log_command])
|
|
33
|
+
local_options = local_options.merge(logger: logger)
|
|
34
|
+
end
|
|
35
|
+
Terrapin::CommandLine.new(cmd, arguments, local_options).run(interpolation_values)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Find all instances of the given Active Record model +klass+ with attachment +name+.
|
|
39
|
+
# This method is used by the refresh rake tasks.
|
|
40
|
+
def each_instance_with_attachment(klass, name)
|
|
41
|
+
class_for(klass).unscoped.where("#{name}_file_name IS NOT NULL").find_each do |instance|
|
|
42
|
+
yield(instance)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def class_for(class_name)
|
|
47
|
+
class_name.split("::").inject(Object) do |klass, partial_class_name|
|
|
48
|
+
if klass.const_defined?(partial_class_name)
|
|
49
|
+
klass.const_get(partial_class_name, false)
|
|
50
|
+
else
|
|
51
|
+
klass.const_missing(partial_class_name)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def reset_duplicate_clash_check!
|
|
57
|
+
@names_url = nil
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
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
|