├── .editorconfig
├── .gitignore
├── .travis.yml
├── composer.json
├── phpunit.xml
├── readme.md
├── release.sh
├── scripts
└── install_php_extensions.sh
├── src
├── Folklore
│ └── Image
│ │ ├── Events
│ │ └── ImageSaved.php
│ │ ├── Exception
│ │ ├── Exception.php
│ │ ├── FileMissingException.php
│ │ ├── FormatException.php
│ │ └── ParseException.php
│ │ ├── Facades
│ │ └── Image.php
│ │ ├── ImageController.php
│ │ ├── ImageManager.php
│ │ ├── ImageProxy.php
│ │ ├── ImageServe.php
│ │ └── ImageServiceProvider.php
└── resources
│ ├── assets
│ ├── .gitkeep
│ └── js
│ │ └── image.js
│ └── config
│ ├── .gitkeep
│ └── image.php
└── tests
├── .gitkeep
├── ImageProxyTestCase.php
├── ImageServeTestCase.php
├── ImageTestCase.php
└── fixture
├── cache
└── .gitignore
├── custom
└── .gitignore
├── image.jpg
├── image_small.jpg
└── wrong.jpg
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.php]
2 | indent_style = space
3 | indent_size = 4
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /_book
3 | /coverage
4 | /docs
5 | /js
6 | node_modules
7 | composer.phar
8 | composer.lock
9 | package-lock.json
10 | .DS_Store
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | cache:
4 | directories:
5 | - $HOME/.cache/pip
6 | - $HOME/.composer/cache/files
7 | - ${TRAVIS_BUILD_DIR}/travis/extension-cache
8 |
9 | php:
10 | - 5.5
11 | - 5.6
12 | - 7.0
13 | - 7.1
14 |
15 | env:
16 | - ILLUMINATE_VERSION=5.1.* PHPUNIT_VERSION=~4.0
17 | - ILLUMINATE_VERSION=5.2.* PHPUNIT_VERSION=~4.0
18 | - ILLUMINATE_VERSION=5.3.* PHPUNIT_VERSION=~5.0
19 | - ILLUMINATE_VERSION=5.4.* PHPUNIT_VERSION=~5.7
20 | - ILLUMINATE_VERSION=5.5.* PHPUNIT_VERSION=~6.0
21 | - ILLUMINATE_VERSION=5.6.* PHPUNIT_VERSION=~7.0
22 | - ILLUMINATE_VERSION=5.7.* PHPUNIT_VERSION=~7.0 COVERAGE=true
23 |
24 | matrix:
25 | # For each PHP version we exclude the coverage env, except for PHP 7.1
26 | exclude:
27 | # Test only Laravel 5.1 and 5.2 on PHP 5.5
28 | - php: 5.5
29 | env: ILLUMINATE_VERSION=5.3.* PHPUNIT_VERSION=~5.0
30 | - php: 5.5
31 | env: ILLUMINATE_VERSION=5.4.* PHPUNIT_VERSION=~5.7
32 | - php: 5.5
33 | env: ILLUMINATE_VERSION=5.5.* PHPUNIT_VERSION=~6.0
34 | - php: 5.5
35 | env: ILLUMINATE_VERSION=5.6.* PHPUNIT_VERSION=~7.0
36 | - php: 5.5
37 | env: ILLUMINATE_VERSION=5.7.* PHPUNIT_VERSION=~7.0 COVERAGE=true
38 | # Don't test Laravel 5.5 and up on PHP 5.6
39 | - php: 5.6
40 | env: ILLUMINATE_VERSION=5.5.* PHPUNIT_VERSION=~6.0
41 | - php: 5.6
42 | env: ILLUMINATE_VERSION=5.6.* PHPUNIT_VERSION=~7.0
43 | - php: 5.6
44 | env: ILLUMINATE_VERSION=5.7.* PHPUNIT_VERSION=~7.0 COVERAGE=true
45 | # Test Laravel 5.5 and down on PHP 7.0
46 | - php: 7.0
47 | env: ILLUMINATE_VERSION=5.6.* PHPUNIT_VERSION=~7.0
48 | - php: 7.0
49 | env: ILLUMINATE_VERSION=5.7.* PHPUNIT_VERSION=~7.0 COVERAGE=true
50 | # Test only Laravel 5.4 and up on PHP 7.1
51 | - php: 7.1
52 | env: ILLUMINATE_VERSION=5.1.* PHPUNIT_VERSION=~4.0
53 | - php: 7.1
54 | env: ILLUMINATE_VERSION=5.2.* PHPUNIT_VERSION=~4.0
55 | - php: 7.1
56 | env: ILLUMINATE_VERSION=5.3.* PHPUNIT_VERSION=~5.0
57 |
58 | before_install:
59 | - cp ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini ~/xdebug.ini
60 | - phpenv config-rm xdebug.ini
61 | - sudo apt-get install graphicsmagick libgraphicsmagick1-dev
62 | - pear config-set preferred_state beta
63 | - pecl channel-update pecl.php.net
64 | - ./scripts/install_php_extensions.sh "imagick.so:imagick gmagick.so:gmagick"
65 | - composer global require hirak/prestissimo --update-no-dev
66 | - composer require "illuminate/support:${ILLUMINATE_VERSION}" --no-update --prefer-dist
67 | - composer require "orchestra/testbench:${ILLUMINATE_VERSION/5\./3\.}" --no-update --prefer-dist
68 | - composer require "phpunit/phpunit:${PHPUNIT_VERSION}" --no-update --prefer-dist
69 |
70 | install: travis_retry composer install --no-interaction --prefer-dist
71 |
72 | before_script: phpenv config-add ~/xdebug.ini
73 |
74 | script: vendor/bin/phpunit
75 |
76 | after_success: sh -c "if [ ! -z ${COVERAGE+x} ]; then travis_retry php vendor/bin/php-coveralls; fi"
77 |
78 | notifications:
79 | email: false
80 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "folklore/image",
3 | "description": "Image manipulation library for Laravel 5 based on Imagine and inspired by Croppa for easy url based manipulation",
4 | "keywords": ["laravel","image","imagick","gd","imagine","watermark","gmagick","thumbnail"],
5 | "homepage": "http://github.com/Folkloreatelier/laravel-image",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Folklore",
10 | "email": "info@atelierfolklore.ca",
11 | "homepage": "http://atelierfolklore.ca"
12 | },
13 | {
14 | "name": "David Mongeau-Petitpas",
15 | "email": "dmp@atelierfolklore.ca",
16 | "homepage": "http://mongo.ca",
17 | "role": "Developer"
18 | }
19 | ],
20 | "require": {
21 | "php": ">=5.5.9",
22 | "illuminate/support": "5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*|5.7.*",
23 | "guzzlehttp/guzzle": "5.3|~6.0",
24 | "imagine/imagine": "0.6.*"
25 | },
26 | "require-dev": {
27 | "fzaninotto/faker": "~1.4",
28 | "orchestra/testbench": "3.1.*|3.2.*|3.3.*|3.4.*|3.5.*|3.6.*|3.7.*",
29 | "mockery/mockery": "0.9.*|1.0.*",
30 | "phpunit/phpunit": "~4.0|~4.1|~5.4|~5.7|~6.0|~7.0",
31 | "php-coveralls/php-coveralls": "^2.1"
32 | },
33 | "autoload": {
34 | "psr-0": {
35 | "Folklore\\Image\\": "src/",
36 | "Folklore\\Image\\Tests": "tests/"
37 | }
38 | },
39 | "autoload-dev": {
40 | "classmap": [
41 | "tests/"
42 | ]
43 | },
44 | "extra": {
45 | "laravel": {
46 | "providers": [
47 | "Folklore\\Image\\ImageServiceProvider"
48 | ],
49 | "aliases": {
50 | "Image": "Folklore\\Image\\Facades\\Image"
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | ./tests/ImageTestCase.php
15 | ./tests/ImageServeTestCase.php
16 | ./tests/ImageProxyTestCase.php
17 |
18 |
19 |
20 |
21 | ./src/Folklore/Image
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Laravel Image
2 | Laravel Image is an image manipulation package for Laravel 4 and 5 based on the [PHP Imagine library](https://github.com/avalanche123/Imagine). It is inspired by [Croppa](https://github.com/BKWLD/croppa) as it can use specially formatted urls to do the manipulations. It supports basic image manipulations such as resize, crop, rotation and flip. It also supports effects such as negative, grayscale, gamma, colorize and blur. You can also define custom filters for greater flexibility.
3 |
4 | [](https://packagist.org/packages/folklore/image)
5 | [](https://travis-ci.org/Folkloreatelier/laravel-image)
6 | [](https://packagist.org/packages/folklore/image)
7 |
8 | The main difference between this package and other image manipulation libraries is that you can use parameters directly in the url to manipulate the image. A manipulated version of the image is then saved in the same path as the original image, **creating a static version of the file and bypassing PHP for all future requests**.
9 |
10 | For example, if you have an image at this URL:
11 |
12 | /uploads/photo.jpg
13 |
14 | To create a 300x300 version of this image in black and white, you use the URL:
15 |
16 | /uploads/photo-image(300x300-crop-grayscale).jpg
17 |
18 | To help you generate the URL to an image, you can use the `Image::url()` method
19 |
20 | ```php
21 | Image::url('/uploads/photo.jpg',300,300,array('crop','grayscale'));
22 | ```
23 |
24 | or
25 |
26 | ```html
27 |
28 | ```
29 |
30 | Alternatively, you can programmatically manipulate images using the `Image::make()` method. It supports all the same options as the `Image::url()` method.
31 |
32 | ```php
33 | Image::make('/uploads/photo.jpg',array(
34 | 'width' => 300,
35 | 'height' => 300,
36 | 'grayscale' => true
37 | ))->save('/path/to/the/thumbnail.jpg');
38 | ```
39 |
40 | or use directly the Imagine library
41 |
42 | ```php
43 | $thumbnail = Image::open('/uploads/photo.jpg')
44 | ->thumbnail(new Imagine\Image\Box(300,300));
45 |
46 | $thumbnail->effects()->grayscale();
47 |
48 | $thumbnail->save('/path/to/the/thumbnail.jpg');
49 | ```
50 |
51 | ## Features
52 |
53 | This package use [Imagine](https://github.com/avalanche123/Imagine) for image manipulation. Imagine is compatible with GD2, Imagick, Gmagick and supports a lot of [features](http://imagine.readthedocs.org/en/latest/).
54 |
55 | This package also provides some common filters ready to use ([more on this](https://github.com/Folkloreatelier/laravel-image/wiki/Image-filters)):
56 | - Resize
57 | - Crop (with position)
58 | - Rotation
59 | - Black and white
60 | - Invert
61 | - Gamma
62 | - Blur
63 | - Colorization
64 | - Interlace
65 |
66 | ## Version Compatibility
67 |
68 | Laravel | Image
69 | :---------|:----------
70 | 4.2.x | 0.1.x
71 | 5.0.x | 0.2.x
72 | 5.1.x | 0.3.x
73 | 5.2.x | 0.3.x
74 |
75 | ## Installation
76 |
77 | #### Dependencies:
78 |
79 | * [Laravel 5.x](https://github.com/laravel/laravel)
80 | * [Imagine 0.6.x](https://github.com/avalanche123/Imagine)
81 |
82 | #### Server Requirements:
83 |
84 | * [gd](http://php.net/manual/en/book.image.php) or [Imagick](http://php.net/manual/fr/book.imagick.php) or [Gmagick](http://www.php.net/manual/fr/book.gmagick.php)
85 | * [exif](http://php.net/manual/en/book.exif.php) - Required to get image format.
86 |
87 | #### Installation:
88 |
89 | **1-** Require the package via Composer in your `composer.json`.
90 | ```json
91 | {
92 | "require": {
93 | "folklore/image": "0.3.*"
94 | }
95 | }
96 | ```
97 |
98 | **2-** Run Composer to install or update the new requirement.
99 |
100 | ```bash
101 | $ composer install
102 | ```
103 |
104 | or
105 |
106 | ```bash
107 | $ composer update
108 | ```
109 |
110 | **3-** Add the service provider to your `app/config/app.php` file
111 | ```php
112 | 'Folklore\Image\ImageServiceProvider',
113 | ```
114 |
115 | **4-** Add the facade to your `app/config/app.php` file
116 | ```php
117 | 'Image' => 'Folklore\Image\Facades\Image',
118 | ```
119 |
120 | **5-** Publish the configuration file and public files
121 |
122 | ```bash
123 | $ php artisan vendor:publish --provider="Folklore\Image\ImageServiceProvider"
124 | ```
125 |
126 | **6-** Review the configuration file
127 |
128 | ```
129 | app/config/image.php
130 | ```
131 |
132 | ## Documentation
133 | * [Complete documentation](https://github.com/Folkloreatelier/image/wiki)
134 | * [Configuration options](https://github.com/Folkloreatelier/image/wiki/Configuration-options)
135 |
136 | ## Roadmap
137 | Here are some features we would like to add in the future. Feel free to collaborate and improve this library.
138 |
139 | * More built-in filters such as Brightness and Contrast
140 | * More configuration when serving images
141 | * Artisan command to manipulate images
142 | * Support for batch operations on multiple files
143 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 |
3 | #Fetch remote tags
4 | git fetch origin 'refs/tags/*:refs/tags/*'
5 |
6 | #Variables
7 | LAST_VERSION=$(git tag -l | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | tail -n 1)
8 | NEXT_VERSION=$(echo $LAST_VERSION | awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{if(length($NF+1)>length($NF))$(NF-1)++; $NF=sprintf("%0*d", length($NF), ($NF+1)%(10^length($NF))); print}')
9 | VERSION=${1-${NEXT_VERSION}}
10 | DEFAULT_MESSAGE="Release"
11 | MESSAGE=${2-${DEFAULT_MESSAGE}}
12 | RELEASE_BRANCH="release/$VERSION"
13 |
14 | # Commit uncommited changes
15 | git add .
16 | git commit -am $MESSAGE
17 | git push origin develop
18 |
19 | # Merge develop branch in master
20 | git checkout master
21 | git merge develop
22 |
23 | # Tag and push master
24 | git tag $VERSION
25 | git push origin master --tags
26 |
27 | # Return to develop
28 | git checkout develop
29 |
--------------------------------------------------------------------------------
/scripts/install_php_extensions.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | EXTENSIONS=$1
3 | EXTENSION_CACHE_DIR=${TRAVIS_BUILD_DIR}/travis/extension-cache/`php-config --vernum`
4 | INI_DIR=${TRAVIS_BUILD_DIR}/travis/ini/
5 | PHP_TARGET_DIR=`php-config --extension-dir`
6 |
7 | mkdir -p ${EXTENSION_CACHE_DIR}
8 |
9 | if [ -d ${EXTENSION_CACHE_DIR} ]
10 | then
11 | cp ${EXTENSION_CACHE_DIR}/* ${PHP_TARGET_DIR}
12 | fi
13 |
14 | mkdir -p ${INI_DIR}
15 | mkdir -p ${EXTENSION_CACHE_DIR}
16 |
17 | for extension in $EXTENSIONS
18 | do
19 | FILENAME=`echo $extension|cut -d : -f 1`
20 | PACKAGE=`echo $extension|cut -d : -f 2`
21 | if [ ! -f ${PHP_TARGET_DIR}/${FILENAME} ]
22 | then
23 | echo "$FILENAME not found in extension dir, compiling"
24 | printf "yes\n" | pecl install ${PACKAGE} || true
25 | else
26 | echo "Adding $FILENAME to php config"
27 | echo "extension = $FILENAME" > ${INI_DIR}/${FILENAME}.ini
28 | phpenv config-add ${INI_DIR}/${FILENAME}.ini
29 | fi
30 | if [ -f ${PHP_TARGET_DIR}/${FILENAME} ]
31 | then
32 | echo "Copying $FILENAME to php config"
33 | cp ${PHP_TARGET_DIR}/${FILENAME} ${EXTENSION_CACHE_DIR}
34 | fi
35 | done
36 |
--------------------------------------------------------------------------------
/src/Folklore/Image/Events/ImageSaved.php:
--------------------------------------------------------------------------------
1 | path = $path;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Folklore/Image/Exception/Exception.php:
--------------------------------------------------------------------------------
1 | serve($path);
25 | } catch (ParseException $e) {
26 | return abort(404);
27 | } catch (FileMissingException $e) {
28 | return abort(404);
29 | } catch (Exception $e) {
30 | return abort(500);
31 | }
32 | }
33 |
34 | public function proxy($path)
35 | {
36 | // Serve the image response from proxy. If there is a file missing
37 | // exception or parse exception, throw a 404.
38 | try {
39 | return app('image')->proxy($path);
40 | } catch (ParseException $e) {
41 | return abort(404);
42 | } catch (FileMissingException $e) {
43 | return abort(404);
44 | } catch (Exception $e) {
45 | return abort(500);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Folklore/Image/ImageManager.php:
--------------------------------------------------------------------------------
1 | null,
24 | 'height' => null,
25 | 'quality' => 80,
26 | 'filters' => array()
27 | );
28 |
29 | /**
30 | * All of the custom filters.
31 | *
32 | * @var array
33 | */
34 | protected $filters = array();
35 |
36 | /**
37 | * Return an URL to process the image
38 | *
39 | * @param string $src
40 | * @param int $width
41 | * @param int $height
42 | * @param array $options
43 | * @return string
44 | */
45 | public function url($src, $width = null, $height = null, $options = array())
46 | {
47 |
48 | // Don't allow empty strings
49 | if (empty($src)) {
50 | return;
51 | }
52 |
53 | // Extract the path from a URL if a URL was provided instead of a path
54 | $src = parse_url($src, PHP_URL_PATH);
55 |
56 | //If width parameter is an array, use it as options
57 | if (is_array($width)) {
58 | $options = $width;
59 | $width = null;
60 | $height = null;
61 | }
62 |
63 | $config = $this->app['config'];
64 | $url_parameter = isset($options['url_parameter']) ? $options['url_parameter']:$config['image.url_parameter'];
65 | $url_parameter_separator = isset($options['url_parameter_separator']) ? $options['url_parameter_separator']:$config['image.url_parameter_separator'];
66 | unset($options['url_parameter'],$options['url_parameter_separator']);
67 |
68 | //Get size
69 | if (isset($options['width'])) {
70 | $width = $options['width'];
71 | }
72 | if (isset($options['height'])) {
73 | $height = $options['height'];
74 | }
75 | if (empty($width)) {
76 | $width = '_';
77 | }
78 | if (empty($height)) {
79 | $height = '_';
80 | }
81 |
82 | // Produce the parameter parts
83 | $params = array();
84 |
85 | //Add size only if present
86 | if ($width != '_' || $height != '_') {
87 | $params[] = $width.'x'.$height;
88 | }
89 |
90 | // Build options. If the key as no value or is equal to
91 | // true, only the key is added.
92 | if ($options && is_array($options)) {
93 | foreach ($options as $key => $val) {
94 | if (is_numeric($key)) {
95 | $params[] = $val;
96 | } elseif ($val === true || $val === null) {
97 | $params[] = $key;
98 | } elseif (is_array($val)) {
99 | $params[] = $key.'('.implode(',', $val).')';
100 | } else {
101 | $params[] = $key.'('.$val.')';
102 | }
103 | }
104 | }
105 |
106 | //Create the url parameter
107 | $params = implode($url_parameter_separator, $params);
108 | $parameter = str_replace('{options}', $params, $url_parameter);
109 |
110 | // Break the path apart and put back together again
111 | $parts = pathinfo($src);
112 | $host = isset($options['host']) ? $options['host']:$this->app['config']['image.host'];
113 | $dir = trim($parts['dirname'], '/');
114 |
115 | $path = array();
116 | $path[] = rtrim($host, '/');
117 |
118 | if ($prefix = $this->app['config']->get('image.write_path')) {
119 | $path[] = trim($prefix, '/');
120 | }
121 |
122 | if (!empty($dir)) {
123 | $path[] = $dir;
124 | }
125 |
126 | $filename = array();
127 | $filename[] = $parts['filename'].$parameter;
128 | if (!empty($parts['extension'])) {
129 | $filename[] = $parts['extension'];
130 | }
131 | $path[] = implode('.', $filename);
132 |
133 | return implode('/', $path);
134 |
135 | }
136 |
137 | /**
138 | * Make an image and apply options
139 | *
140 | * @param string $path The path of the image
141 | * @param array $options The manipulations to apply on the image
142 | * @return ImageInterface
143 | */
144 | public function make($path, $options = array())
145 | {
146 | //Get app config
147 | $config = $this->app['config'];
148 |
149 | // See if the referenced file exists and is an image
150 | if (!($path = $this->getRealPath($path))) {
151 | throw new FileMissingException('Image file missing');
152 | }
153 |
154 | // Get image format
155 | $format = $this->format($path);
156 | if (!$format) {
157 | throw new FormatException('Image format is not supported');
158 | }
159 |
160 | // Check if all filters exists
161 | if (isset($options['filters']) && sizeof($options['filters'])) {
162 | foreach ($options['filters'] as $filter) {
163 | $filter = (array)$filter;
164 | $key = $filter[0];
165 | if (!$this->filters[$key]) {
166 | throw new Exception('Custom filter "'.$key.'" doesn\'t exists.');
167 | }
168 | }
169 | }
170 |
171 | // Increase memory limit, cause some images require a lot to resize
172 | if ($config->get('image.memory_limit')) {
173 | ini_set('memory_limit', $config->get('image.memory_limit'));
174 | }
175 |
176 | //Open the image
177 | $image = $this->open($path);
178 |
179 | //Merge options with the default
180 | $options = array_merge($this->defaultOptions, $options);
181 |
182 | // Apply the custom filter on the image. Replace the
183 | // current image with the return value.
184 | if (isset($options['filters']) && sizeof($options['filters'])) {
185 | foreach ($options['filters'] as $filter) {
186 | $arguments = (array)$filter;
187 | array_unshift($arguments, $image);
188 |
189 | $image = call_user_func_array(array($this,'applyCustomFilter'), $arguments);
190 | }
191 | }
192 |
193 | // Resize only if one or both width and height values are set.
194 | if ($options['width'] !== null || $options['height'] !== null) {
195 | $crop = isset($options['crop']) ? $options['crop']:false;
196 |
197 | $image = $this->thumbnail($image, $options['width'], $options['height'], $crop);
198 | }
199 |
200 | // Apply built-in filters by checking fi a method $this->filterName
201 | // exists. Also if the value of the option is false, the filter
202 | // is ignored.
203 | foreach ($options as $key => $arguments) {
204 | $method = 'filter'.ucfirst($key);
205 |
206 | if ($arguments !== false && method_exists($this, $method)) {
207 | $arguments = (array)$arguments;
208 | array_unshift($arguments, $image);
209 |
210 | $image = call_user_func_array(array($this, $method), $arguments);
211 | }
212 | }
213 |
214 |
215 |
216 | return $image;
217 | }
218 |
219 | /**
220 | * Serve an image from an url
221 | *
222 | * @param string $path
223 | * @param array $config
224 | * @return Illuminate\Support\Facades\Response
225 | */
226 | public function serve($path, $config = array())
227 | {
228 | //Use user supplied quality or the config value
229 | $quality = array_get($config, 'quality', $this->app['config']['image.quality']);
230 | //if nothing works fallback to the hardcoded value
231 | $quality = $quality ?: $this->defaultOptions['quality'];
232 |
233 | //Merge config with defaults
234 | $config = array_merge(array(
235 | 'quality' => $quality,
236 | 'custom_filters_only' => $this->app['config']['image.serve_custom_filters_only'],
237 | 'write_image' => $this->app['config']['image.write_image'],
238 | 'write_path' => $this->app['config']['image.write_path']
239 | ), $config);
240 |
241 | $serve = new ImageServe($this, $config);
242 |
243 | return $serve->response($path);
244 | }
245 |
246 | /**
247 | * Proxy an image
248 | *
249 | * @param string $path
250 | * @param array $config
251 | * @return Illuminate\Support\Facades\Response
252 | */
253 | public function proxy($path, $config = array())
254 | {
255 | //Merge config with defaults
256 | $config = array_merge(array(
257 | 'tmp_path' => $this->app['config']['image.proxy_tmp_path'],
258 | 'filesystem' => $this->app['config']['image.proxy_filesystem'],
259 | 'cache' => $this->app['config']['image.proxy_cache'],
260 | 'cache_expiration' => $this->app['config']['image.proxy_cache_expiration'],
261 | 'write_image' => $this->app['config']['image.proxy_write_image'],
262 | 'cache_filesystem' => $this->app['config']['image.proxy_cache_filesystem']
263 | ), $config);
264 |
265 | $serve = new ImageProxy($this, $config);
266 | return $serve->response($path);
267 | }
268 |
269 | /**
270 | * Register a custom filter.
271 | *
272 | * @param string $name The name of the filter
273 | * @param Closure|string $filter
274 | * @return void
275 | */
276 | public function filter($name, $filter)
277 | {
278 | $this->filters[$name] = $filter;
279 | }
280 |
281 | /**
282 | * Create a thumbnail from an image
283 | *
284 | * @param ImageInterface|string $image An image instance or the path to an image
285 | * @param int $width
286 | * @return ImageInterface
287 | */
288 | public function thumbnail($image, $width = null, $height = null, $crop = true)
289 | {
290 | //If $image is a path, open it
291 | if (is_string($image)) {
292 | $image = $this->open($image);
293 | }
294 |
295 | //Get new size
296 | $imageSize = $image->getSize();
297 | $newWidth = $width === null ? $imageSize->getWidth():$width;
298 | $newHeight = $height === null ? $imageSize->getHeight():$height;
299 | $size = new Box($newWidth, $newHeight);
300 |
301 | $ratios = array(
302 | $size->getWidth() / $imageSize->getWidth(),
303 | $size->getHeight() / $imageSize->getHeight()
304 | );
305 |
306 | $thumbnail = $image->copy();
307 |
308 | $thumbnail->usePalette($image->palette());
309 | $thumbnail->strip();
310 |
311 | if (!$crop) {
312 | $ratio = min($ratios);
313 | } else {
314 | $ratio = max($ratios);
315 | }
316 |
317 | if ($crop) {
318 |
319 | $imageSize = $thumbnail->getSize()->scale($ratio);
320 | $thumbnail->resize($imageSize);
321 |
322 | $x = max(0, round(($imageSize->getWidth() - $size->getWidth()) / 2));
323 | $y = max(0, round(($imageSize->getHeight() - $size->getHeight()) / 2));
324 |
325 | $cropPositions = $this->getCropPositions($crop);
326 |
327 | if ($cropPositions[0] === 'top') {
328 | $y = 0;
329 | } elseif ($cropPositions[0] === 'bottom') {
330 | $y = $imageSize->getHeight() - $size->getHeight();
331 | }
332 |
333 | if ($cropPositions[1] === 'left') {
334 | $x = 0;
335 | } elseif ($cropPositions[1] === 'right') {
336 | $x = $imageSize->getWidth() - $size->getWidth();
337 | }
338 |
339 | $point = new Point($x, $y);
340 |
341 | $thumbnail->crop($point, $size);
342 | } else {
343 | if (!$imageSize->contains($size)) {
344 | $imageSize = $imageSize->scale($ratio);
345 | $thumbnail->resize($imageSize);
346 | } else {
347 | $imageSize = $thumbnail->getSize()->scale($ratio);
348 | $thumbnail->resize($imageSize);
349 | }
350 | }
351 |
352 | //Create the thumbnail
353 | return $thumbnail;
354 | }
355 |
356 | /**
357 | * Get the format of an image
358 | *
359 | * @param string $path The path to an image
360 | * @return ImageInterface
361 | */
362 | public function format($path)
363 | {
364 |
365 | $format = @exif_imagetype($path);
366 | switch ($format) {
367 | case IMAGETYPE_GIF:
368 | return 'gif';
369 | break;
370 | case IMAGETYPE_JPEG:
371 | return 'jpeg';
372 | break;
373 | case IMAGETYPE_PNG:
374 | return 'png';
375 | break;
376 | }
377 |
378 | return null;
379 | }
380 |
381 | /**
382 | * Delete a file and all manipulated files
383 | *
384 | * @param string $path The path to an image
385 | * @return void
386 | */
387 | public function delete($path)
388 | {
389 | $files = $this->getFiles($path);
390 |
391 | foreach ($files as $file) {
392 | if (!unlink($file)) {
393 | throw new Exception('Unlink failed: '.$file);
394 | }
395 | }
396 | }
397 |
398 | /**
399 | * Delete all manipulated files
400 | *
401 | * @param string $path The path to an image
402 | * @return void
403 | */
404 | public function deleteManipulated($path)
405 | {
406 | $files = $this->getFiles($path, false);
407 |
408 | foreach ($files as $file) {
409 | if (!unlink($file)) {
410 | throw new Exception('Unlink failed: '.$file);
411 | }
412 | }
413 | }
414 |
415 | /**
416 | * Get the URL pattern
417 | *
418 | * @return string
419 | */
420 | public function pattern($parameter = null, $pattern = null)
421 | {
422 | //Replace the {options} with the options regular expression
423 | $config = $this->app['config'];
424 | $parameter = !isset($parameter) ? $config['image.url_parameter']:$parameter;
425 | $parameter = preg_replace('/\\\{\s*options\s*\\\}/', '([0-9a-zA-Z\(\),\-/._]+?)?', preg_quote($parameter));
426 |
427 | if(!$pattern)
428 | {
429 | $pattern = $config->get('image.pattern', '^(.*){parameters}\.(jpg|jpeg|png|gif|JPG|JPEG|PNG|GIF)$');
430 | }
431 | $pattern = preg_replace('/\{\s*parameters\s*\}/', $parameter, $pattern);
432 |
433 | return $pattern;
434 | }
435 |
436 | /**
437 | * Parse the path for the original path of the image and options
438 | *
439 | * @param string $path A path to parse
440 | * @param array $config Configuration options for the parsing
441 | * @return array
442 | */
443 | public function parse($path, $config = array())
444 | {
445 | //Default config
446 | $config = array_merge(array(
447 | 'custom_filters_only' => false,
448 | 'url_parameter' => null,
449 | 'url_parameter_separator' => $this->app['config']['image.url_parameter_separator']
450 | ), $config);
451 |
452 | $parsedOptions = array();
453 | if (preg_match('#'.$this->pattern($config['url_parameter']).'#i', $path, $matches)) {
454 | //Get path and options
455 | $path = $matches[1].'.'.$matches[3];
456 | $pathOptions = $matches[2];
457 |
458 | // Parse options from path
459 | $parsedOptions = $this->parseOptions($pathOptions, $config);
460 | }
461 |
462 | return array(
463 | 'path' => $path,
464 | 'options' => $parsedOptions
465 | );
466 | }
467 |
468 | /**
469 | * Parse options from url string
470 | *
471 | * @param string $option_path The path contaning all the options
472 | * @param array $config Configuration options for the parsing
473 | * @return array
474 | */
475 | protected function parseOptions($option_path, $config = array())
476 | {
477 |
478 | //Default config
479 | $config = array_merge(array(
480 | 'custom_filters_only' => false,
481 | 'url_parameter_separator' => $this->app['config']['image.url_parameter_separator']
482 | ), $config);
483 |
484 | $options = array();
485 |
486 | // These will look like (depends on the url_parameter_separator): "-colorize(CC0000)-greyscale"
487 | $option_path_parts = explode($config['url_parameter_separator'], $option_path);
488 |
489 | // Loop through the params and make the options key value pairs
490 | foreach ($option_path_parts as $option) {
491 | //Check if the option is a size or is properly formatted
492 | if (!$config['custom_filters_only'] && preg_match('#([0-9]+|_)x([0-9]+|_)#i', $option, $matches)) {
493 | $options['width'] = $matches[1] === '_' ? null:(int)$matches[1];
494 | $options['height'] = $matches[2] === '_' ? null:(int)$matches[2];
495 | continue;
496 | } elseif (!preg_match('#(\w+)(?:\(([\w,.]+)\))?#i', $option, $matches)) {
497 | continue;
498 | }
499 |
500 | //Check if the key is valid
501 | $key = $matches[1];
502 | if (!$this->isValidOption($key)) {
503 | throw new ParseException('The option key "'.$key.'" does not exists.');
504 | }
505 |
506 | // If the option is a custom filter, check if it's a closure or an array.
507 | // If it's an array, merge it with options
508 | if (isset($this->filters[$key])) {
509 | if (is_object($this->filters[$key]) && is_callable($this->filters[$key])) {
510 | $arguments = isset($matches[2]) ? explode(',', $matches[2]):array();
511 | array_unshift($arguments, $key);
512 | $options['filters'][] = $arguments;
513 | } elseif (is_array($this->filters[$key])) {
514 | $options = array_merge($options, $this->filters[$key]);
515 | }
516 | } elseif (!$config['custom_filters_only']) {
517 | if (isset($matches[2])) {
518 | $options[$key] = strpos($matches[2], ',') === true ? explode(',', $matches[2]):$matches[2];
519 | } else {
520 | $options[$key] = true;
521 | }
522 | } else {
523 | throw new ParseException('The option key "'.$key.'" does not exists.');
524 | }
525 | }
526 |
527 | // Merge the options with defaults
528 | return $options;
529 | }
530 |
531 | /**
532 | * Check if an option key is valid by checking if a
533 | * $this->filterName() method is present or if a custom filter
534 | * is registered.
535 | *
536 | * @param string $key Option key to check
537 | * @return boolean
538 | */
539 | protected function isValidOption($key)
540 | {
541 | if (in_array($key, array('crop','width','height'))) {
542 | return true;
543 | }
544 |
545 | $method = 'filter'.ucfirst($key);
546 | if (method_exists($this, $method) || isset($this->filters[$key])) {
547 | return true;
548 | }
549 | return false;
550 | }
551 |
552 | /**
553 | * Get real path
554 | *
555 | * @param string $path Path to an original image
556 | * @return string
557 | */
558 | public function getRealPath($path)
559 | {
560 | if (is_file(realpath($path))) {
561 | return realpath($path);
562 | }
563 |
564 | //Get directories
565 | $dirs = $this->app['config']['image.src_dirs'];
566 | if ($this->app['config']['image.write_path']) {
567 | $dirs[] = $this->app['config']['image.write_path'];
568 | }
569 |
570 | // Loop through all the directories files may be uploaded to
571 | foreach ($dirs as $dir) {
572 | $dir = rtrim($dir, '/');
573 |
574 | // Check that directory exists
575 | if (!is_dir($dir)) {
576 | continue;
577 | }
578 |
579 | // Look for the image in the directory
580 | $src = realpath($dir.'/'.ltrim($path, '/'));
581 | if (is_file($src)) {
582 | return $src;
583 | }
584 | }
585 |
586 | // None found
587 | return false;
588 | }
589 |
590 | /**
591 | * Get all files (including manipulated images)
592 | *
593 | * @param string $path Path to an original image
594 | * @return array
595 | */
596 | protected function getFiles($path, $withOriginal = true)
597 | {
598 |
599 | $images = array();
600 |
601 | //Check path
602 | $path = urldecode($path);
603 | if (!($path = $this->getRealPath($path))) {
604 | return $images;
605 | }
606 |
607 | // Add the source image to the list
608 | if ($withOriginal) {
609 | $images[] = $path;
610 | }
611 |
612 | // Loop through the contents of the source and write directory and get
613 | // all files that match the pattern
614 | $parts = pathinfo($path);
615 | $dirs = [$parts['dirname']];
616 | $dirs = [$parts['dirname']];
617 | if ($this->app['config']['image.write_path']) {
618 | $dirs[] = $this->app['config']['image.write_path'];
619 | }
620 | foreach ($dirs as $directory) {
621 | $files = scandir($directory);
622 | foreach ($files as $file) {
623 | if (strpos($file, $parts['filename']) === false || !preg_match('#'.$this->pattern().'#', $file)) {
624 | continue;
625 | }
626 | $images[] = $directory.'/'.$file;
627 | }
628 | }
629 |
630 | // Return the list
631 | return $images;
632 | }
633 |
634 | /**
635 | * Apply a custom filter or an image
636 | *
637 | * @param ImageInterface $image An image instance
638 | * @param string $name The filter name
639 | * @return ImageInterface|array
640 | */
641 | protected function applyCustomFilter(ImageInterface $image, $name)
642 | {
643 | //Get all arguments following $name and add $image as the first
644 | //arguments then call the filter closure
645 | $arguments = array_slice(func_get_args(), 2);
646 | array_unshift($arguments, $image);
647 | $return = call_user_func_array($this->filters[$name], $arguments);
648 |
649 | // If the return value is an instance of ImageInterface,
650 | // replace the current image with it.
651 | if ($return instanceof ImageInterface) {
652 | $image = $return;
653 | }
654 |
655 | return $image;
656 | }
657 |
658 | /**
659 | * Apply rotate filter
660 | *
661 | * @param ImageInterface $image An image instance
662 | * @param float $degree The rotation degree
663 | * @return void
664 | */
665 | protected function filterRotate(ImageInterface $image, $degree)
666 | {
667 | return $image->rotate($degree);
668 | }
669 |
670 | /**
671 | * Apply grayscale filter
672 | *
673 | * @param ImageInterface $image An image instance
674 | * @return void
675 | */
676 | protected function filterGrayscale(ImageInterface $image)
677 | {
678 | $image->effects()->grayscale();
679 | return $image;
680 | }
681 |
682 | /**
683 | * Apply negative filter
684 | *
685 | * @param ImageInterface $image An image instance
686 | * @return void
687 | */
688 | protected function filterNegative(ImageInterface $image)
689 | {
690 | $image->effects()->negative();
691 | return $image;
692 | }
693 |
694 | /**
695 | * Apply gamma filter
696 | *
697 | * @param ImageInterface $image An image instance
698 | * @param float $gamma The gamma value
699 | * @return void
700 | */
701 | protected function filterGamma(ImageInterface $image, $gamma)
702 | {
703 | $image->effects()->gamma($gamma);
704 | return $image;
705 | }
706 |
707 | /**
708 | * Apply blur filter
709 | *
710 | * @param ImageInterface $image An image instance
711 | * @param int $blur The amount of blur
712 | * @return void
713 | */
714 | protected function filterBlur(ImageInterface $image, $blur)
715 | {
716 | $image->effects()->blur($blur);
717 | return $image;
718 | }
719 |
720 | /**
721 | * Apply colorize filter
722 | *
723 | * @param ImageInterface $image An image instance
724 | * @param string $color The hex value of the color
725 | * @return void
726 | */
727 | protected function filterColorize(ImageInterface $image, $color)
728 | {
729 | $palettes = ['RGB','CMYK'];
730 | $parts = explode(',', $color);
731 | $color = $parts[0];
732 | if(isset($parts[1]) && in_array(strtoupper($parts[1]), $palettes))
733 | {
734 | $className = '\\Imagine\\Image\\Palette\\'.strtoupper($parts[1]);
735 | $palette = new $className();
736 | }
737 | else
738 | {
739 | $palette = $image->palette();
740 | }
741 | $color = $palette->color($color);
742 | $image->effects()->colorize($color);
743 | return $image;
744 | }
745 |
746 | /**
747 | * Apply interlace filter
748 | *
749 | * @param ImageInterface $image An image instance
750 | * @return void
751 | */
752 | protected function filterInterlace(ImageInterface $image)
753 | {
754 | $image->interlace(ImageInterface::INTERLACE_LINE);
755 | return $image;
756 | }
757 |
758 | /**
759 | * Get mime type from image format
760 | *
761 | * @return string
762 | */
763 | public function getMimeFromFormat($format)
764 | {
765 |
766 | switch ($format) {
767 | case 'gif':
768 | return 'image/gif';
769 | break;
770 | case 'jpg':
771 | case 'jpeg':
772 | return 'image/jpeg';
773 | break;
774 | case 'png':
775 | return 'image/png';
776 | break;
777 | }
778 |
779 | return null;
780 | }
781 |
782 | /**
783 | * Return crop positions from the crop parameter
784 | *
785 | * @return array
786 | */
787 | protected function getCropPositions($crop)
788 | {
789 | $crop = $crop === true ? 'center':$crop;
790 |
791 | $cropPositions = explode('_', $crop);
792 | if (sizeof($cropPositions) === 1) {
793 | if ($cropPositions[0] === 'top' || $cropPositions[0] === 'bottom' || $cropPositions[0] === 'center') {
794 | $cropPositions[] = 'center';
795 | } elseif ($cropPositions[0] === 'left' || $cropPositions[0] === 'right') {
796 | array_unshift($cropPositions, 'center');
797 | }
798 | }
799 |
800 | return $cropPositions;
801 | }
802 |
803 | /**
804 | * Create an instance of the Imagine Gd driver.
805 | *
806 | * @return \Imagine\Gd\Imagine
807 | */
808 | protected function createGdDriver()
809 | {
810 | return new \Imagine\Gd\Imagine();
811 | }
812 |
813 | /**
814 | * Create an instance of the Imagine Imagick driver.
815 | *
816 | * @return \Imagine\Imagick\Imagine
817 | */
818 | protected function createImagickDriver()
819 | {
820 | return new \Imagine\Imagick\Imagine();
821 | }
822 |
823 | /**
824 | * Create an instance of the Imagine Gmagick driver.
825 | *
826 | * @return \Imagine\Gmagick\Imagine
827 | */
828 | protected function createGmagickDriver()
829 | {
830 | return new \Imagine\Gmagick\Imagine();
831 | }
832 |
833 | /**
834 | * Get the default image driver name.
835 | *
836 | * @return string
837 | */
838 | public function getDefaultDriver()
839 | {
840 | return $this->app['config']['image.driver'];
841 | }
842 |
843 | /**
844 | * Set the default image driver name.
845 | *
846 | * @param string $name
847 | * @return void
848 | */
849 | public function setDefaultDriver($name)
850 | {
851 | $this->app['config']['image.driver'] = $name;
852 | }
853 | }
854 |
--------------------------------------------------------------------------------
/src/Folklore/Image/ImageProxy.php:
--------------------------------------------------------------------------------
1 | image = $image;
18 |
19 | $this->config = array_merge([
20 | 'tmp_path' => sys_get_temp_dir(),
21 | 'cache' => false,
22 | 'cache_expiration' => 60*24,
23 | 'write_image' => false,
24 | 'filesystem' => null,
25 | 'cache_filesystem' => null
26 | ], $config);
27 | }
28 |
29 | public function response($path)
30 | {
31 | // Increase memory limit, cause some images require a lot to resize
32 | if (config('image.memory_limit')) {
33 | ini_set('memory_limit', config('image.memory_limit'));
34 | }
35 |
36 | $app = app();
37 |
38 | $disk = $this->getDisk();
39 |
40 | //Check if file exists
41 | $fullPath = $path;
42 | $cache = $this->config['cache'];
43 | $existsCache = $cache ? $this->existsOnProxyCache($fullPath):false;
44 | $existsDisk = !$existsCache && $disk ? $this->existsOnProxyDisk($fullPath):false;
45 | if ($existsCache) {
46 | return $this->getResponseFromCache($fullPath);
47 | } elseif ($existsDisk) {
48 | $response = $this->getResponseFromDisk($fullPath);
49 |
50 | if ($cache) {
51 | $this->saveToProxyCache($path, $response->getContent());
52 | }
53 |
54 | return $response;
55 | }
56 |
57 | $parse = $this->image->parse($fullPath);
58 | $originalPath = $parse['path'];
59 | $tmpPath = $this->config['tmp_path'];
60 | $extension = pathinfo($originalPath, PATHINFO_EXTENSION);
61 | $tmpOriginalPath = tempnam($tmpPath, 'original').'.'.$extension;
62 | $tmpTransformedPath = tempnam($tmpPath, 'transformed').'.'.$extension;
63 | if ($disk && !$disk->exists($originalPath)) {
64 | throw new FileMissingException();
65 | }
66 |
67 | //Download original file
68 | if (!$disk) {
69 | $downloadFile = $this->downloadFile($originalPath);
70 | $contents = file_get_contents($downloadFile);
71 | } else {
72 | $contents = !$disk ? $this->getRemoteFile($originalPath):$disk->get($originalPath);
73 | }
74 | file_put_contents($tmpOriginalPath, $contents);
75 | $contents = null;
76 |
77 | //Get mime
78 | $format = $this->image->format($tmpOriginalPath);
79 | $mime = $this->image->getMimeFromFormat($format);
80 |
81 | $this->image->make($tmpOriginalPath, $parse['options'])
82 | ->save($tmpTransformedPath);
83 |
84 | // Trigger event
85 | event(new ImageSaved($tmpTransformedPath));
86 |
87 | //Write image
88 | if ($this->config['write_image'] && $disk) {
89 | $resource = fopen($tmpTransformedPath, 'r');
90 | $disk
91 | ->getDriver()
92 | ->put($fullPath, $resource, [
93 | 'visibility' => 'public',
94 | 'ContentType' => $mime,
95 | 'CacheControl' => 'max-age='.(3600 * 24)
96 | ]);
97 | fclose($resource);
98 | }
99 |
100 | //Get response
101 | if (!$disk) {
102 | $response = $this->getResponseFromPath($tmpTransformedPath);
103 | } else {
104 | $response = $this->getResponseFromDisk($fullPath);
105 | }
106 |
107 | $response->header('Content-Type', $mime);
108 |
109 | //Save to cache
110 | $cache = $this->config['cache'];
111 | if ($cache) {
112 | $this->saveToProxyCache($path, $response->getContent());
113 | }
114 |
115 | unlink($tmpOriginalPath);
116 | unlink($tmpTransformedPath);
117 |
118 | return $response;
119 | }
120 |
121 | protected function downloadFile($path)
122 | {
123 | $deleteOriginalFile = true;
124 | $tmpPath = tempnam($this->config['tmp_path'], 'image');
125 | $client = new GuzzleClient();
126 | $response = $client->request('GET', $path, [
127 | 'sink' => $tmpPath
128 | ]);
129 | $path = $tmpPath;
130 |
131 | return $tmpPath;
132 | }
133 |
134 | protected function getDisk()
135 | {
136 | $filesystem = $this->config['filesystem'];
137 | if (!$filesystem) {
138 | return null;
139 | }
140 |
141 | return $filesystem === 'cloud' ? app('filesystem')->cloud():app('filesystem')->disk($filesystem);
142 | }
143 |
144 | protected function getCacheDisk()
145 | {
146 | $filesystem = $this->config['cache_filesystem'];
147 | if (!$filesystem) {
148 | return null;
149 | }
150 |
151 | return $filesystem === 'cloud' ? app('filesystem')->cloud():app('filesystem')->disk($filesystem);
152 | }
153 |
154 | protected function getCacheKey($path)
155 | {
156 | $key = md5($path).'_'.sha1($path);
157 |
158 | return 'image/'.preg_replace('/^([0-9a-z]{2})([0-9a-z]{2})/i', '$1/$2/', $key);
159 | }
160 |
161 | protected function getEscapedCacheKey($path)
162 | {
163 | $cacheKey = $this->getCacheKey($path);
164 | return preg_replace('/[^a-zA-Z0-9]+/i', '_', $cacheKey);
165 | }
166 |
167 | protected function existsOnProxyCache($path)
168 | {
169 | $disk = $this->getCacheDisk();
170 | if ($disk) {
171 | $cacheKey = $this->getCacheKey($path);
172 | return $disk->exists($cacheKey);
173 | }
174 |
175 | $cacheKey = $this->getEscapedCacheKey($path);
176 | return app('cache')->has($cacheKey);
177 | }
178 |
179 | protected function existsOnProxyDisk($path)
180 | {
181 | $disk = $this->getDisk();
182 | return $disk->exists($path);
183 | }
184 |
185 | protected function getMimeFromContent($content)
186 | {
187 | $finfo = new finfo(FILEINFO_MIME);
188 | return $finfo->buffer($content);
189 | }
190 |
191 | protected function getResponseFromCache($path)
192 | {
193 | $disk = $this->getCacheDisk();
194 | if ($disk) {
195 | $cacheKey = $this->getCacheKey($path);
196 | $contents = $disk->get($cacheKey);
197 | } else {
198 | $cacheKey = $this->getEscapedCacheKey($path);
199 | $contents = app('cache')->get($cacheKey);
200 | }
201 |
202 | $response = response()->make($contents, 200);
203 | $response->header('Cache-control', 'max-age='.(3600*24).', public');
204 | $response->header('Content-type', $this->getMimeFromContent($contents));
205 | $contents = null;
206 |
207 | return $response;
208 | }
209 |
210 | protected function getResponseFromPath($path)
211 | {
212 | $content = file_get_contents($path);
213 | $response = $this->createResponseFromContent($content);
214 | $response->header('Content-type', $this->getMimeFromContent($content));
215 | $content = null;
216 |
217 | return $response;
218 | }
219 |
220 | protected function getResponseFromDisk($path)
221 | {
222 | $disk = $this->getDisk();
223 | $content = $disk->get($path);
224 | $response = $this->createResponseFromContent($content);
225 | $response->header('Content-type', $this->getMimeFromContent($content));
226 | $content = null;
227 |
228 | return $response;
229 | }
230 |
231 | protected function getResponseExpires()
232 | {
233 | $proxyExpires = config('image.proxy_expires', null);
234 | return $proxyExpires ? $proxyExpires:config('image.serve_expires', 3600*24*31);
235 | }
236 |
237 | protected function saveToProxyCache($path, $contents)
238 | {
239 | $disk = $this->getCacheDisk();
240 | if ($disk) {
241 | $cacheKey = $this->getCacheKey($path);
242 | $disk->put($cacheKey, $contents);
243 | } else {
244 | $cacheKey = $this->getEscapedCacheKey($path);
245 | $cacheExpiration = $this->config['cache_expiration'];
246 | if ($cacheExpiration === -1) {
247 | app('cache')->forever($cacheKey, $contents);
248 | } else {
249 | app('cache')->put($cacheKey, $contents, $cacheExpiration);
250 | }
251 | }
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/src/Folklore/Image/ImageServe.php:
--------------------------------------------------------------------------------
1 | image = $image;
16 |
17 | $this->config = array_merge([
18 | 'custom_filters_only' => false,
19 | 'write_image' => false,
20 | 'write_path' => null,
21 | 'quality' => 80,
22 | 'options' => []
23 | ], $config);
24 | }
25 |
26 | public function response($path)
27 | {
28 | // Parse the current path
29 | $parsedPath = $this->image->parse($path, array(
30 | 'custom_filters_only' => $this->config['custom_filters_only']
31 | ));
32 | $writePath = isset($this->config['write_path']) ? trim($this->config['write_path'], '/') : null;
33 | $parsedOptions = $parsedPath['options'];
34 | $imagePath = $parsedPath['path'];
35 |
36 | if ($writePath && strpos($imagePath, $writePath) === 0) {
37 | $imagePath = substr($imagePath, strlen($writePath)+1);
38 | }
39 |
40 | // See if the referenced file exists and is an image
41 | if (!($realPath = $this->image->getRealPath($imagePath))) {
42 | throw new FileMissingException('Image file missing');
43 | }
44 |
45 | // create the destination if it does not exist
46 | if ($this->config['write_image']) {
47 | // make sure the path is relative to the document root
48 | if (strpos($realPath, public_path()) === 0) {
49 | $imagePath = substr($realPath, strlen(public_path()));
50 | }
51 | $destinationFolder = public_path(trim($writePath, '/') . '/' . ltrim(dirname($imagePath), '/'));
52 |
53 | if (isset($writePath)) {
54 | \File::makeDirectory($destinationFolder, 0770, true, true);
55 | }
56 |
57 | // Make sure destination is writeable
58 | if (!is_writable($destinationFolder)) {
59 | throw new Exception('Destination is not writeable');
60 | }
61 | }
62 |
63 |
64 | // Merge all options with the following priority:
65 | // Options passed as an argument to the serve method
66 | // Options parsed from the URL
67 | // Default options
68 | $options = array_merge($parsedOptions, $this->config['options']);
69 |
70 | //Make the image
71 | $image = $this->image->make($imagePath, $options);
72 |
73 | //Get the image format
74 | $format = $this->image->format($realPath);
75 |
76 | //Get the image content
77 | $saveOptions = array();
78 | $quality = array_get($options, 'quality', $this->config['quality']);
79 | if ($format === 'jpeg') {
80 | $saveOptions['jpeg_quality'] = $quality;
81 | } elseif ($format === 'png') {
82 | $saveOptions['png_compression_level'] = round($quality / 100 * 9);
83 | }
84 |
85 | //Write the image
86 | if ($this->config['write_image']) {
87 | $destinationPath = rtrim($destinationFolder, '/') . '/' . basename($path);
88 | $image->save($destinationPath, $saveOptions);
89 |
90 | // Trigger event
91 | event(new ImageSaved($destinationPath));
92 | }
93 |
94 | $content = $image->get($format, $saveOptions);
95 |
96 | //Create the response
97 | $mime = $this->image->getMimeFromFormat($format);
98 | $response = $this->createResponseFromContent($content);
99 | $response->header('Content-type', $mime);
100 |
101 | return $response;
102 | }
103 |
104 | protected function getResponseExpires()
105 | {
106 | return config('image.serve_expires', 3600*24*31);
107 | }
108 |
109 | protected function createResponseFromContent($content)
110 | {
111 | $expires = $this->getResponseExpires();
112 | $response = response()->make($content, 200);
113 | $response->header('Cache-control', 'max-age='.$expires.', public');
114 | $response->header('Expires', gmdate('D, d M Y H:i:s \G\M\T', time() + $expires));
115 | return $response;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/Folklore/Image/ImageServiceProvider.php:
--------------------------------------------------------------------------------
1 | mergeConfigFrom($configFile, 'image');
28 |
29 | // Publish
30 | $this->publishes([
31 | $configFile => config_path('image.php')
32 | ], 'config');
33 |
34 | $this->publishes([
35 | $publicFile => public_path('vendor/folklore/image')
36 | ], 'public');
37 |
38 | $app = $this->app;
39 | $router = $app['router'];
40 | $config = $app['config'];
41 |
42 | $pattern = $app['image']->pattern();
43 | $proxyPattern = $config->get('image.proxy_route_pattern');
44 | $router->pattern('image_pattern', $pattern);
45 | $router->pattern('image_proxy_pattern', $proxyPattern ? $proxyPattern:$pattern);
46 |
47 | //Serve image
48 | $serve = config('image.serve');
49 | if ($serve) {
50 | // Create a route that match pattern
51 | $serveRoute = $config->get('image.serve_route', '{image_pattern}');
52 | $router->get($serveRoute, array(
53 | 'as' => 'image.serve',
54 | 'domain' => $config->get('image.domain', null),
55 | 'uses' => 'Folklore\Image\ImageController@serve'
56 | ));
57 | }
58 |
59 | //Proxy
60 | $proxy = $this->app['config']['image.proxy'];
61 | if ($proxy) {
62 | $serveRoute = $config->get('image.proxy_route', '{image_proxy_pattern}');
63 | $router->get($serveRoute, array(
64 | 'as' => 'image.proxy',
65 | 'domain' => $config->get('image.proxy_domain'),
66 | 'uses' => 'Folklore\Image\ImageController@proxy'
67 | ));
68 | }
69 | }
70 |
71 | /**
72 | * Register the service provider.
73 | *
74 | * @return void
75 | */
76 | public function register()
77 | {
78 | $this->app->singleton('image', function ($app) {
79 | return new ImageManager($app);
80 | });
81 | }
82 |
83 | /**
84 | * Get the services provided by the provider.
85 | *
86 | * @return array
87 | */
88 | public function provides()
89 | {
90 | return array('image');
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/resources/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/folkloreinc/laravel-image-legacy/146ba2710575c1f21f3b0eb1193af4ca19ac5f36/src/resources/assets/.gitkeep
--------------------------------------------------------------------------------
/src/resources/assets/js/image.js:
--------------------------------------------------------------------------------
1 | (function (root, factory) {
2 | if (typeof define === 'function' && define.amd) {
3 | // AMD. Register as an anonymous module.
4 | define([], factory);
5 | } else if (typeof exports === 'object') {
6 | // Node. Does not work with strict CommonJS, but
7 | // only CommonJS-like environments that support module.exports,
8 | // like Node.
9 | module.exports = factory();
10 | } else {
11 | // Browser globals (root is window)
12 | if(typeof(root.Folklore) === 'undefined') {
13 | root.Folklore = {};
14 | }
15 | root.Folklore.Image = factory();
16 | }
17 | }(this, function () {
18 |
19 | 'use strict';
20 |
21 | var URL_PARAMETER = '-image({options})';
22 |
23 | // Build a image formatted URL
24 | function url(src, width, height, options) {
25 |
26 | // Don't allow empty strings
27 | if (!src || !src.length) return;
28 |
29 | //If width parameter is an array, use it as options
30 | if(width instanceof Object)
31 | {
32 | options = width;
33 | width = null;
34 | height = null;
35 | }
36 |
37 | //Get size
38 | if (!width) width = '_';
39 | if (!height) height = '_';
40 |
41 | // Produce the image option
42 | var params = [];
43 |
44 | //Add size if presents
45 | if(width != '_' || height != '_') {
46 | params.push(width+'x'+height);
47 | }
48 |
49 | // Add options.
50 | if (options && options instanceof Object) {
51 | var val, key;
52 | for (key in options) {
53 | val = options[key];
54 | if (val === true || val === null) {
55 | params.push(key);
56 | }
57 | else if (val instanceof Array) {
58 | params.push(key+'('+val.join(',')+')');
59 | }
60 | else {
61 | params.push(key+'('+val+')');
62 | }
63 | }
64 | }
65 |
66 | params = params.join('-');
67 | var parameter = URL_PARAMETER.replace('{options}',params);
68 |
69 | // Break the path apart and put back together again
70 | return src.replace(/^(.+)(\.[a-z]+)$/i, "$1"+parameter+"$2");
71 |
72 | }
73 |
74 | // Expose public methods.
75 | return {
76 | url: url
77 | };
78 | }));
79 |
--------------------------------------------------------------------------------
/src/resources/config/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/folkloreinc/laravel-image-legacy/146ba2710575c1f21f3b0eb1193af4ca19ac5f36/src/resources/config/.gitkeep
--------------------------------------------------------------------------------
/src/resources/config/image.php:
--------------------------------------------------------------------------------
1 | 'gd',
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Memory limit
23 | |--------------------------------------------------------------------------
24 | |
25 | | When manipulating an image, the memory limit is increased to this value
26 | |
27 | */
28 | 'memory_limit' => '128M',
29 |
30 | /*
31 | |--------------------------------------------------------------------------
32 | | Source directories
33 | |--------------------------------------------------------------------------
34 | |
35 | | A list a directories to look for images
36 | |
37 | */
38 | 'src_dirs' => array(
39 | public_path()
40 | ),
41 |
42 | /*
43 | |--------------------------------------------------------------------------
44 | | Host
45 | |--------------------------------------------------------------------------
46 | |
47 | | The http host where the image are served. Used by the Image::url() method
48 | | to generate the right URL.
49 | |
50 | */
51 | 'host' => '',
52 |
53 | /*
54 | |--------------------------------------------------------------------------
55 | | Pattern
56 | |--------------------------------------------------------------------------
57 | |
58 | | The pattern that is used to match routes that will be handled by the
59 | | ImageController. The {parameters} will be remplaced by the url parameters
60 | | pattern.
61 | |
62 | */
63 | 'pattern' => '^(.*){parameters}\.(jpg|jpeg|png|gif|JPG|JPEG|PNG|GIF)$',
64 |
65 | /*
66 | |--------------------------------------------------------------------------
67 | | URL parameter
68 | |--------------------------------------------------------------------------
69 | |
70 | | The URL parameter that will be appended to your image filename containing
71 | | all the options for image manipulation. You have to put {options} where
72 | | you want options to be placed. Keep in mind that this parameter is used
73 | | in an url so all characters should be URL safe.
74 | |
75 | | Default: -image({options})
76 | |
77 | | Example: /uploads/photo-image(300x300-grayscale).jpg
78 | |
79 | */
80 | 'url_parameter' => '-image({options})',
81 |
82 | /*
83 | |--------------------------------------------------------------------------
84 | | URL parameter separator
85 | |--------------------------------------------------------------------------
86 | |
87 | | The URL parameter separator is used to build the parameters string
88 | | that will replace {options} in url_parameter
89 | |
90 | | Default: -
91 | |
92 | | Example: /uploads/photo-image(300x300-grayscale).jpg
93 | |
94 | */
95 | 'url_parameter_separator' => '-',
96 |
97 | /*
98 | |--------------------------------------------------------------------------
99 | | Serve image
100 | |--------------------------------------------------------------------------
101 | |
102 | | If true, a route will be added to catch image containing the
103 | | URL parameter above.
104 | |
105 | */
106 | 'serve' => true,
107 |
108 | /*
109 | |--------------------------------------------------------------------------
110 | | Serve route
111 | |--------------------------------------------------------------------------
112 | |
113 | | If you want to restrict the route to a specific domain.
114 | |
115 | */
116 | 'serve_domain' => null,
117 |
118 | /*
119 | |--------------------------------------------------------------------------
120 | | Serve route
121 | |--------------------------------------------------------------------------
122 | |
123 | | The route where image are served
124 | |
125 | */
126 | 'serve_route' => '{image_pattern}',
127 |
128 | /*
129 | |--------------------------------------------------------------------------
130 | | Serve custom Filters only
131 | |--------------------------------------------------------------------------
132 | |
133 | | Restrict options in url to custom filters only. This prevent direct
134 | | manipulation of the image.
135 | |
136 | */
137 | 'serve_custom_filters_only' => false,
138 |
139 | /*
140 | |--------------------------------------------------------------------------
141 | | Serve expires
142 | |--------------------------------------------------------------------------
143 | |
144 | | The expires headers that are sent when sending image.
145 | |
146 | */
147 | 'serve_expires' => (3600*24*31),
148 |
149 | /*
150 | |--------------------------------------------------------------------------
151 | | Write image
152 | |--------------------------------------------------------------------------
153 | |
154 | | When serving an image, write the manipulated image in the same directory
155 | | as the original image so the next request will serve this static file
156 | |
157 | */
158 | 'write_image' => false,
159 |
160 | /*
161 | |--------------------------------------------------------------------------
162 | | Write path
163 | |--------------------------------------------------------------------------
164 | |
165 | | By default, the manipulated images are saved in the same path as the
166 | | as the original image, you can override this path here
167 | |
168 | */
169 | 'write_path' => null,
170 |
171 | /*
172 | |--------------------------------------------------------------------------
173 | | Proxy
174 | |--------------------------------------------------------------------------
175 | |
176 | | This enable or disable the proxy route
177 | |
178 | */
179 | 'proxy' => false,
180 |
181 | /*
182 | |--------------------------------------------------------------------------
183 | | Proxy expires
184 | |--------------------------------------------------------------------------
185 | |
186 | | The expires headers that are sent when proxying image. Defaults to
187 | | serve_expires
188 | |
189 | */
190 | 'proxy_expires' => null,
191 |
192 | /*
193 | |--------------------------------------------------------------------------
194 | | Proxy route
195 | |--------------------------------------------------------------------------
196 | |
197 | | The route that will be used to serve proxied image
198 | |
199 | */
200 | 'proxy_route' => '{image_proxy_pattern}',
201 |
202 |
203 |
204 | /*
205 | |--------------------------------------------------------------------------
206 | | Proxy route pattern
207 | |--------------------------------------------------------------------------
208 | |
209 | | The proxy route pattern that will be available as `image_proxy_pattern`.
210 | | If the value is null, the default image pattern will be used.
211 | |
212 | */
213 | 'proxy_route_pattern' => null,
214 |
215 | /*
216 | |--------------------------------------------------------------------------
217 | | Proxy route domain
218 | |--------------------------------------------------------------------------
219 | |
220 | | If you wind to bind your route to a specific domain.
221 | |
222 | */
223 | 'proxy_route_domain' => null,
224 |
225 | /*
226 | |--------------------------------------------------------------------------
227 | | Proxy filesystem
228 | |--------------------------------------------------------------------------
229 | |
230 | | The filesystem from which the file will be proxied
231 | |
232 | */
233 | 'proxy_filesystem' => 'cloud',
234 |
235 | /*
236 | |--------------------------------------------------------------------------
237 | | Proxy temporary directory
238 | |--------------------------------------------------------------------------
239 | |
240 | | Write the manipulated image back to the file system
241 | |
242 | */
243 | 'proxy_write_image' => true,
244 |
245 | /*
246 | |--------------------------------------------------------------------------
247 | | Proxy cache
248 | |--------------------------------------------------------------------------
249 | |
250 | | Cache the response of the proxy on the local filesystem. The proxy will be
251 | | cached using the laravel cache driver.
252 | |
253 | */
254 | 'proxy_cache' => true,
255 |
256 | /*
257 | |--------------------------------------------------------------------------
258 | | Proxy cache filesystem
259 | |--------------------------------------------------------------------------
260 | |
261 | | If you want the proxy to cache files on a filesystem instead of using the
262 | | cache driver.
263 | |
264 | */
265 | 'proxy_cache_filesystem' => null,
266 |
267 | /*
268 | |--------------------------------------------------------------------------
269 | | Proxy cache expiration
270 | |--------------------------------------------------------------------------
271 | |
272 | | The number of minuts that a proxied image can stay in cache. If the value
273 | | is -1, the image is cached forever.
274 | |
275 | */
276 | 'proxy_cache_expiration' => 60*24,
277 |
278 | /*
279 | |--------------------------------------------------------------------------
280 | | Proxy temporary path
281 | |--------------------------------------------------------------------------
282 | |
283 | | The temporary path where the manipulated file are saved.
284 | |
285 | */
286 | 'proxy_tmp_path' => sys_get_temp_dir(),
287 |
288 | );
289 |
--------------------------------------------------------------------------------
/tests/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/folkloreinc/laravel-image-legacy/146ba2710575c1f21f3b0eb1193af4ca19ac5f36/tests/.gitkeep
--------------------------------------------------------------------------------
/tests/ImageProxyTestCase.php:
--------------------------------------------------------------------------------
1 | image = $this->app['image'];
19 | $this->imageSize = getimagesize(public_path().$this->imagePath);
20 | $this->imageSmallSize = getimagesize(public_path().$this->imageSmallPath);
21 | }
22 |
23 | public function tearDown()
24 | {
25 | $customPath = $this->app['path.public'].'/custom';
26 | $this->app['config']->set('image.write_path', $customPath);
27 |
28 | $this->image->deleteManipulated($this->imagePath);
29 |
30 | parent::tearDown();
31 | }
32 |
33 | public function testProxy()
34 | {
35 | $url = $this->image->url($this->imagePath, 300, 300, [
36 | 'crop' => true
37 | ]);
38 | $response = $this->call('GET', $url);
39 | $this->assertTrue($response->isOk());
40 |
41 | $image = imagecreatefromstring($response->getContent());
42 | $this->assertTrue($image !== false);
43 |
44 | $this->assertEquals(imagesx($image), 300);
45 | $this->assertEquals(imagesy($image), 300);
46 |
47 | imagedestroy($image);
48 | }
49 |
50 | public function testProxyURL()
51 | {
52 | $this->app['config']->set('image.host', '/proxy/http://placehold.it/');
53 | $this->app['config']->set('image.proxy_filesystem', null);
54 | $this->app['config']->set('image.proxy_route_pattern', '^(.*)$');
55 |
56 | $url = $this->image->url('/640x480.png', 300, 300, [
57 | 'crop' => true
58 | ]);
59 | $response = $this->call('GET', $url);
60 | $this->assertTrue($response->isOk());
61 |
62 | $image = imagecreatefromstring($response->getContent());
63 | $this->assertTrue($image !== false);
64 |
65 | $this->assertEquals(imagesx($image), 300);
66 | $this->assertEquals(imagesy($image), 300);
67 |
68 | imagedestroy($image);
69 | }
70 |
71 | /**
72 | * Define environment setup.
73 | *
74 | * @param \Illuminate\Foundation\Application $app
75 | * @return void
76 | */
77 | protected function getEnvironmentSetUp($app)
78 | {
79 | $app->instance('path.public', __DIR__.'/fixture');
80 |
81 | $app['config']->set('image.host', '/proxy');
82 | $app['config']->set('image.serve', false);
83 | $app['config']->set('image.proxy', true);
84 | $app['config']->set('image.proxy_route', '/proxy/{image_proxy_pattern}');
85 | $app['config']->set('image.proxy_filesystem', 'image_testbench');
86 | $app['config']->set('image.proxy_cache_filesystem', null);
87 |
88 | $app['config']->set('filesystems.default', 'image_testbench');
89 | $app['config']->set('filesystems.cloud', 'image_testbench');
90 |
91 | $app['config']->set('filesystems.disks.image_testbench', [
92 | 'driver' => 'local',
93 | 'root' => __DIR__.'/fixture'
94 | ]);
95 |
96 | $app['config']->set('filesystems.disks.image_testbench_cache', [
97 | 'driver' => 'local',
98 | 'root' => __DIR__.'/fixture/cache'
99 | ]);
100 | }
101 |
102 | protected function getPackageProviders($app)
103 | {
104 | return array('Folklore\Image\ImageServiceProvider');
105 | }
106 |
107 | protected function getPackageAliases($app)
108 | {
109 | return array(
110 | 'Image' => 'Folklore\Image\Facades\Image'
111 | );
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/tests/ImageServeTestCase.php:
--------------------------------------------------------------------------------
1 | image = $this->app['image'];
19 | $this->imageSize = getimagesize(public_path().$this->imagePath);
20 | $this->imageSmallSize = getimagesize(public_path().$this->imageSmallPath);
21 | }
22 |
23 | public function tearDown()
24 | {
25 | $customPath = $this->app['path.public'].'/custom';
26 | $this->app['config']->set('image.write_path', $customPath);
27 |
28 | $this->image->deleteManipulated($this->imagePath);
29 |
30 | parent::tearDown();
31 | }
32 |
33 | public function testServeWriteImage()
34 | {
35 | $this->app['config']->set('image.write_image', true);
36 |
37 | $url = $this->image->url($this->imagePath, 300, 300, [
38 | 'crop' => true
39 | ]);
40 |
41 | $response = $this->call('GET', $url);
42 |
43 | $this->assertTrue($response->isOk());
44 |
45 | $imagePath = $this->app['path.public'].'/'.basename($url);
46 | $this->assertFileExists($imagePath);
47 |
48 | $sizeManipulated = getimagesize($imagePath);
49 | $this->assertEquals($sizeManipulated[0], 300);
50 | $this->assertEquals($sizeManipulated[1], 300);
51 |
52 | $this->app['config']->set('image.write_image', false);
53 | }
54 |
55 | public function testServeWriteImagePath()
56 | {
57 | $customPath = 'custom';
58 |
59 | $this->app['config']->set('image.write_image', true);
60 | $this->app['config']->set('image.write_path', $customPath);
61 |
62 | $url = $this->image->url($this->imagePath, 300, 300, [
63 | 'crop' => true
64 | ]);
65 |
66 | $response = $this->call('GET', $url);
67 |
68 | $this->assertTrue($response->isOk());
69 |
70 | $imagePath = public_path($customPath.'/'.basename($url));
71 | $this->assertFileExists($imagePath);
72 |
73 | $sizeManipulated = getimagesize($imagePath);
74 | $this->assertEquals($sizeManipulated[0], 300);
75 | $this->assertEquals($sizeManipulated[1], 300);
76 |
77 | $this->app['config']->set('image.write_image', false);
78 | $this->app['config']->set('image.write_path', null);
79 | }
80 |
81 | public function testServeNoResize()
82 | {
83 |
84 | $url = $this->image->url($this->imagePath, null, null, array(
85 | 'grayscale'
86 | ));
87 | $response = $this->call('GET', $url);
88 |
89 | $this->assertTrue($response->isOk());
90 |
91 | $sizeManipulated = getimagesizefromstring($response->getContent());
92 | $this->assertEquals($sizeManipulated[0], $this->imageSize[0]);
93 | $this->assertEquals($sizeManipulated[1], $this->imageSize[1]);
94 | }
95 |
96 | public function testServeResizeWidth()
97 | {
98 | $url = $this->image->url($this->imagePath, 300);
99 | $response = $this->call('GET', $url);
100 |
101 | $this->assertTrue($response->isOk());
102 |
103 | $sizeManipulated = getimagesizefromstring($response->getContent());
104 | $this->assertEquals($sizeManipulated[0], 300);
105 | }
106 |
107 | public function testServeResizeHeight()
108 | {
109 | $url = $this->image->url($this->imagePath, null, 300);
110 | $response = $this->call('GET', $url);
111 |
112 | $this->assertTrue($response->isOk());
113 |
114 | $sizeManipulated = getimagesizefromstring($response->getContent());
115 | $this->assertEquals($sizeManipulated[1], 300);
116 | }
117 |
118 | public function testServeResizeCrop()
119 | {
120 | //Both height and width with crop
121 | $url = $this->image->url($this->imagePath, 300, 300, array(
122 | 'crop' => true
123 | ));
124 | $response = $this->call('GET', $url);
125 |
126 | $this->assertTrue($response->isOk());
127 |
128 | $sizeManipulated = getimagesizefromstring($response->getContent());
129 | $this->assertEquals($sizeManipulated[0], 300);
130 | $this->assertEquals($sizeManipulated[1], 300);
131 | }
132 |
133 | public function testServeResizeCropSmall()
134 | {
135 | //Both height and width with crop
136 | $url = $this->image->url($this->imageSmallPath, 300, 300, array(
137 | 'crop' => true
138 | ));
139 | $response = $this->call('GET', $url);
140 |
141 | $this->assertTrue($response->isOk());
142 |
143 | $sizeManipulated = getimagesizefromstring($response->getContent());
144 | $this->assertEquals($sizeManipulated[0], 300);
145 | $this->assertEquals($sizeManipulated[1], 300);
146 | }
147 |
148 | public function testServeWrongParameter()
149 | {
150 | $url = $this->image->url($this->imagePath, 300, 300, array(
151 | 'crop' => true,
152 | 'wrong' => true
153 | ));
154 |
155 | try {
156 | $response = $this->call('GET', $url);
157 | $this->assertSame(404, $response->getStatusCode());
158 | }
159 | catch(\Symfony\Component\HttpKernel\Exception\NotFoundHttpException $e)
160 | {
161 | $this->assertInstanceOf('\Symfony\Component\HttpKernel\Exception\NotFoundHttpException', $e);
162 | }
163 | }
164 |
165 | public function testServeWrongFile()
166 | {
167 | $url = $this->image->url('/wrong123.jpg', 300, 300, array(
168 | 'crop' => true,
169 | 'wrong' => true
170 | ));
171 |
172 | try {
173 | $response = $this->call('GET', $url);
174 | $this->assertSame(404, $response->getStatusCode());
175 | }
176 | catch(\Symfony\Component\HttpKernel\Exception\NotFoundHttpException $e)
177 | {
178 | $this->assertInstanceOf('\Symfony\Component\HttpKernel\Exception\NotFoundHttpException', $e);
179 | }
180 | }
181 |
182 | public function testServeWrongFormat()
183 | {
184 | $url = $this->image->url('/wrong.jpg', 300, 300, array(
185 | 'crop' => true
186 | ));
187 |
188 | try {
189 | $response = $this->call('GET', $url);
190 | $this->assertSame(500, $response->getStatusCode());
191 | }
192 | catch(\Symfony\Component\HttpKernel\Exception\HttpException $e)
193 | {
194 | $this->assertInstanceOf('\Symfony\Component\HttpKernel\Exception\HttpException', $e);
195 | }
196 | }
197 |
198 | /**
199 | * Define environment setup.
200 | *
201 | * @param \Illuminate\Foundation\Application $app
202 | * @return void
203 | */
204 | protected function getEnvironmentSetUp($app)
205 | {
206 | $app->instance('path.public', __DIR__.'/fixture');
207 | }
208 |
209 | protected function getPackageProviders($app)
210 | {
211 | return array('Folklore\Image\ImageServiceProvider');
212 | }
213 |
214 | protected function getPackageAliases($app)
215 | {
216 | return array(
217 | 'Image' => 'Folklore\Image\Facades\Image'
218 | );
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/tests/ImageTestCase.php:
--------------------------------------------------------------------------------
1 | image = $this->app['image'];
19 | $this->imageSize = getimagesize(public_path().$this->imagePath);
20 | $this->imageSmallSize = getimagesize(public_path().$this->imageSmallPath);
21 | }
22 |
23 | public function tearDown()
24 | {
25 | $customPath = $this->app['path.public'].'/custom';
26 | $this->app['config']->set('image.write_path', $customPath);
27 |
28 | $this->image->deleteManipulated($this->imagePath);
29 |
30 | parent::tearDown();
31 | }
32 |
33 | public function testURLisValid()
34 | {
35 |
36 | $patterns = array(
37 | array(
38 | 'url_parameter' => null
39 | ),
40 | array(
41 | 'url_parameter' => '-image({options})',
42 | 'url_parameter_separator' => '-'
43 | ),
44 | array(
45 | 'url_parameter' => '-i-{options}',
46 | 'url_parameter_separator' => '-'
47 | ),
48 | array(
49 | 'url_parameter' => '/i/{options}',
50 | 'url_parameter_separator' => '/'
51 | )
52 | );
53 |
54 | foreach ($patterns as $pattern) {
55 | $options = array(
56 | 'grayscale',
57 | 'crop' => true,
58 | 'colorize' => 'FFCC00'
59 | );
60 | $url = $this->image->url($this->imagePath, 300, 300, array_merge($pattern, $options));
61 |
62 | //Check against pattern
63 | $urlMatch = preg_match('#'.$this->image->pattern($pattern['url_parameter']).'#', $url, $matches);
64 | $this->assertEquals($urlMatch, 1);
65 |
66 | //Check path
67 | $parsedPath = $this->image->parse($url, $pattern);
68 | $this->assertEquals($parsedPath['path'], $this->imagePath);
69 |
70 | //Check options
71 | foreach ($options as $key => $value) {
72 | if (is_numeric($key)) {
73 | $this->assertTrue($parsedPath['options'][$value]);
74 | } else {
75 | $this->assertEquals($parsedPath['options'][$key], $value);
76 | }
77 | }
78 |
79 | }
80 | }
81 |
82 | /**
83 | * Define environment setup.
84 | *
85 | * @param \Illuminate\Foundation\Application $app
86 | * @return void
87 | */
88 | protected function getEnvironmentSetUp($app)
89 | {
90 | $app->instance('path.public', __DIR__.'/fixture');
91 | }
92 |
93 | protected function getPackageProviders($app)
94 | {
95 | return array('Folklore\Image\ImageServiceProvider');
96 | }
97 |
98 | protected function getPackageAliases($app)
99 | {
100 | return array(
101 | 'Image' => 'Folklore\Image\Facades\Image'
102 | );
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/tests/fixture/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/fixture/custom/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/fixture/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/folkloreinc/laravel-image-legacy/146ba2710575c1f21f3b0eb1193af4ca19ac5f36/tests/fixture/image.jpg
--------------------------------------------------------------------------------
/tests/fixture/image_small.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/folkloreinc/laravel-image-legacy/146ba2710575c1f21f3b0eb1193af4ca19ac5f36/tests/fixture/image_small.jpg
--------------------------------------------------------------------------------
/tests/fixture/wrong.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/folkloreinc/laravel-image-legacy/146ba2710575c1f21f3b0eb1193af4ca19ac5f36/tests/fixture/wrong.jpg
--------------------------------------------------------------------------------