1<?php 2/** 3 * 2007-2016 PrestaShop 4 * 5 * thirty bees is an extension to the PrestaShop e-commerce software developed by PrestaShop SA 6 * Copyright (C) 2017-2018 thirty bees 7 * 8 * NOTICE OF LICENSE 9 * 10 * This source file is subject to the Open Software License (OSL 3.0) 11 * that is bundled with this package in the file LICENSE.txt. 12 * It is also available through the world-wide-web at this URL: 13 * http://opensource.org/licenses/osl-3.0.php 14 * If you did not receive a copy of the license and are unable to 15 * obtain it through the world-wide-web, please send an email 16 * to license@thirtybees.com so we can send you a copy immediately. 17 * 18 * DISCLAIMER 19 * 20 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer 21 * versions in the future. If you wish to customize PrestaShop for your 22 * needs please refer to https://www.thirtybees.com for more information. 23 * 24 * @author thirty bees <contact@thirtybees.com> 25 * @author PrestaShop SA <contact@prestashop.com> 26 * @copyright 2017-2018 thirty bees 27 * @copyright 2007-2016 PrestaShop SA 28 * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 29 * PrestaShop is an internationally registered trademark & property of PrestaShop SA 30 */ 31 32/** 33 * Class ImageManagerCore 34 * 35 * @since 1.0.0 36 */ 37class ImageManagerCore 38{ 39 const ERROR_FILE_NOT_EXIST = 1; 40 const ERROR_FILE_WIDTH = 2; 41 const ERROR_MEMORY_LIMIT = 3; 42 43 /** 44 * Generate a cached thumbnail for object lists (eg. carrier, order statuses...etc) 45 * 46 * @param string $image Real image filename 47 * @param string $cacheImage Cached filename 48 * @param int $size Desired size 49 * @param string $imageType Image type 50 * @param bool $disableCache When turned on a timestamp will be added to the image URI to disable the HTTP cache 51 * @param bool $regenerate When turned on and the file already exist, the file will be regenerated 52 * 53 * @return string 54 * 55 * @since 1.0.0 56 * @version 1.0.0 Initial version 57 */ 58 public static function thumbnail($image, $cacheImage, $size, $imageType = 'jpg', $disableCache = true, $regenerate = false) 59 { 60 if (!file_exists($image)) { 61 return ''; 62 } 63 64 if (file_exists(_PS_TMP_IMG_DIR_.$cacheImage) && $regenerate) { 65 @unlink(_PS_TMP_IMG_DIR_.$cacheImage); 66 } 67 68 if ($regenerate || !file_exists(_PS_TMP_IMG_DIR_.$cacheImage)) { 69 $infos = getimagesize($image); 70 71 // Evaluate the memory required to resize the image: if it's too much, you can't resize it. 72 if (!ImageManager::checkImageMemoryLimit($image)) { 73 return false; 74 } 75 76 $x = $infos[0]; 77 $y = $infos[1]; 78 $maxX = $size * 3; 79 80 // Size is already ok 81 if ($y < $size && $x <= $maxX) { 82 copy($image, _PS_TMP_IMG_DIR_.$cacheImage); 83 } // We need to resize */ 84 else { 85 $ratio_x = $x / ($y / $size); 86 if ($ratio_x > $maxX) { 87 $ratio_x = $maxX; 88 $size = $y / ($x / $maxX); 89 } 90 91 ImageManager::resize($image, _PS_TMP_IMG_DIR_.$cacheImage, $ratio_x, $size, $imageType); 92 } 93 } 94 // Relative link will always work, whatever the base uri set in the admin 95 if (Context::getContext()->controller->controller_type == 'admin') { 96 return '<img src="../img/tmp/'.$cacheImage.($disableCache ? '?time='.time() : '').'" alt="" class="imgm img-thumbnail" />'; 97 } else { 98 return '<img src="'._PS_TMP_IMG_.$cacheImage.($disableCache ? '?time='.time() : '').'" alt="" class="imgm img-thumbnail" />'; 99 } 100 } 101 102 /** 103 * Check if memory limit is too long or not 104 * 105 * @param $image 106 * 107 * @return bool 108 * 109 * @since 1.0.0 110 * @version 1.0.0 Initial version 111 */ 112 public static function checkImageMemoryLimit($image) 113 { 114 $infos = @getimagesize($image); 115 116 if (!is_array($infos) || !isset($infos['bits'])) { 117 return true; 118 } 119 120 $memoryLimit = Tools::getMemoryLimit(); 121 // memory_limit == -1 => unlimited memory 122 if (isset($infos['bits']) && function_exists('memory_get_usage') && (int) $memoryLimit != -1) { 123 $currentMemory = memory_get_usage(); 124 $bits = $infos['bits'] / 8; 125 $channel = isset($infos['channels']) ? $infos['channels'] : 1; 126 127 // Evaluate the memory required to resize the image: if it's too much, you can't resize it. 128 // For perfs, avoid computing static maths formulas in the code. pow(2, 16) = 65536 ; 1024 * 1024 = 1048576 129 if (($infos[0] * $infos[1] * $bits * $channel + 65536) * 1.8 + $currentMemory > $memoryLimit - 1048576) { 130 return false; 131 } 132 } 133 134 return true; 135 } 136 137 /** 138 * Resize, cut and optimize image 139 * 140 * @param string $srcFile Image object from $_FILE 141 * @param string $dstFile Destination filename 142 * @param int $dstWidth Desired width (optional) 143 * @param int $dstHeight Desired height (optional) 144 * @param string $fileType 145 * @param bool $forceType 146 * @param int $error 147 * @param int $tgtWidth 148 * @param int $tgtHeight 149 * @param int $quality 150 * @param int $srcWidth 151 * @param int $srcHeight 152 * 153 * @return bool Operation result 154 * 155 * @since 1.0.0 156 * @version 1.0.0 Initial version 157 * @throws PrestaShopException 158 */ 159 public static function resize( 160 $srcFile, 161 $dstFile, 162 $dstWidth = null, 163 $dstHeight = null, 164 $fileType = 'jpg', 165 $forceType = false, 166 &$error = 0, 167 &$tgtWidth = null, 168 &$tgtHeight = null, 169 $quality = 5, 170 &$srcWidth = null, 171 &$srcHeight = null 172 ) { 173 clearstatcache(true, $srcFile); 174 175 if (!file_exists($srcFile) || !filesize($srcFile)) { 176 return !($error = static::ERROR_FILE_NOT_EXIST); 177 } 178 179 list($tmpWidth, $tmpHeight, $type) = getimagesize($srcFile); 180 $rotate = 0; 181 if (function_exists('exif_read_data') && function_exists('mb_strtolower')) { 182 $exif = @exif_read_data($srcFile); 183 184 if ($exif && isset($exif['Orientation'])) { 185 switch ($exif['Orientation']) { 186 case 3: 187 $srcWidth = $tmpWidth; 188 $srcHeight = $tmpHeight; 189 $rotate = 180; 190 break; 191 192 case 6: 193 $srcWidth = $tmpHeight; 194 $srcHeight = $tmpWidth; 195 $rotate = -90; 196 break; 197 198 case 8: 199 $srcWidth = $tmpHeight; 200 $srcHeight = $tmpWidth; 201 $rotate = 90; 202 break; 203 204 default: 205 $srcWidth = $tmpWidth; 206 $srcHeight = $tmpHeight; 207 } 208 } else { 209 $srcWidth = $tmpWidth; 210 $srcHeight = $tmpHeight; 211 } 212 } else { 213 $srcWidth = $tmpWidth; 214 $srcHeight = $tmpHeight; 215 } 216 217 // If PS_IMAGE_QUALITY is activated, the generated image will be a PNG with .jpg as a file extension. 218 // This allow for higher quality and for transparency. JPG source files will also benefit from a higher quality 219 // because JPG reencoding by GD, even with max quality setting, degrades the image. 220 if ($fileType !== 'webp' && (Configuration::get('PS_IMAGE_QUALITY') == 'png_all' 221 || (Configuration::get('PS_IMAGE_QUALITY') == 'png' && $type == IMAGETYPE_PNG) && !$forceType) 222 ) { 223 $fileType = 'png'; 224 } 225 226 if (!$srcWidth) { 227 return !($error = static::ERROR_FILE_WIDTH); 228 } 229 if (!$dstWidth) { 230 $dstWidth = $srcWidth; 231 } 232 if (!$dstHeight) { 233 $dstHeight = $srcHeight; 234 } 235 236 $widthDiff = $dstWidth / $srcWidth; 237 $heightDiff = $dstHeight / $srcHeight; 238 239 $psImageGenerationMethod = Configuration::get('PS_IMAGE_GENERATION_METHOD'); 240 if ($widthDiff > 1 && $heightDiff > 1) { 241 $nextWidth = $srcWidth; 242 $nextHeight = $srcHeight; 243 } else { 244 if ($psImageGenerationMethod == 2 || (!$psImageGenerationMethod && $widthDiff > $heightDiff)) { 245 $nextHeight = $dstHeight; 246 $nextWidth = round(($srcWidth * $nextHeight) / $srcHeight); 247 $dstWidth = (int) (!$psImageGenerationMethod ? $dstWidth : $nextWidth); 248 } else { 249 $nextWidth = $dstWidth; 250 $nextHeight = round($srcHeight * $dstWidth / $srcWidth); 251 $dstHeight = (int) (!$psImageGenerationMethod ? $dstHeight : $nextHeight); 252 } 253 } 254 255 if (!ImageManager::checkImageMemoryLimit($srcFile)) { 256 return !($error = static::ERROR_MEMORY_LIMIT); 257 } 258 259 $tgtWidth = $dstWidth; 260 $tgtHeight = $dstHeight; 261 262 $destImage = imagecreatetruecolor($dstWidth, $dstHeight); 263 264 // If image is a PNG or WEBP and the output is PNG/WEBP, fill with transparency. Else fill with white background. 265 if ($fileType == 'png' && $type == IMAGETYPE_PNG || $fileType === 'webp') { 266 imagealphablending($destImage, false); 267 imagesavealpha($destImage, true); 268 $transparent = imagecolorallocatealpha($destImage, 255, 255, 255, 127); 269 imagefilledrectangle($destImage, 0, 0, $dstWidth, $dstHeight, $transparent); 270 } else { 271 $white = imagecolorallocate($destImage, 255, 255, 255); 272 imagefilledrectangle($destImage, 0, 0, $dstWidth, $dstHeight, $white); 273 } 274 275 $srcImage = ImageManager::create($type, $srcFile); 276 if ($rotate) { 277 $srcImage = imagerotate($srcImage, $rotate, 0); 278 } 279 280 if ($dstWidth >= $srcWidth && $dstHeight >= $srcHeight) { 281 imagecopyresized($destImage, $srcImage, (int) (($dstWidth - $nextWidth) / 2), (int) (($dstHeight - $nextHeight) / 2), 0, 0, $nextWidth, $nextHeight, $srcWidth, $srcHeight); 282 } else { 283 ImageManager::imagecopyresampled($destImage, $srcImage, (int) (($dstWidth - $nextWidth) / 2), (int) (($dstHeight - $nextHeight) / 2), 0, 0, $nextWidth, $nextHeight, $srcWidth, $srcHeight, $quality); 284 } 285 $writeFile = ImageManager::write($fileType, $destImage, $dstFile); 286 @imagedestroy($srcImage); 287 288 return $writeFile; 289 } 290 291 /** 292 * Create an image with GD extension from a given type 293 * 294 * @param string $type 295 * @param string $filename 296 * 297 * @return resource 298 * 299 * @since 1.0.0 300 * @version 1.0.0 Initial version 301 */ 302 public static function create($type, $filename) 303 { 304 switch ($type) { 305 case IMAGETYPE_GIF : 306 return imagecreatefromgif($filename); 307 308 case IMAGETYPE_PNG : 309 return imagecreatefrompng($filename); 310 311 case 18: 312 return imagecreatefromwebp($filename); 313 314 case IMAGETYPE_JPEG : 315 default: 316 return imagecreatefromjpeg($filename); 317 break; 318 } 319 } 320 321 /** 322 * @param $dstImage 323 * @param $srcImage 324 * @param $dstX 325 * @param $dstY 326 * @param $srcX 327 * @param $srcY 328 * @param $dstW 329 * @param $dstH 330 * @param $srcW 331 * @param $srcH 332 * @param int $quality 333 * 334 * @return bool 335 * 336 * @since 1.0.0 337 * @version 1.0.0 Initial version 338 */ 339 public static function imagecopyresampled(&$dstImage, $srcImage, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH, $quality = 3) 340 { 341 // Plug-and-Play fastimagecopyresampled function replaces much slower imagecopyresampled. 342 // Just include this function and change all "imagecopyresampled" references to "fastimagecopyresampled". 343 // Typically from 30 to 60 times faster when reducing high resolution images down to thumbnail size using the default quality setting. 344 // Author: Tim Eckel - Date: 09/07/07 - Version: 1.1 - Project: FreeRingers.net - Freely distributable - These comments must remain. 345 // 346 // Optional "quality" parameter (defaults is 3). Fractional values are allowed, for example 1.5. Must be greater than zero. 347 // Between 0 and 1 = Fast, but mosaic results, closer to 0 increases the mosaic effect. 348 // 1 = Up to 350 times faster. Poor results, looks very similar to imagecopyresized. 349 // 2 = Up to 95 times faster. Images appear a little sharp, some prefer this over a quality of 3. 350 // 3 = Up to 60 times faster. Will give high quality smooth results very close to imagecopyresampled, just faster. 351 // 4 = Up to 25 times faster. Almost identical to imagecopyresampled for most images. 352 // 5 = No speedup. Just uses imagecopyresampled, no advantage over imagecopyresampled. 353 354 if (empty($srcImage) || empty($dstImage) || $quality <= 0) { 355 return false; 356 } 357 if ($quality < 5 && (($dstW * $quality) < $srcW || ($dstH * $quality) < $srcH)) { 358 $temp = imagecreatetruecolor($dstW * $quality + 1, $dstH * $quality + 1); 359 imagecopyresized($temp, $srcImage, 0, 0, $srcX, $srcY, $dstW * $quality + 1, $dstH * $quality + 1, $srcW, $srcH); 360 imagecopyresampled($dstImage, $temp, $dstX, $dstY, 0, 0, $dstW, $dstH, $dstW * $quality, $dstH * $quality); 361 imagedestroy($temp); 362 } else { 363 imagecopyresampled($dstImage, $srcImage, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH); 364 } 365 366 return true; 367 } 368 369 /** 370 * Generate and write image 371 * 372 * @param string $type 373 * @param resource $resource 374 * @param string $filename 375 * 376 * @return bool 377 * 378 * @since 1.0.0 379 * @version 1.0.0 Initial version 380 * @throws PrestaShopException 381 */ 382 public static function write($type, $resource, $filename) 383 { 384 static $psPngQuality = null; 385 static $psJpegQuality = null; 386 static $psWebpQuality = null; 387 388 if ($psPngQuality === null) { 389 $psPngQuality = Configuration::get('PS_PNG_QUALITY'); 390 } 391 392 if ($psJpegQuality === null) { 393 $psJpegQuality = Configuration::get('PS_JPEG_QUALITY'); 394 } 395 396 if ($psWebpQuality === null) { 397 $psWebpQuality = Configuration::get('PS_WEBP_QUALITY'); 398 } 399 400 switch ($type) { 401 case 'gif': 402 $success = imagegif($resource, $filename); 403 break; 404 405 case 'png': 406 $quality = ($psPngQuality === false ? 7 : $psPngQuality); 407 $success = imagepng($resource, $filename, (int) $quality); 408 break; 409 410 case 'webp': 411 $quality = ($psWebpQuality === false ? 90 : $psWebpQuality); 412 $success = imagewebp($resource, $filename, (int) $quality); 413 break; 414 415 case 'jpg': 416 case 'jpeg': 417 default: 418 $quality = ($psJpegQuality === false ? 90 : $psJpegQuality); 419 imageinterlace($resource, 1); /// make it PROGRESSIVE 420 $success = imagejpeg($resource, $filename, (int) $quality); 421 break; 422 } 423 imagedestroy($resource); 424 @chmod($filename, 0664); 425 426 return $success; 427 } 428 429 /** 430 * Validate image upload (check image type and weight) 431 * 432 * @param array $file Upload $_FILE value 433 * @param int $maxFileSize Maximum upload size 434 * 435 * @return bool|string Return false if no error encountered 436 * 437 * @since 1.0.0 438 * @version 1.0.0 Initial version 439 */ 440 public static function validateUpload($file, $maxFileSize = 0, $types = null) 441 { 442 if ((int) $maxFileSize > 0 && $file['size'] > (int) $maxFileSize) { 443 return sprintf(Tools::displayError('Image is too large (%1$d kB). Maximum allowed: %2$d kB'), $file['size'] / 1024, $maxFileSize / 1024); 444 } 445 if (!ImageManager::isRealImage($file['tmp_name'], $file['type']) || !ImageManager::isCorrectImageFileExt($file['name'], $types) || preg_match('/\%00/', $file['name'])) { 446 return Tools::displayError('Image format not recognized, allowed formats are: .gif, .jpg, .png'); 447 } 448 if ($file['error']) { 449 return sprintf(Tools::displayError('Error while uploading image; please change your server\'s settings. (Error code: %s)'), $file['error']); 450 } 451 452 return false; 453 } 454 455 /** 456 * Check if file is a real image 457 * 458 * @param string $filename File path to check 459 * @param string $fileMimeType File known mime type (generally from $_FILES) 460 * @param array $mimeTypeList Allowed MIME types 461 * 462 * @return bool 463 * 464 * @since 1.0.0 465 * @version 1.0.0 Initial version 466 */ 467 public static function isRealImage($filename, $fileMimeType = null, $mimeTypeList = null) 468 { 469 // Detect mime content type 470 $mimeType = false; 471 if (!$mimeTypeList) { 472 $mimeTypeList = ['image/gif', 'image/jpg', 'image/jpeg', 'image/pjpeg', 'image/png', 'image/x-png']; 473 } 474 475 // Try 4 different methods to determine the mime type 476 if (function_exists('getimagesize')) { 477 $imageInfo = @getimagesize($filename); 478 479 if ($imageInfo) { 480 $mimeType = $imageInfo['mime']; 481 } else { 482 $fileMimeType = false; 483 } 484 } elseif (function_exists('finfo_open')) { 485 $const = defined('FILEINFO_MIME_TYPE') ? FILEINFO_MIME_TYPE : FILEINFO_MIME; 486 $finfo = finfo_open($const); 487 $mimeType = finfo_file($finfo, $filename); 488 finfo_close($finfo); 489 } elseif (function_exists('mime_content_type')) { 490 $mimeType = mime_content_type($filename); 491 } elseif (function_exists('exec')) { 492 $mimeType = trim(exec('file -b --mime-type '.escapeshellarg($filename))); 493 if (!$mimeType) { 494 $mimeType = trim(exec('file --mime '.escapeshellarg($filename))); 495 } 496 if (!$mimeType) { 497 $mimeType = trim(exec('file -bi '.escapeshellarg($filename))); 498 } 499 } 500 501 if ($fileMimeType && (empty($mimeType) || $mimeType == 'regular file' || $mimeType == 'text/plain')) { 502 $mimeType = $fileMimeType; 503 } 504 505 // For each allowed MIME type, we are looking for it inside the current MIME type 506 foreach ($mimeTypeList as $type) { 507 if (strstr($mimeType, $type)) { 508 return true; 509 } 510 } 511 512 return false; 513 } 514 515 /** 516 * Check if image file extension is correct 517 * 518 * @param string $filename Real filename 519 * @param array|null $authorizedExtensions 520 * 521 * @return bool True if it's correct 522 * 523 * @since 1.0.0 524 * @version 1.0.0 Initial version 525 */ 526 public static function isCorrectImageFileExt($filename, $authorizedExtensions = null) 527 { 528 // Filter on file extension 529 if ($authorizedExtensions === null) { 530 $authorizedExtensions = ['gif', 'jpg', 'jpeg', 'jpe', 'png']; 531 } 532 $nameExplode = explode('.', $filename); 533 if (count($nameExplode) >= 2) { 534 $current_extension = strtolower($nameExplode[count($nameExplode) - 1]); 535 if (!in_array($current_extension, $authorizedExtensions)) { 536 return false; 537 } 538 } else { 539 return false; 540 } 541 542 return true; 543 } 544 545 /** 546 * Validate icon upload 547 * 548 * @param array $file Upload $_FILE value 549 * @param int $maxFileSize Maximum upload size 550 * 551 * @return bool|string Return false if no error encountered 552 * 553 * @since 1.0.0 554 * @version 1.0.0 Initial version 555 */ 556 public static function validateIconUpload($file, $maxFileSize = 0) 557 { 558 if ((int) $maxFileSize > 0 && $file['size'] > $maxFileSize) { 559 return sprintf( 560 Tools::displayError('Image is too large (%1$d kB). Maximum allowed: %2$d kB'), 561 $file['size'] / 1000, 562 $maxFileSize / 1000 563 ); 564 } 565 if (substr($file['name'], -4) != '.ico' && substr($file['name'], -4) != '.png') { 566 return Tools::displayError('Image format not recognized, allowed formats are: .ico, .png'); 567 } 568 if ($file['error']) { 569 return Tools::displayError('Error while uploading image; please change your server\'s settings.'); 570 } 571 572 return false; 573 } 574 575 /** 576 * Cut image 577 * 578 * @param array $srcFile Origin filename 579 * @param string $dstFile Destination filename 580 * @param int $dstWidth Desired width 581 * @param int $dstHeight Desired height 582 * @param string $fileType 583 * @param int $dstX 584 * @param int $dstY 585 * 586 * @return bool Operation result 587 * 588 * @since 1.0.0 589 * @version 1.0.0 Initial version 590 * @throws PrestaShopException 591 */ 592 public static function cut($srcFile, $dstFile, $dstWidth = null, $dstHeight = null, $fileType = 'jpg', $dstX = 0, $dstY = 0) 593 { 594 if (!file_exists($srcFile)) { 595 return false; 596 } 597 598 // Source information 599 $srcInfo = getimagesize($srcFile); 600 $src = [ 601 'width' => $srcInfo[0], 602 'height' => $srcInfo[1], 603 'ressource' => ImageManager::create($srcInfo[2], $srcFile), 604 ]; 605 606 // Destination information 607 $dest = []; 608 $dest['x'] = $dstX; 609 $dest['y'] = $dstY; 610 $dest['width'] = !is_null($dstWidth) ? $dstWidth : $src['width']; 611 $dest['height'] = !is_null($dstHeight) ? $dstHeight : $src['height']; 612 $dest['ressource'] = ImageManager::createWhiteImage($dest['width'], $dest['height']); 613 614 $white = imagecolorallocate($dest['ressource'], 255, 255, 255); 615 imagecopyresampled($dest['ressource'], $src['ressource'], 0, 0, $dest['x'], $dest['y'], $dest['width'], $dest['height'], $dest['width'], $dest['height']); 616 imagecolortransparent($dest['ressource'], $white); 617 $return = ImageManager::write($fileType, $dest['ressource'], $dstFile); 618 @imagedestroy($src['ressource']); 619 620 return $return; 621 } 622 623 /** 624 * Create an empty image with white background 625 * 626 * @param int $width 627 * @param int $height 628 * 629 * @return resource 630 * 631 * @since 1.0.0 632 * @version 1.0.0 Initial version 633 */ 634 public static function createWhiteImage($width, $height) 635 { 636 $image = imagecreatetruecolor($width, $height); 637 $white = imagecolorallocate($image, 255, 255, 255); 638 imagefill($image, 0, 0, $white); 639 640 return $image; 641 } 642 643 /** 644 * Return the mime type by the file extension 645 * 646 * @param string $fileName 647 * 648 * @return string 649 * 650 * @since 1.0.0 651 * @version 1.0.0 Initial version 652 */ 653 public static function getMimeTypeByExtension($fileName) 654 { 655 $types = [ 656 'image/gif' => ['gif'], 657 'image/jpeg' => ['jpg', 'jpeg'], 658 'image/png' => ['png'], 659 ]; 660 $extension = substr($fileName, strrpos($fileName, '.') + 1); 661 662 $mimeType = null; 663 foreach ($types as $mime => $exts) { 664 if (in_array($extension, $exts)) { 665 $mimeType = $mime; 666 break; 667 } 668 } 669 670 if ($mimeType === null) { 671 $mimeType = 'image/jpeg'; 672 } 673 674 return $mimeType; 675 } 676 677 678 /** 679 * Add an image to the generator. 680 * 681 * This function adds a source image to the generator. It serves two main purposes: add a source image if one was 682 * not supplied to the constructor and to add additional source images so that different images can be supplied for 683 * different sized images in the resulting ICO file. For instance, a small source image can be used for the small 684 * resolutions while a larger source image can be used for large resolutions. 685 * 686 * @param string $source Path to the source image file. 687 * @param array $sizes Optional. An array of sizes (each size is an array with a width and height) that the source image should be rendered at in the generated ICO file. If sizes are not supplied, the size of the source image will be used. 688 * 689 * @return boolean true on success and false on failure. 690 * 691 * @copyright 2011-2016 Chris Jean 692 * @author Chris Jean 693 * @license GNU General Public License v2.0 694 * @source https://github.com/chrisbliss18/php-ico 695 */ 696 public static function generateFavicon($source, $sizes = [['16', '16'], ['24', '24'], ['32', '32'], ['48', '48'], ['64', '64']]) 697 { 698 $images = []; 699 700 if (!$size = getimagesize($source)) { 701 return false; 702 } 703 if (!$file_data = file_get_contents($source)) { 704 return false; 705 } 706 if (!$im = imagecreatefromstring($file_data)) { 707 return false; 708 } 709 unset($file_data); 710 if (empty($sizes)) { 711 $sizes = [imagesx($im), imagesy($im)]; 712 } 713 714 // If just a single size was passed, put it in array. 715 if ( ! is_array( $sizes[0] ) ) { 716 $sizes = [$sizes]; 717 } 718 719 foreach ( (array) $sizes as $size ) { 720 list( $width, $height ) = $size; 721 $new_im = imagecreatetruecolor( $width, $height ); 722 imagecolortransparent( $new_im, imagecolorallocatealpha( $new_im, 0, 0, 0, 127 ) ); 723 imagealphablending( $new_im, false ); 724 imagesavealpha( $new_im, true ); 725 $source_width = imagesx( $im ); 726 $source_height = imagesy( $im ); 727 if ( false === imagecopyresampled( $new_im, $im, 0, 0, 0, 0, $width, $height, $source_width, $source_height ) ) 728 continue; 729 730 static::addFaviconImageData($new_im, $images); 731 } 732 733 return static::getIcoData($images); 734 } 735 736 /** 737 * Generate the final ICO data by creating a file header and adding the image data. 738 * 739 * @copyright 2011-2016 Chris Jean 740 * @author Chris Jean 741 * @license GNU General Public License v2.0 742 * @source https://github.com/chrisbliss18/php-ico 743 */ 744 protected static function getIcoData($images) 745 { 746 if (!is_array($images) || empty($images)) 747 return false; 748 $data = pack('vvv', 0, 1, count($images)); 749 $pixel_data = ''; 750 $icon_dir_entry_size = 16; 751 $offset = 6 + ($icon_dir_entry_size * count($images)); 752 foreach ($images as $image) { 753 $data .= pack('CCCCvvVV', $image['width'], $image['height'], $image['color_palette_colors'], 0, 1, $image['bits_per_pixel'], $image['size'], $offset); 754 $pixel_data .= $image['data']; 755 $offset += $image['size']; 756 } 757 $data .= $pixel_data; 758 unset($pixel_data); 759 return $data; 760 } 761 762 /** 763 * Take a GD image resource and change it into a raw BMP format. 764 * 765 * @copyright 2011-2016 Chris Jean 766 * @author Chris Jean 767 * @license GNU General Public License v2.0 768 * @source https://github.com/chrisbliss18/php-ico 769 */ 770 protected static function addFaviconImageData($im, &$images) 771 { 772 $width = imagesx($im); 773 $height = imagesy($im); 774 $pixel_data = []; 775 $opacity_data = []; 776 $current_opacity_val = 0; 777 for ($y = $height - 1; $y >= 0; $y--) { 778 for ($x = 0; $x < $width; $x++) { 779 $color = imagecolorat($im, $x, $y); 780 $alpha = ($color & 0x7F000000) >> 24; 781 $alpha = (1 - ($alpha / 127)) * 255; 782 $color &= 0xFFFFFF; 783 $color |= 0xFF000000 & ($alpha << 24); 784 $pixel_data[] = $color; 785 $opacity = ($alpha <= 127) ? 1 : 0; 786 $current_opacity_val = ($current_opacity_val << 1) | $opacity; 787 if ((($x + 1) % 32) == 0) { 788 $opacity_data[] = $current_opacity_val; 789 $current_opacity_val = 0; 790 } 791 } 792 if (($x % 32) > 0) { 793 while (($x++ % 32) > 0) { 794 $current_opacity_val = $current_opacity_val << 1; 795 } 796 $opacity_data[] = $current_opacity_val; 797 $current_opacity_val = 0; 798 } 799 } 800 $image_header_size = 40; 801 $color_mask_size = $width * $height * 4; 802 $opacity_mask_size = (ceil($width / 32) * 4) * $height; 803 $data = pack('VVVvvVVVVVV', 40, $width, ($height * 2), 1, 32, 0, 0, 0, 0, 0, 0); 804 foreach ($pixel_data as $color) { 805 $data .= pack('V', $color); 806 } 807 foreach ($opacity_data as $opacity) { 808 $data .= pack('N', $opacity); 809 } 810 $image = [ 811 'width' => $width, 812 'height' => $height, 813 'color_palette_colors' => 0, 814 'bits_per_pixel' => 32, 815 'size' => $image_header_size + $color_mask_size + $opacity_mask_size, 816 'data' => $data, 817 ]; 818 $images[] = $image; 819 } 820 821 /** 822 * @param bool $checkAccept Check the accept header 823 * 824 * @return bool 825 * 826 * @since 1.0.4 827 */ 828 public static function webpSupport($checkAccept = false) 829 { 830 static $supported = null; 831 if ($supported === null) { 832 $config = Context::getContext()->theme->getConfiguration(); 833 834 try { 835 $supported = Configuration::get('TB_USE_WEBP') 836 && !empty($config['webp']) 837 && function_exists('imagewebp'); 838 } catch (PrestaShopException $e) { 839 $supported = false; 840 } 841 } 842 843 if ($checkAccept) { 844 $supported &= !empty($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'image/webp' ) !== false; 845 } 846 847 return $supported; 848 } 849 850 /** 851 * @return bool 852 * 853 * @since 1.0.4 854 */ 855 public static function retinaSupport() 856 { 857 static $supported = null; 858 if ($supported === null) { 859 try { 860 $supported = (bool) Configuration::get('PS_HIGHT_DPI'); 861 } catch (PrestaShopException $e) { 862 $supported = false; 863 } 864 } 865 866 return $supported; 867 } 868} 869