/** * @param bool $deferredAllowed */ public function generate($deferredAllowed = true) { $errorImage = PIMCORE_PATH . '/static6/img/filetype-not-supported.png'; $deferred = false; $generated = false; if (!$this->asset) { $this->filesystemPath = $errorImage; } elseif (!$this->filesystemPath) { // if no correct thumbnail config is given use the original image as thumbnail if (!$this->config) { $fsPath = $this->asset->getFileSystemPath(); $this->filesystemPath = str_replace(PIMCORE_DOCUMENT_ROOT, "", $fsPath); } else { try { $deferred = $deferredAllowed && $this->deferred ? true : false; $this->filesystemPath = Thumbnail\Processor::process($this->asset, $this->config, null, $deferred, true, $generated); } catch (\Exception $e) { $this->filesystemPath = $errorImage; \Logger::error("Couldn't create thumbnail of image " . $this->asset->getRealFullPath()); \Logger::error($e); } } } \Pimcore::getEventManager()->trigger("asset.image.thumbnail", $this, ["deferred" => $deferred, "generated" => $generated]); }
/** * @return string */ public function __toString() { if ($this->image) { return $this->image->__toString(); } return ""; }
/** * Get default image * * @return bool|\Pimcore\Model\Asset */ public function getDefaultImage() { $config = Config::getConfig(); $config = $config->toArray(); $image = Image::getByPath($config['category']['default-image']); if ($image instanceof Image) { return $image; } return false; }
/** * @param $asset * @param Config $config * @param null $fileSystemPath * @param bool $deferred deferred means that the image will be generated on-the-fly (details see below) * @return mixed|string */ public static function process($asset, Config $config, $fileSystemPath = null, $deferred = false) { $format = strtolower($config->getFormat()); $contentOptimizedFormat = false; if (!$fileSystemPath && $asset instanceof Asset) { $fileSystemPath = $asset->getFileSystemPath(); } if ($asset instanceof Asset) { $id = $asset->getId(); } else { $id = "dyn~" . crc32($fileSystemPath); } if (!file_exists($fileSystemPath)) { return "/pimcore/static/img/filetype-not-supported.png"; } $modificationDate = filemtime($fileSystemPath); $fileExt = File::getFileExtension(basename($fileSystemPath)); // simple detection for source type if SOURCE is selected if ($format == "source" || empty($format)) { $format = self::getAllowedFormat($fileExt, array("jpeg", "gif", "png"), "png"); $contentOptimizedFormat = true; // format can change depending of the content (alpha-channel, ...) } if ($format == "print") { $format = self::getAllowedFormat($fileExt, array("svg", "jpeg", "png", "tiff"), "png"); if (($format == "tiff" || $format == "svg") && \Pimcore\Tool::isFrontentRequestByAdmin()) { // return a webformat in admin -> tiff cannot be displayed in browser $format = "png"; } else { if ($format == "tiff") { $transformations = $config->getItems(); if (is_array($transformations) && count($transformations) > 0) { foreach ($transformations as $transformation) { if (!empty($transformation)) { if ($transformation["method"] == "tifforiginal") { return str_replace(PIMCORE_DOCUMENT_ROOT, "", $fileSystemPath); } } } } } else { if ($format == "svg") { return str_replace(PIMCORE_DOCUMENT_ROOT, "", $fileSystemPath); } } } } $thumbDir = $asset->getImageThumbnailSavePath() . "/thumb__" . $config->getName(); $filename = preg_replace("/\\." . preg_quote(File::getFileExtension($asset->getFilename())) . "/", "", $asset->getFilename()); // add custom suffix if available if ($config->getFilenameSuffix()) { $filename .= "~-~" . $config->getFilenameSuffix(); } // add high-resolution modifier suffix to the filename if ($config->getHighResolution() > 1) { $filename .= "@" . $config->getHighResolution() . "x"; } $filename .= "." . $format; $fsPath = $thumbDir . "/" . $filename; if (!is_dir(dirname($fsPath))) { File::mkdir(dirname($fsPath)); } $path = str_replace(PIMCORE_DOCUMENT_ROOT, "", $fsPath); // check for existing and still valid thumbnail if (is_file($fsPath) and filemtime($fsPath) >= $modificationDate) { return $path; } // deferred means that the image will be generated on-the-fly (when requested by the browser) // the configuration is saved for later use in Pimcore\Controller\Plugin\Thumbnail::routeStartup() // so that it can be used also with dynamic configurations if ($deferred) { $configId = "thumb_" . $id . "__" . md5($path); TmpStore::add($configId, $config, "thumbnail_deferred"); return $path; } // transform image $image = Asset\Image::getImageTransformInstance(); if (!$image->load($fileSystemPath)) { return "/pimcore/static/img/filetype-not-supported.png"; } $image->setUseContentOptimizedFormat($contentOptimizedFormat); $startTime = StopWatch::microtime_float(); $transformations = $config->getItems(); // check if the original image has an orientation exif flag // if so add a transformation at the beginning that rotates and/or mirrors the image if (function_exists("exif_read_data")) { $exif = @exif_read_data($fileSystemPath); if (is_array($exif)) { if (array_key_exists("Orientation", $exif)) { $orientation = intval($exif["Orientation"]); if ($orientation > 1) { $angleMappings = [2 => 180, 3 => 180, 4 => 180, 5 => 90, 6 => 90, 7 => 90, 8 => 270]; if (array_key_exists($orientation, $angleMappings)) { array_unshift($transformations, ["method" => "rotate", "arguments" => ["angle" => $angleMappings[$orientation]]]); } // values that have to be mirrored, this is not very common, but should be covered anyway $mirrorMappings = [2 => "vertical", 4 => "horizontal", 5 => "vertical", 7 => "horizontal"]; if (array_key_exists($orientation, $mirrorMappings)) { array_unshift($transformations, ["method" => "mirror", "arguments" => ["mode" => $mirrorMappings[$orientation]]]); } } } } } if (is_array($transformations) && count($transformations) > 0) { foreach ($transformations as $transformation) { if (!empty($transformation)) { $arguments = array(); $mapping = self::$argumentMapping[$transformation["method"]]; if (is_array($transformation["arguments"])) { foreach ($transformation["arguments"] as $key => $value) { $position = array_search($key, $mapping); if ($position !== false) { // high res calculations if enabled if (!in_array($transformation["method"], ["cropPercent"]) && in_array($key, array("width", "height", "x", "y"))) { if ($config->getHighResolution() && $config->getHighResolution() > 1) { $value *= $config->getHighResolution(); } } $arguments[$position] = $value; } } } ksort($arguments); call_user_func_array(array($image, $transformation["method"]), $arguments); } } } $image->save($fsPath, $format, $config->getQuality()); if ($contentOptimizedFormat) { $tmpStoreKey = str_replace(PIMCORE_TEMPORARY_DIRECTORY . "/", "", $fsPath); TmpStore::add($tmpStoreKey, "-", "image-optimize-queue"); } clearstatcache(); \Logger::debug("Thumbnail " . $path . " generated in " . (StopWatch::microtime_float() - $startTime) . " seconds"); // set proper permissions @chmod($fsPath, File::getDefaultMode()); // quick bugfix / workaround, it seems that imagemagick / image optimizers creates sometimes empty PNG chunks (total size 33 bytes) // no clue why it does so as this is not continuous reproducible, and this is the only fix we can do for now // if the file is corrupted the file will be created on the fly when requested by the browser (because it's deleted here) if (is_file($fsPath) && filesize($fsPath) < 50) { unlink($fsPath); } return $path; }
/** * @see Object\ClassDefinition\Data::getVersionPreview * @param Asset\Image $data * @return string */ public function getVersionPreview($data) { if ($data && $data->getType() == "asset" && $data->getData() instanceof Asset) { return '<img src="/admin/asset/get-video-thumbnail/id/' . $data->getData()->getId() . '/width/100/height/100/aspectratio/true" />'; } return parent::getVersionPreview($data); }
/** * @see Object\ClassDefinition\Data::getVersionPreview * @param Asset\Image $data * @return string */ public function getVersionPreview($data) { if ($data instanceof Asset\Image) { return '<img src="/admin/asset/get-image-thumbnail/id/' . $data->getId() . '/width/100/height/100/aspectratio/true" />'; } }
/** * @return Asset\Image */ public function getImage() { if (!$this->image) { $this->image = Asset\Image::getById($this->getId()); } return $this->image; }
public function getImageThumbnailAction() { $fileinfo = $this->getParam("fileinfo"); $image = Asset\Image::getById(intval($this->getParam("id"))); $thumbnail = null; if ($this->getParam("thumbnail")) { $thumbnail = $image->getThumbnailConfig($this->getParam("thumbnail")); } if (!$thumbnail) { if ($this->getParam("config")) { $thumbnail = $image->getThumbnailConfig(\Zend_Json::decode($this->getParam("config"))); } else { $thumbnail = $image->getThumbnailConfig($this->getAllParams()); } } else { // no high-res images in admin mode (editmode) // this is mostly because of the document's image editable, which doesn't know anything about the thumbnail // configuration, so the dimensions would be incorrect (double the size) $thumbnail->setHighResolution(1); } $format = strtolower($thumbnail->getFormat()); if ($format == "source" || $format == "print") { $thumbnail->setFormat("PNG"); $format = "png"; } if ($this->getParam("treepreview")) { $thumbnail = Asset\Image\Thumbnail\Config::getPreviewConfig(); } if ($this->getParam("cropPercent")) { $thumbnail->addItemAt(0, "cropPercent", array("width" => $this->getParam("cropWidth"), "height" => $this->getParam("cropHeight"), "y" => $this->getParam("cropTop"), "x" => $this->getParam("cropLeft"))); $hash = md5(Tool\Serialize::serialize($this->getAllParams())); $thumbnail->setName($thumbnail->getName() . "_auto_" . $hash); } if ($this->getParam("download")) { $downloadFilename = str_replace("." . File::getFileExtension($image->getFilename()), "." . $thumbnail->getFormat(), $image->getFilename()); $downloadFilename = strtolower($downloadFilename); header('Content-Disposition: attachment; filename="' . $downloadFilename . '"'); } $thumbnail = $image->getThumbnail($thumbnail); if ($fileinfo) { $this->_helper->json(array("width" => $thumbnail->getWidth(), "height" => $thumbnail->getHeight())); } $thumbnailFile = PIMCORE_DOCUMENT_ROOT . $thumbnail; $fileExtension = File::getFileExtension($thumbnailFile); if (in_array($fileExtension, array("gif", "jpeg", "jpeg", "png", "pjpeg"))) { header("Content-Type: image/" . $fileExtension, true); } else { header("Content-Type: " . $image->getMimetype(), true); } header("Content-Length: " . filesize($thumbnailFile), true); $this->sendThumbnailCacheHeaders(); while (@ob_end_flush()) { } flush(); readfile($thumbnailFile); exit; }
/** See marshal * @param mixed $value * @param Model\Object\AbstractObject $object * @param mixed $params * @return mixed */ public function unmarshal($value, $object = null, $params = []) { $id = $value["id"]; if (intval($id) > 0) { return Asset\Image::getById($id); } }
/** * @param Asset\Image $image * @return Model\Document\Tag\Image */ public function setImage($image) { $this->image = $image; if ($image instanceof Asset) { $this->setId($image->getId()); } return $this; }
public function getImageThumbnailAction() { $fileinfo = $this->getParam("fileinfo"); $image = Asset\Image::getById(intval($this->getParam("id"))); $thumbnail = null; if ($this->getParam("thumbnail")) { $thumbnail = $image->getThumbnailConfig($this->getParam("thumbnail")); } if (!$thumbnail) { if ($this->getParam("config")) { $thumbnail = $image->getThumbnailConfig(\Zend_Json::decode($this->getParam("config"))); } else { $thumbnail = $image->getThumbnailConfig($this->getAllParams()); } } else { // no high-res images in admin mode (editmode) // this is mostly because of the document's image editable, which doesn't know anything about the thumbnail // configuration, so the dimensions would be incorrect (double the size) $thumbnail->setHighResolution(1); } $format = strtolower($thumbnail->getFormat()); if ($format == "source" || $format == "print") { $thumbnail->setFormat("PNG"); } if ($this->getParam("treepreview")) { $thumbnail = Asset\Image\Thumbnail\Config::getPreviewConfig(); } if ($this->getParam("cropPercent")) { $thumbnail->addItemAt(0, "cropPercent", ["width" => $this->getParam("cropWidth"), "height" => $this->getParam("cropHeight"), "y" => $this->getParam("cropTop"), "x" => $this->getParam("cropLeft")]); $hash = md5(Tool\Serialize::serialize($this->getAllParams())); $thumbnail->setName($thumbnail->getName() . "_auto_" . $hash); } $thumbnail = $image->getThumbnail($thumbnail); if ($fileinfo) { $this->_helper->json(["width" => $thumbnail->getWidth(), "height" => $thumbnail->getHeight()]); } $thumbnailFile = $thumbnail->getFileSystemPath(); header("Content-Type: " . $thumbnail->getMimeType(), true); header("Access-Control-Allow-Origin: *"); // for Aviary.Feather (Adobe Creative SDK) header("Content-Length: " . filesize($thumbnailFile), true); $this->sendThumbnailCacheHeaders(); while (@ob_end_flush()) { } flush(); readfile($thumbnailFile); exit; }
/** * @see Object\ClassDefinition\Data::getVersionPreview * @param Asset\Image $data * @param null|Object\AbstractObject $object * @param mixed $params * @return string */ public function getVersionPreview($data, $object = null, $params = []) { if ($data instanceof Object\Data\Hotspotimage && $data->getImage() instanceof Asset\Image) { return '<img src="/admin/asset/get-image-thumbnail/id/' . $data->getImage()->getId() . '/width/100/height/100/aspectratio/true" />'; } }
/** * @param $asset * @param Config $config * @param null $fileSystemPath * @param bool $deferred deferred means that the image will be generated on-the-fly (details see below) * @param bool $returnAbsolutePath * @param bool $generated * @return mixed|string */ public static function process($asset, Config $config, $fileSystemPath = null, $deferred = false, $returnAbsolutePath = false, &$generated = false) { $generated = false; $errorImage = PIMCORE_PATH . "/static6/img/filetype-not-supported.png"; $format = strtolower($config->getFormat()); $contentOptimizedFormat = false; if (!$fileSystemPath && $asset instanceof Asset) { $fileSystemPath = $asset->getFileSystemPath(); } if ($asset instanceof Asset) { $id = $asset->getId(); } else { $id = "dyn~" . crc32($fileSystemPath); } $fileExt = File::getFileExtension(basename($fileSystemPath)); // simple detection for source type if SOURCE is selected if ($format == "source" || empty($format)) { $format = self::getAllowedFormat($fileExt, ["jpeg", "gif", "png"], "png"); $contentOptimizedFormat = true; // format can change depending of the content (alpha-channel, ...) } if ($format == "print") { $format = self::getAllowedFormat($fileExt, ["svg", "jpeg", "png", "tiff"], "png"); if (($format == "tiff" || $format == "svg") && \Pimcore\Tool::isFrontentRequestByAdmin()) { // return a webformat in admin -> tiff cannot be displayed in browser $format = "png"; $deferred = false; // deferred is default, but it's not possible when using isFrontentRequestByAdmin() } elseif ($format == "tiff") { $transformations = $config->getItems(); if (is_array($transformations) && count($transformations) > 0) { foreach ($transformations as $transformation) { if (!empty($transformation)) { if ($transformation["method"] == "tifforiginal") { return self::returnPath($fileSystemPath, $returnAbsolutePath); } } } } } elseif ($format == "svg") { return self::returnPath($fileSystemPath, $returnAbsolutePath); } } elseif ($format == "tiff") { if (\Pimcore\Tool::isFrontentRequestByAdmin()) { // return a webformat in admin -> tiff cannot be displayed in browser $format = "png"; $deferred = false; // deferred is default, but it's not possible when using isFrontentRequestByAdmin() } } $thumbDir = $asset->getImageThumbnailSavePath() . "/thumb__" . $config->getName(); $filename = preg_replace("/\\." . preg_quote(File::getFileExtension($asset->getFilename())) . "/", "", $asset->getFilename()); // add custom suffix if available if ($config->getFilenameSuffix()) { $filename .= "~-~" . $config->getFilenameSuffix(); } // add high-resolution modifier suffix to the filename if ($config->getHighResolution() > 1) { $filename .= "@" . $config->getHighResolution() . "x"; } $fileExtension = $format; if ($format == "original") { $fileExtension = \Pimcore\File::getFileExtension($fileSystemPath); } $filename .= "." . $fileExtension; $fsPath = $thumbDir . "/" . $filename; // deferred means that the image will be generated on-the-fly (when requested by the browser) // the configuration is saved for later use in Pimcore\Controller\Plugin\Thumbnail::routeStartup() // so that it can be used also with dynamic configurations if ($deferred) { // only add the config to the TmpStore if necessary (the config is auto-generated) if (!Config::getByName($config->getName())) { $configId = "thumb_" . $id . "__" . md5(str_replace(PIMCORE_TEMPORARY_DIRECTORY, "", $fsPath)); TmpStore::add($configId, $config, "thumbnail_deferred"); } return self::returnPath($fsPath, $returnAbsolutePath); } // all checks on the file system should be below the deferred part for performance reasons (remote file systems) if (!file_exists($fileSystemPath)) { return self::returnPath($errorImage, $returnAbsolutePath); } if (!is_dir(dirname($fsPath))) { File::mkdir(dirname($fsPath)); } $path = self::returnPath($fsPath, false); // check for existing and still valid thumbnail if (is_file($fsPath) and filemtime($fsPath) >= filemtime($fileSystemPath)) { return self::returnPath($fsPath, $returnAbsolutePath); } // transform image $image = Asset\Image::getImageTransformInstance(); $image->setPreserveColor($config->isPreserveColor()); $image->setPreserveMetaData($config->isPreserveMetaData()); if (!$image->load($fileSystemPath)) { return self::returnPath($errorImage, $returnAbsolutePath); } $image->setUseContentOptimizedFormat($contentOptimizedFormat); $startTime = StopWatch::microtime_float(); $transformations = $config->getItems(); // check if the original image has an orientation exif flag // if so add a transformation at the beginning that rotates and/or mirrors the image if (function_exists("exif_read_data")) { $exif = @exif_read_data($fileSystemPath); if (is_array($exif)) { if (array_key_exists("Orientation", $exif)) { $orientation = intval($exif["Orientation"]); if ($orientation > 1) { $angleMappings = [2 => 180, 3 => 180, 4 => 180, 5 => 90, 6 => 90, 7 => 90, 8 => 270]; if (array_key_exists($orientation, $angleMappings)) { array_unshift($transformations, ["method" => "rotate", "arguments" => ["angle" => $angleMappings[$orientation]]]); } // values that have to be mirrored, this is not very common, but should be covered anyway $mirrorMappings = [2 => "vertical", 4 => "horizontal", 5 => "vertical", 7 => "horizontal"]; if (array_key_exists($orientation, $mirrorMappings)) { array_unshift($transformations, ["method" => "mirror", "arguments" => ["mode" => $mirrorMappings[$orientation]]]); } } } } } if (is_array($transformations) && count($transformations) > 0) { $sourceImageWidth = PHP_INT_MAX; $sourceImageHeight = PHP_INT_MAX; if ($asset instanceof Asset\Image) { $sourceImageWidth = $asset->getWidth(); $sourceImageHeight = $asset->getHeight(); } $highResFactor = $config->getHighResolution(); $calculateMaxFactor = function ($factor, $original, $new) { $newFactor = $factor * $original / $new; if ($newFactor < 1) { // don't go below factor 1 $newFactor = 1; } return $newFactor; }; // sorry for the goto/label - but in this case it makes life really easier and the code more readable prepareTransformations: foreach ($transformations as $transformation) { if (!empty($transformation)) { $arguments = []; $mapping = self::$argumentMapping[$transformation["method"]]; if (is_array($transformation["arguments"])) { foreach ($transformation["arguments"] as $key => $value) { $position = array_search($key, $mapping); if ($position !== false) { // high res calculations if enabled if (!in_array($transformation["method"], ["cropPercent"]) && in_array($key, ["width", "height", "x", "y"])) { if ($highResFactor && $highResFactor > 1) { $value *= $highResFactor; $value = (int) ceil($value); // check if source image is big enough otherwise adjust the high-res factor if (in_array($key, ["width", "x"])) { if ($sourceImageWidth < $value) { $highResFactor = $calculateMaxFactor($highResFactor, $sourceImageWidth, $value); goto prepareTransformations; } } elseif (in_array($key, ["height", "y"])) { if ($sourceImageHeight < $value) { $highResFactor = $calculateMaxFactor($highResFactor, $sourceImageHeight, $value); goto prepareTransformations; } } } } $arguments[$position] = $value; } } } ksort($arguments); if (method_exists($image, $transformation["method"])) { call_user_func_array([$image, $transformation["method"]], $arguments); } } } } $image->save($fsPath, $format, $config->getQuality()); $generated = true; if ($contentOptimizedFormat) { $tmpStoreKey = str_replace(PIMCORE_TEMPORARY_DIRECTORY . "/", "", $fsPath); TmpStore::add($tmpStoreKey, "-", "image-optimize-queue"); } clearstatcache(); Logger::debug("Thumbnail " . $path . " generated in " . (StopWatch::microtime_float() - $startTime) . " seconds"); // set proper permissions @chmod($fsPath, File::getDefaultMode()); // quick bugfix / workaround, it seems that imagemagick / image optimizers creates sometimes empty PNG chunks (total size 33 bytes) // no clue why it does so as this is not continuous reproducible, and this is the only fix we can do for now // if the file is corrupted the file will be created on the fly when requested by the browser (because it's deleted here) if (is_file($fsPath) && filesize($fsPath) < 50) { unlink($fsPath); } return self::returnPath($fsPath, $returnAbsolutePath); }
/** * @param Model\Document\Webservice\Data\Document\Element $wsElement * @param null $idMapper * @throws \Exception */ public function getFromWebserviceImport($wsElement, $idMapper = null) { $data = $wsElement->value; if ($data->id !== null) { $this->alt = $data->alt; $this->id = $data->id; if ($idMapper) { $this->id = $idMapper->getMappedId("asset", $data->id); } if (is_numeric($this->id)) { $this->image = Asset\Image::getById($this->id); if (!$this->image instanceof Asset\Image) { if ($idMapper && $idMapper->ignoreMappingFailures()) { $idMapper->recordMappingFailure("document", $this->getDocumentId(), "asset", $data->id); } else { throw new \Exception("cannot get values from web service import - referenced image with id [ " . $this->id . " ] is unknown"); } } } else { if ($idMapper && $idMapper->ignoreMappingFailures()) { $idMapper->recordMappingFailure("document", $this->getDocumentId(), "asset", $data->id); } else { throw new \Exception("cannot get values from web service import - id is not valid"); } } } }