├── .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 |