├── EasyThumbnail.php ├── EasyThumbnailImage.php ├── FileNotFoundException.php ├── LICENSE ├── README.md └── composer.json /EasyThumbnail.php: -------------------------------------------------------------------------------- 1 | cacheAlias; 45 | EasyThumbnailImage::$cacheExpire = $this->cacheExpire; 46 | EasyThumbnailImage::$httpClient = $this->httpClient; 47 | 48 | if ($this->thumbnailBackgroundColor) { 49 | Image::$thumbnailBackgroundColor = $this->thumbnailBackgroundColor; 50 | } 51 | if ($this->thumbnailBackgroundAlpha) { 52 | Image::$thumbnailBackgroundAlpha = $this->thumbnailBackgroundAlpha; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /EasyThumbnailImage.php: -------------------------------------------------------------------------------- 1 | open(static::thumbnailFile($filename, $width, $height, $mode, $quality, $checkRemFileMode)); 85 | } 86 | 87 | /** 88 | * Creates and caches the image thumbnail and returns full path from thumbnail file. 89 | * 90 | * If one of thumbnail dimensions is set to `null`, another one is calculated automatically based on aspect ratio of 91 | * original image. Note that calculated thumbnail dimension may vary depending on the source image in this case. 92 | * 93 | * If both dimensions are specified, resulting thumbnail would be exactly the width and height specified. How it's 94 | * achieved depends on the mode. 95 | * 96 | * If `self::THUMBNAIL_OUTBOUND` mode is used, which is default, then the thumbnail is scaled so that 97 | * its smallest side equals the length of the corresponding side in the original image. Any excess outside of 98 | * the scaled thumbnail’s area will be cropped, and the returned thumbnail will have the exact width and height 99 | * specified. 100 | * 101 | * If thumbnail mode is `self::THUMBNAIL_INSET`, the original image is scaled down so it is fully 102 | * contained within the thumbnail dimensions. The rest is filled with background that could be configured via 103 | * [[Image::$thumbnailBackgroundColor]] or [[EasyThumbnail::$thumbnailBackgroundColor]], 104 | * and [[Image::$thumbnailBackgroundAlpha]] or [[EasyThumbnail::$thumbnailBackgroundAlpha]]. 105 | * 106 | * If thumbnail mode is `self::THUMBNAIL_INSET_BOX`, the original image is scaled down so it is fully contained 107 | * within the thumbnail dimensions. The specified $width and $height (supplied via $size) will be considered 108 | * maximum limits. Unless the given dimensions are equal to the original image’s aspect ratio, one dimension in the 109 | * resulting thumbnail will be smaller than the given limit. 110 | * 111 | * @param string $filename the image file path or path alias or URL 112 | * @param integer $width the width in pixels to create the thumbnail 113 | * @param integer $height the height in pixels to create the thumbnail 114 | * @param string $mode mode of resizing original image to use in case both width and height specified 115 | * @param integer $quality 116 | * @param integer $checkRemFileMode check file version on remote server 117 | * @return string 118 | * @throws FileNotFoundException 119 | * @throws InvalidConfigException 120 | * @throws \yii\httpclient\Exception 121 | */ 122 | public static function thumbnailFile($filename, $width, $height, $mode = self::THUMBNAIL_OUTBOUND, $quality = null, 123 | $checkRemFileMode = self::CHECK_REM_MODE_NONE) 124 | { 125 | $fileContent = null; 126 | $fileNameIsUrl = false; 127 | if (\preg_match('/^https?:\/\//', $filename)) { 128 | $fileNameIsUrl = true; 129 | switch ($checkRemFileMode) { 130 | case self::CHECK_REM_MODE_NONE: 131 | $thumbnailFileName = \md5($filename . $width . $height . $mode); 132 | break; 133 | case self::CHECK_REM_MODE_CRC: 134 | $fileContent = static::fileFromUrlContent($filename); 135 | $thumbnailFileName = \md5($filename . $width . $height . $mode . \crc32($fileContent)); 136 | break; 137 | case self::CHECK_REM_MODE_HEADER: 138 | $fileContent = static::fileFromUrlContent($filename); 139 | $thumbnailFileName = \md5( 140 | $filename . $width . $height . $mode . static::fileFromUrlDate($filename) 141 | ); 142 | break; 143 | default: 144 | throw new InvalidConfigException('Unknown `checkRemFileMode` param value.'); 145 | } 146 | } else { 147 | $filename = FileHelper::normalizePath(Yii::getAlias($filename)); 148 | if (!\is_file($filename)) { 149 | throw new FileNotFoundException("File {$filename} doesn't exist"); 150 | } 151 | $thumbnailFileName = \md5($filename . $width . $height . $mode . \filemtime($filename)); 152 | } 153 | $cachePath = Yii::getAlias('@webroot/' . static::$cacheAlias); 154 | 155 | $thumbnailFileExt = \strrchr($filename, '.'); 156 | $thumbnailFilePath = $cachePath . DIRECTORY_SEPARATOR . \substr($thumbnailFileName, 0, 2); 157 | $thumbnailFile = $thumbnailFilePath . DIRECTORY_SEPARATOR . $thumbnailFileName . $thumbnailFileExt; 158 | 159 | if (\file_exists($thumbnailFile)) { 160 | if (static::$cacheExpire !== 0 && (\time() - \filemtime($thumbnailFile)) > static::$cacheExpire) { 161 | \unlink($thumbnailFile); 162 | } else { 163 | return $thumbnailFile; 164 | } 165 | } 166 | if (!\is_dir($thumbnailFilePath)) { 167 | \mkdir($thumbnailFilePath, self::MKDIR_MODE, true); 168 | } 169 | 170 | if ($fileNameIsUrl) { 171 | $image = Image::getImagine()->load($fileContent ?: static::fileFromUrlContent($filename)); 172 | } else { 173 | $image = Image::getImagine()->open($filename); 174 | } 175 | if ($mode === self::THUMBNAIL_INSET_BOX) { 176 | $image = $image->thumbnail(new Box($width, $height), ManipulatorInterface::THUMBNAIL_INSET); 177 | } else { 178 | $image = Image::thumbnail($image, $width, $height, $mode); 179 | } 180 | 181 | $options = [ 182 | 'quality' => $quality === null ? self::QUALITY : $quality 183 | ]; 184 | 185 | $image->save($thumbnailFile, $options); 186 | return $thumbnailFile; 187 | } 188 | 189 | /** 190 | * Creates and caches the image thumbnail and returns URL from thumbnail file. 191 | * 192 | * If one of thumbnail dimensions is set to `null`, another one is calculated automatically based on aspect ratio of 193 | * original image. Note that calculated thumbnail dimension may vary depending on the source image in this case. 194 | * 195 | * If both dimensions are specified, resulting thumbnail would be exactly the width and height specified. How it's 196 | * achieved depends on the mode. 197 | * 198 | * If `self::THUMBNAIL_OUTBOUND` mode is used, which is default, then the thumbnail is scaled so that 199 | * its smallest side equals the length of the corresponding side in the original image. Any excess outside of 200 | * the scaled thumbnail’s area will be cropped, and the returned thumbnail will have the exact width and height 201 | * specified. 202 | * 203 | * If thumbnail mode is `self::THUMBNAIL_INSET`, the original image is scaled down so it is fully 204 | * contained within the thumbnail dimensions. The rest is filled with background that could be configured via 205 | * [[Image::$thumbnailBackgroundColor]] or [[EasyThumbnail::$thumbnailBackgroundColor]], 206 | * and [[Image::$thumbnailBackgroundAlpha]] or [[EasyThumbnail::$thumbnailBackgroundAlpha]]. 207 | * 208 | * If thumbnail mode is `self::THUMBNAIL_INSET_BOX`, the original image is scaled down so it is fully contained 209 | * within the thumbnail dimensions. The specified $width and $height (supplied via $size) will be considered 210 | * maximum limits. Unless the given dimensions are equal to the original image’s aspect ratio, one dimension in the 211 | * resulting thumbnail will be smaller than the given limit. 212 | * 213 | * @param string $filename the image file path or path alias or URL 214 | * @param integer $width the width in pixels to create the thumbnail 215 | * @param integer $height the height in pixels to create the thumbnail 216 | * @param string $mode mode of resizing original image to use in case both width and height specified 217 | * @param integer $quality 218 | * @param integer $checkRemFileMode check file version on remote server 219 | * @return string 220 | * @throws FileNotFoundException 221 | * @throws InvalidConfigException 222 | * @throws \yii\httpclient\Exception 223 | */ 224 | public static function thumbnailFileUrl($filename, $width, $height, $mode = self::THUMBNAIL_OUTBOUND, 225 | $quality = null, $checkRemFileMode = self::CHECK_REM_MODE_NONE) 226 | { 227 | $cacheUrl = Yii::getAlias('@web/' . static::$cacheAlias); 228 | $thumbnailFilePath = static::thumbnailFile($filename, $width, $height, $mode, $quality, $checkRemFileMode); 229 | 230 | \preg_match('#[^\\' . DIRECTORY_SEPARATOR . ']+$#', $thumbnailFilePath, $matches); 231 | $fileName = $matches[0]; 232 | 233 | return $cacheUrl . '/' . \substr($fileName, 0, 2) . '/' . $fileName; 234 | } 235 | 236 | /** 237 | * Creates and caches the image thumbnail and returns tag. 238 | * 239 | * If one of thumbnail dimensions is set to `null`, another one is calculated automatically based on aspect ratio of 240 | * original image. Note that calculated thumbnail dimension may vary depending on the source image in this case. 241 | * 242 | * If both dimensions are specified, resulting thumbnail would be exactly the width and height specified. How it's 243 | * achieved depends on the mode. 244 | * 245 | * If `self::THUMBNAIL_OUTBOUND` mode is used, which is default, then the thumbnail is scaled so that 246 | * its smallest side equals the length of the corresponding side in the original image. Any excess outside of 247 | * the scaled thumbnail’s area will be cropped, and the returned thumbnail will have the exact width and height 248 | * specified. 249 | * 250 | * If thumbnail mode is `self::THUMBNAIL_INSET`, the original image is scaled down so it is fully 251 | * contained within the thumbnail dimensions. The rest is filled with background that could be configured via 252 | * [[Image::$thumbnailBackgroundColor]] or [[EasyThumbnail::$thumbnailBackgroundColor]], 253 | * and [[Image::$thumbnailBackgroundAlpha]] or [[EasyThumbnail::$thumbnailBackgroundAlpha]]. 254 | * 255 | * @param string $filename the image file path or path alias or URL 256 | * @param integer $width the width in pixels to create the thumbnail 257 | * @param integer $height the height in pixels to create the thumbnail 258 | * @param string $mode mode of resizing original image to use in case both width and height specified 259 | * @param integer $quality 260 | * @param integer $checkRemFileMode check file version on remote server 261 | * @return string 262 | * @throws \Imagine\Exception\InvalidArgumentException 263 | * @throws \Imagine\Exception\RuntimeException 264 | * @throws \yii\base\InvalidParamException 265 | */ 266 | public static function thumbnailImg($filename, $width, $height, $mode = self::THUMBNAIL_OUTBOUND, $options = [], 267 | $quality = null, $checkRemFileMode = self::CHECK_REM_MODE_NONE) 268 | { 269 | try { 270 | $thumbnailFileUrl = static::thumbnailFileUrl( 271 | $filename, 272 | $width, 273 | $height, 274 | $mode, 275 | $quality, 276 | $checkRemFileMode 277 | ); 278 | } catch (\Exception $e) { 279 | return static::errorHandler($e, $filename); 280 | } 281 | 282 | return Html::img( 283 | $thumbnailFileUrl, 284 | $options 285 | ); 286 | } 287 | 288 | /** 289 | * Clear cache directory. 290 | * 291 | * @return bool 292 | * @throws \yii\base\ErrorException 293 | */ 294 | public static function clearCache() 295 | { 296 | $cacheDir = Yii::getAlias('@webroot/' . static::$cacheAlias); 297 | FileHelper::removeDirectory($cacheDir); 298 | return @\mkdir($cacheDir, self::MKDIR_MODE, true); 299 | } 300 | 301 | /** 302 | * @param \Exception $error 303 | * @param string $filename 304 | * @return string 305 | */ 306 | protected static function errorHandler($error, $filename) 307 | { 308 | if ($error instanceof FileNotFoundException) { 309 | return $error->getMessage(); 310 | } 311 | 312 | Yii::warning("{$error->getCode()}\n{$error->getMessage()}\n{$error->getFile()}"); 313 | return 'Error ' . $error->getCode(); 314 | } 315 | 316 | /** 317 | * @param string $url 318 | * @return string 319 | * @throws FileNotFoundException 320 | * @throws \yii\httpclient\Exception 321 | */ 322 | protected static function fileFromUrlDate($url) 323 | { 324 | $response = self::getHttpClient() 325 | ->head($url) 326 | ->send(); 327 | if (!$response->isOk) { 328 | throw new FileNotFoundException("URL {$url} doesn't exist"); 329 | } 330 | 331 | return $response->headers['Last-Modified']; 332 | } 333 | 334 | /** 335 | * @param string $url 336 | * @return string 337 | * @throws FileNotFoundException 338 | * @throws InvalidConfigException 339 | * @throws \yii\httpclient\Exception 340 | */ 341 | protected static function fileFromUrlContent($url) 342 | { 343 | $response = self::getHttpClient() 344 | ->createRequest() 345 | ->setMethod('GET') 346 | ->setUrl($url) 347 | ->send(); 348 | if (!$response->isOk) { 349 | throw new FileNotFoundException("URL {$url} doesn't exist"); 350 | } 351 | 352 | return $response->content; 353 | } 354 | 355 | /** 356 | * @return HttpClient 357 | */ 358 | protected static function getHttpClient() 359 | { 360 | if (self::$httpClient === null || !(self::$httpClient instanceof HttpClient)) { 361 | self::$httpClient = new HttpClient(); 362 | } 363 | 364 | return self::$httpClient; 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /FileNotFoundException.php: -------------------------------------------------------------------------------- 1 | [ 29 | 'thumbnail' => [ 30 | 'class' => 'himiklab\thumbnail\EasyThumbnail', 31 | 'cacheAlias' => 'assets/gallery_thumbnails', 32 | ], 33 | ], 34 | ``` 35 | 36 | and in `bootstrap` section, for example: 37 | 38 | ```php 39 | 'bootstrap' => ['log', 'thumbnail'], 40 | ``` 41 | 42 | It is necessary if you want to set global helper's settings for the application. 43 | 44 | Usage 45 | ----- 46 | For example: 47 | 48 | ```php 49 | use himiklab\thumbnail\EasyThumbnailImage; 50 | 51 | echo EasyThumbnailImage::thumbnailImg( 52 | $model->pictureFile, 53 | 50, 54 | 50, 55 | EasyThumbnailImage::THUMBNAIL_OUTBOUND, 56 | ['alt' => $model->pictureName] 57 | ); 58 | ``` 59 | 60 | or 61 | 62 | ```php 63 | use himiklab\thumbnail\EasyThumbnailImage; 64 | 65 | echo EasyThumbnailImage::thumbnailImg( 66 | 'http://...', 67 | 50, 68 | 50, 69 | EasyThumbnailImage::THUMBNAIL_OUTBOUND, 70 | ); 71 | ``` 72 | 73 | For other functions please see the source code. 74 | 75 | If you want to handle errors that appear while converting to thumbnail by yourself, please make your own class and inherit it from EasyThumbnailImage. In your class replace only protected method errorHandler. For example 76 | 77 | ```php 78 | class ThumbHelper extends \himiklab\thumbnail\EasyThumbnailImage 79 | { 80 | 81 | protected static function errorHandler($error, $filename) 82 | { 83 | if ($error instanceof \himiklab\thumbnail\FileNotFoundException) { 84 | return \yii\helpers\Html::img('@web/images/notfound.png'); 85 | } else { 86 | $filename = basename($filename); 87 | return \yii\helpers\Html::a($filename,"@web/files/$filename"); 88 | } 89 | } 90 | } 91 | ``` 92 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "himiklab/yii2-easy-thumbnail-image-helper", 3 | "description": "Yii2 helper for creating and caching thumbnails on real time", 4 | "keywords": ["yii2", "thumbnail", "image", "helper"], 5 | "type": "yii2-extension", 6 | "license": "MIT", 7 | "support": { 8 | "source": "https://github.com/himiklab/yii2-easy-thumbnail-image-helper", 9 | "issues": "https://github.com/himiklab/yii2-easy-thumbnail-image-helper/issues" 10 | }, 11 | "authors": [ 12 | { 13 | "name": "HimikLab", 14 | "homepage": "https://github.com/himiklab/" 15 | } 16 | ], 17 | "require": { 18 | "yiisoft/yii2": "~2.0.13", 19 | "yiisoft/yii2-imagine": ">=2.0.4", 20 | "yiisoft/yii2-httpclient": "*" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "himiklab\\thumbnail\\": "" 25 | } 26 | } 27 | } 28 | --------------------------------------------------------------------------------