├── .php_cs ├── ImageComponent.php ├── LICENSE.md ├── README.md ├── actions └── ImageAction.php ├── assets └── no-image.png ├── behaviors └── ImageBehavior.php └── composer.json /.php_cs: -------------------------------------------------------------------------------- 1 | exclude('vendor') 5 | ->in([__DIR__]); 6 | 7 | $config = PhpCsFixer\Config::create() 8 | ->setUsingCache(false) 9 | ->setRules([ 10 | '@Symfony' => true, 11 | 'phpdoc_align' => false, 12 | 'phpdoc_summary' => false, 13 | 'phpdoc_inline_tag' => false, 14 | 'pre_increment' => false, 15 | 'heredoc_to_nowdoc' => false, 16 | 'cast_spaces' => false, 17 | 'include' => false, 18 | 'phpdoc_no_package' => false, 19 | 'concat_space' => ['spacing' => 'one'], 20 | 'ordered_imports' => true, 21 | 'array_syntax' => ['syntax' => 'short'], 22 | 'yoda_style' => false, 23 | ]) 24 | ->setFinder($finder); 25 | 26 | return $config; 27 | -------------------------------------------------------------------------------- /ImageComponent.php: -------------------------------------------------------------------------------- 1 | [], 89 | 'small' => [ 90 | 'thumbnail' => [ 91 | 'box' => [60, 60], 92 | 'mode' => ManipulatorInterface::THUMBNAIL_OUTBOUND, 93 | ], 94 | ], 95 | 'medium' => [ 96 | 'thumbnail' => [ 97 | 'box' => [240, 240], 98 | 'mode' => ManipulatorInterface::THUMBNAIL_OUTBOUND, 99 | ], 100 | ], 101 | ]; 102 | 103 | /** 104 | * @inheritdoc 105 | */ 106 | public function init() 107 | { 108 | $this->config = ArrayHelper::merge($this->config, Yii::$app->params['image'] ?? []); 109 | 110 | parent::init(); 111 | } 112 | 113 | /** 114 | * This method detects which (absolute or relative) path is used. 115 | * 116 | * @param array $file path 117 | * 118 | * @return string path 119 | */ 120 | public function detectPath($file): string 121 | { 122 | if ($file == $this->noImage) { 123 | return Yii::getAlias($this->noImage); 124 | } 125 | 126 | $fullPath = $this->getImageSourcePath() . $file; 127 | 128 | if (is_file($fullPath)) { 129 | return $fullPath; 130 | } 131 | 132 | return false; 133 | } 134 | 135 | /** 136 | * Get image url 137 | * 138 | * @param $file 139 | * @param $type 140 | * 141 | * @return string 142 | */ 143 | public function getUrl($file, $type): string 144 | { 145 | if (!$this->checkPermission($type)) { 146 | $file = $this->noImage; 147 | } 148 | 149 | $filePath = $this->getCachePath($file, $type); 150 | 151 | if ($this->isFreshCache($filePath['system'])) { 152 | return $filePath['public']; 153 | } 154 | 155 | return Url::toRoute([$this->imageAction, 'path' => urlencode($file), 'type' => $type]); 156 | } 157 | 158 | /** 159 | * Get image cache path 160 | * 161 | * @param $file 162 | * @param $type 163 | * 164 | * @return array 165 | */ 166 | protected function getCachePath($file, $type): array 167 | { 168 | $hash = md5($file . $type); 169 | if (isset($this->config[$type])) { 170 | $isTransparent = ArrayHelper::getValue($this->config[$type], 'transparent', false); 171 | } else { 172 | $isTransparent = false; 173 | } 174 | 175 | $cacheFileExt = $isTransparent ? self::TRANSPARENT_EXTENSION : strtolower(pathinfo($file, PATHINFO_EXTENSION)); 176 | $cachePath = $this->cachePath . $hash[0] . DIRECTORY_SEPARATOR; 177 | $cachePublicPath = $this->cachePublicPath . $hash[0] . DIRECTORY_SEPARATOR; 178 | $cacheFile = "{$hash}.{$cacheFileExt}"; 179 | $systemPath = Yii::getAlias('@app') . $cachePath; 180 | 181 | FileHelper::createDirectory($systemPath); 182 | 183 | return [ 184 | 'system' => $systemPath . $cacheFile, 185 | 'web' => $cachePath . $cacheFile, 186 | 'public' => $cachePublicPath . $cacheFile, 187 | 'extension' => $cacheFileExt, 188 | ]; 189 | } 190 | 191 | /** 192 | * Show image 193 | * 194 | * @param $path 195 | * @param $type 196 | */ 197 | public function show($path, $type = self::IMAGE_ORIGINAL) 198 | { 199 | if (!array_key_exists($type, $this->config)) { 200 | $type = self::IMAGE_ORIGINAL; 201 | } 202 | 203 | if ($this->checkPermission($type)) { 204 | if ($file = $this->detectPath($path)) { 205 | $cachePath = $this->getCachePath($path, $type); 206 | if ($this->redirectWhenCached && $this->isFreshCache($cachePath['system'])) { 207 | return Yii::$app->response->redirect($cachePath['public']); 208 | } 209 | $image = Image::getImagine() 210 | ->open($file) 211 | ->copy(); 212 | $actions = $this->config[$type]; 213 | ArrayHelper::remove($actions, 'transparent'); 214 | foreach ($actions as $action => $options) { 215 | if (method_exists($this, $action)) { 216 | $image = $this->$action($image, $options); 217 | } 218 | } 219 | if (!$this->isFreshCache($cachePath['system'])) { 220 | $image->save($cachePath['system']); 221 | } 222 | $image->show($cachePath['extension']); 223 | Yii::$app->end(); 224 | } 225 | } 226 | 227 | $this->show($this->noImage, $type); 228 | } 229 | 230 | /** 231 | * Crop image 232 | * 233 | * @param $image \Imagine\Imagick\Image 234 | * @param $options 235 | * 236 | * @return mixed 237 | */ 238 | protected function crop($image, $options) 239 | { 240 | return $image->crop(new Point($options['point'][0], $options['point'][1]), new Box($options['box'][0], $options['box'][1])); 241 | } 242 | 243 | /** 244 | * Create thumbnail 245 | * 246 | * @param $image \Imagine\Imagick\Image 247 | * @param $options 248 | * @param string $filter 249 | * 250 | * @return ImageInterface 251 | * 252 | * @throws InvalidArgumentException 253 | */ 254 | protected function thumbnail($image, $options, $filter = ImageInterface::FILTER_UNDEFINED) 255 | { 256 | $width = $options['box'][0]; 257 | $height = $options['box'][1]; 258 | $size = $box = new Box($width, $height); 259 | $mode = $options['mode']; 260 | 261 | if ($mode !== ImageInterface::THUMBNAIL_INSET && $mode !== ImageInterface::THUMBNAIL_OUTBOUND) { 262 | throw new InvalidArgumentException('Invalid mode specified'); 263 | } 264 | 265 | $imageSize = $image->getSize(); 266 | $ratios = [ 267 | $size->getWidth() / $imageSize->getWidth(), 268 | $size->getHeight() / $imageSize->getHeight(), 269 | ]; 270 | 271 | $image->strip(); 272 | 273 | // if target width is larger than image width 274 | // AND target height is longer than image height 275 | if (!$size->contains($imageSize)) { 276 | if ($mode === ImageInterface::THUMBNAIL_INSET) { 277 | $ratio = min($ratios); 278 | } else { 279 | $ratio = max($ratios); 280 | } 281 | 282 | if ($mode === ImageInterface::THUMBNAIL_OUTBOUND) { 283 | if (!$imageSize->contains($size)) { 284 | $size = new Box( 285 | min($imageSize->getWidth(), $size->getWidth()), 286 | min($imageSize->getHeight(), $size->getHeight()) 287 | ); 288 | } else { 289 | $imageSize = $image->getSize()->scale($ratio); 290 | $image->resize($imageSize, $filter); 291 | } 292 | if ($ratios[0] > $ratios[1]) { 293 | $cropPoint = new Point(0, 0); 294 | } else { 295 | $cropPoint = new Point( 296 | max(0, round(($imageSize->getWidth() - $size->getWidth()) / 2)), 297 | max(0, round(($imageSize->getHeight() - $size->getHeight()) / 2)) 298 | ); 299 | } 300 | 301 | $image->crop($cropPoint, $size); 302 | } else { 303 | if (!$imageSize->contains($size)) { 304 | $imageSize = $imageSize->scale($ratio); 305 | $image->resize($imageSize, $filter); 306 | } else { 307 | $imageSize = $image->getSize()->scale($ratio); 308 | $image->resize($imageSize, $filter); 309 | } 310 | } 311 | } 312 | 313 | // create empty image to preserve aspect ratio of thumbnail 314 | $palette = new RGB(); 315 | $color = $palette->color('#000', 0); //transparent png with imagick 316 | $thumb = Image::getImagine()->create($box, $color); 317 | 318 | // calculate points 319 | $size = $image->getSize(); 320 | 321 | $startX = 0; 322 | $startY = 0; 323 | if ($size->getWidth() < $width) { 324 | $startX = ceil($width - $size->getWidth()) / 2; 325 | } 326 | if ($size->getHeight() < $height) { 327 | $startY = ceil($height - $size->getHeight()) / 2; 328 | } 329 | 330 | $thumb->paste($image, new Point($startX, $startY)); 331 | 332 | return $thumb; 333 | } 334 | 335 | /** 336 | * Add watermark 337 | * 338 | * @param $image \Imagine\Imagick\Image 339 | * @param $options 340 | * 341 | * @return mixed 342 | */ 343 | protected function watermark($image, $options) 344 | { 345 | $watermarkFilename = $options['watermarkFilename']; 346 | $watermark = Image::getImagine()->open(Yii::getAlias($watermarkFilename)); 347 | $size = $image->getSize(); 348 | $wSize = $watermark->getSize(); 349 | 350 | $bottomRight = new Point($size->getWidth() - $wSize->getWidth(), $size->getHeight() - $wSize->getHeight()); 351 | $image->paste($watermark, $bottomRight); 352 | 353 | return $image; 354 | } 355 | 356 | /** 357 | * Check permission for current user 358 | * 359 | * @param $type 360 | * 361 | * @return bool 362 | */ 363 | protected function checkPermission($type) 364 | { 365 | if (isset($this->config[$type]['visible'])) { 366 | $role = $this->config[$type]['visible']; 367 | unset($this->config[$type]['visible']); 368 | if (!Yii::$app->getUser()->can($role)) { 369 | return false; 370 | } 371 | } 372 | 373 | return true; 374 | } 375 | 376 | /** 377 | * Get image source path 378 | * 379 | * @return string 380 | */ 381 | protected function getImageSourcePath() 382 | { 383 | return Yii::getAlias('@app') . $this->sourcePath; 384 | } 385 | 386 | /** 387 | * Checks if cached file actual 388 | * 389 | * @param string $filePath 390 | * @return boolean 391 | */ 392 | protected function isFreshCache($filePath) 393 | { 394 | return file_exists($filePath) && (time() - filemtime($filePath) < $this->cacheTime); 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 yii2mod 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

Yii2 Image Extension

6 |
7 |

8 | 9 | Provides methods for the dynamic manipulation of images. Various image formats such as JPEG, PNG, and GIF can be resized, cropped, rotated. 10 | 11 | [![Latest Stable Version](https://poser.pugx.org/yii2mod/yii2-image/v/stable)](https://packagist.org/packages/yii2mod/yii2-image) [![Total Downloads](https://poser.pugx.org/yii2mod/yii2-image/downloads)](https://packagist.org/packages/yii2mod/yii2-image) [![License](https://poser.pugx.org/yii2mod/yii2-image/license)](https://packagist.org/packages/yii2mod/yii2-image) 12 | 13 | Installation 14 | ------------ 15 | 16 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 17 | 18 | Either run 19 | 20 | ``` 21 | php composer.phar require --prefer-dist yii2mod/yii2-image "*" 22 | ``` 23 | 24 | or add 25 | 26 | ``` 27 | "yii2mod/yii2-image": "*" 28 | ``` 29 | 30 | to the require section of your composer.json. 31 | 32 | Configuration 33 | ------------- 34 | 35 | **Component Setup** 36 | 37 | To use the Image Component, you need to configure the components array in your application configuration: 38 | ```php 39 | 'components' => [ 40 | 'image' => [ 41 | 'class' => 'yii2mod\image\ImageComponent', 42 | ], 43 | ], 44 | ``` 45 | 46 | **Attach the behavior to the model** 47 | 48 | You need to add the `ImageBehavior` to the your model. 49 | ```php 50 | public function behaviors() 51 | { 52 | return [ 53 | 'image' => [ 54 | 'class' => ImageBehavior::class, 55 | 'pathAttribute' => 'path', 56 | ], 57 | ]; 58 | } 59 | ``` 60 | 61 | **Action Setup** 62 | 63 | You need to add the `ImageAction` to the your controller. 64 | ```php 65 | public function actions() 66 | { 67 | return [ 68 | 'image' => 'yii2mod\image\actions\ImageAction' 69 | ]; 70 | } 71 | ``` 72 | 73 | **Configuring image types** 74 | 75 | Next, you should configure your params section in your configuration file: 76 | ```php 77 | 'params' => [ 78 | 'image' => [ 79 | 'medium' => [ 80 | 'thumbnail' => [ 81 | 'box' => [194, 194], 82 | 'mode' => 'outbound' 83 | ], 84 | 'visible' => 'user', //checking role before outputing url 85 | ], 86 | 'home' => [ 87 | 'thumbnail' => [ 88 | 'box' => [640, 480], 89 | 'mode' => 'inset', 90 | ], 91 | 'watermark' => [ 92 | 'watermarkFilename' => '@app/web/images/watermark.png', 93 | ], 94 | ], 95 | ], 96 | ], 97 | ``` 98 | 99 | Usage: 100 | ------ 101 | 102 | ```php 103 | $model = Model::find()->one(); 104 | echo $model->url('medium'); // home is the type of photo. 105 | ``` 106 | 107 | 108 | ## Support us 109 | 110 | Does your business depend on our contributions? Reach out and support us on [Patreon](https://www.patreon.com/yii2mod). 111 | All pledges will be dedicated to allocating workforce on maintenance and new awesome stuff. 112 | -------------------------------------------------------------------------------- /actions/ImageAction.php: -------------------------------------------------------------------------------- 1 | [ 26 | 'image' => [ 27 | 'medium' => [ 28 | 'thumbnail' => [ 29 | 'box' => [194, 194], 30 | 'mode' => 'outbound' 31 | ], 32 | 'visible' => 'user', //checking role before outputing url 33 | ], 34 | 'home' => [ 35 | 'thumbnail' => [ 36 | 'box' => [640, 480], 37 | 'mode' => 'inset', 38 | ], 39 | 'watermark' => [ 40 | 'watermarkFilename' => '@app/web/images/watermark.png', 41 | ], 42 | ], 43 | ], 44 | ], 45 | ]; 46 | $path = urldecode($path); 47 | $image = Yii::$app->get('image'); 48 | try { 49 | $image->show($path, $type); 50 | } catch (Exception $e) { 51 | Yii::error($e->getMessage()); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /assets/no-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yii2mod/yii2-image/787856b7f1f8899bbebf45e505db0be0af0bbc73/assets/no-image.png -------------------------------------------------------------------------------- /behaviors/ImageBehavior.php: -------------------------------------------------------------------------------- 1 | attachMethod($this->methodName, function ($mode = 'original') { 36 | $attribute = $this->owner->{$this->pathAttribute}; 37 | 38 | return Yii::$app->get('image')->getUrl($attribute, $mode); 39 | }); 40 | } 41 | 42 | /** 43 | * @param string $name 44 | * @param array $parameters 45 | * 46 | * @return mixed 47 | */ 48 | public function __call($name, $parameters) 49 | { 50 | if (isset($this->_methods[$name])) { 51 | return call_user_func_array($this->_methods[$name], $parameters); 52 | } 53 | 54 | return parent::__call($name, $parameters); 55 | } 56 | 57 | /** 58 | * @param string $name 59 | * 60 | * @return bool 61 | */ 62 | public function hasMethod($name) 63 | { 64 | if ($name === $this->methodName) { 65 | return is_callable([$this, $name]); 66 | } 67 | 68 | return parent::hasMethod($name); 69 | } 70 | 71 | /** 72 | * @param $name 73 | * @param $closure 74 | */ 75 | protected function attachMethod($name, $closure) 76 | { 77 | $this->_methods[$name] = \Closure::bind($closure, $this); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yii2mod/yii2-image", 3 | "description": "Provides methods for the dynamic manipulation of images. Various image formats such as JPEG, PNG, and GIF can be resized, cropped, rotated.", 4 | "type": "yii2-extension", 5 | "keywords": [ 6 | "yii2", 7 | "extension" 8 | ], 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Dmitry Semenov", 13 | "email": "disemx@gmail.com" 14 | }, 15 | { 16 | "name": "Igor Chepurnoi", 17 | "email": "chepurnoi.igor@gmail.com" 18 | } 19 | ], 20 | "require": { 21 | "php": ">=7.0.0", 22 | "yiisoft/yii2": "~2.0.14", 23 | "yiisoft/yii2-imagine": "*" 24 | }, 25 | "require-dev": { 26 | "friendsofphp/php-cs-fixer": "~2.0", 27 | "phpunit/phpunit": "~7.0" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "yii2mod\\image\\": "" 32 | } 33 | } 34 | } 35 | --------------------------------------------------------------------------------