/** * returns the path to a temp file * * @return string */ public function getTemporaryFile($fullPath = false) { $destinationPath = PIMCORE_TEMPORARY_DIRECTORY . "/asset-temporary/asset_" . $this->getId() . "_" . md5(microtime()) . "__" . $this->getFilename(); if (!is_dir(dirname($destinationPath))) { File::mkdir(dirname($destinationPath)); } $src = $this->getStream(); $dest = fopen($destinationPath, "w+"); stream_copy_to_stream($src, $dest); fclose($dest); @chmod($destinationPath, File::getDefaultMode()); if ($fullPath) { return $destinationPath; } return str_replace(PIMCORE_DOCUMENT_ROOT, "", $destinationPath); }
/** * @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; }
/** * returns the path to a temp file * @return string */ public function getTemporaryFile() { $destinationPath = PIMCORE_SYSTEM_TEMP_DIRECTORY . "/asset-temporary/asset_" . $this->getId() . "_" . md5(microtime()) . "__" . $this->getFilename(); if (!is_dir(dirname($destinationPath))) { File::mkdir(dirname($destinationPath)); } $src = $this->getStream(); $dest = fopen($destinationPath, "w+", false, File::getContext()); stream_copy_to_stream($src, $dest); fclose($dest); @chmod($destinationPath, File::getDefaultMode()); return $destinationPath; }
/** * @param User $user * @return void */ public function save($user = null) { if ($this->getElement() instanceof Element\ElementInterface) { $this->setType(Element\Service::getElementType($this->getElement())); } $this->setSubtype($this->getElement()->getType()); $this->setPath($this->getElement()->getFullPath()); $this->setDate(time()); $this->loadChilds($this->getElement()); if ($user instanceof Model\User) { $this->setDeletedby($user->getName()); } // serialize data Element\Service::loadAllFields($this->element); $this->element->_fulldump = true; $data = Serialize::serialize($this->getElement()); $this->getResource()->save(); if (!is_dir(PIMCORE_RECYCLEBIN_DIRECTORY)) { File::mkdir(PIMCORE_RECYCLEBIN_DIRECTORY); } File::put($this->getStoreageFile(), $data); $saveBinaryData = function ($element, $rec, $scope) { // assets are kina special because they can contain massive amount of binary data which isn't serialized, we create separate files for them if ($element instanceof Asset) { if ($element->getType() != "folder") { $handle = fopen($scope->getStorageFileBinary($element), "w+"); $src = $element->getStream(); stream_copy_to_stream($src, $handle); fclose($handle); } $children = $element->getChilds(); foreach ($children as $child) { $rec($child, $rec, $scope); } } }; $saveBinaryData($this->getElement(), $saveBinaryData, $this); @chmod($this->getStoreageFile(), File::getDefaultMode()); }
/** * */ public static function processOptimizeQueue() { $ids = TmpStore::getIdsByTag("image-optimize-queue"); // id = path of image relative to PIMCORE_TEMPORARY_DIRECTORY foreach ($ids as $id) { $file = PIMCORE_TEMPORARY_DIRECTORY . "/" . $id; if (file_exists($file)) { $originalFilesize = filesize($file); \Pimcore\Image\Optimizer::optimize($file); @chmod($file, File::getDefaultMode()); \Logger::debug("Optimized image: " . $file . " saved " . formatBytes($originalFilesize - filesize($file))); } else { \Logger::debug("Skip optimizing of " . $file . " because it doesn't exist anymore"); } TmpStore::delete($id); } }
/** * @param $processId */ public static function execute($processId) { $instance = new self(); $instance->setProcessId($processId); $instanceItem = TmpStore::get($instance->getJobStoreId($processId)); $instance = $instanceItem->getData(); $formats = []; $conversionStatus = "finished"; // check if there is already a transcoding process running, wait if so ... Model\Tool\Lock::acquire("video-transcoding", 7200, 10); // expires after 2 hrs, refreshes every 10 secs $asset = Model\Asset::getById($instance->getAssetId()); // start converting foreach ($instance->queue as $converter) { try { Logger::info("start video " . $converter->getFormat() . " to " . $converter->getDestinationFile()); $success = $converter->save(); Logger::info("finished video " . $converter->getFormat() . " to " . $converter->getDestinationFile()); File::rename($converter->getDestinationFile(), $converter->getStorageFile()); // set proper permissions @chmod($converter->getStorageFile(), File::getDefaultMode()); if ($success) { $formats[$converter->getFormat()] = str_replace($asset->getVideoThumbnailSavePath(), "", $converter->getStorageFile()); } else { $conversionStatus = "error"; } $converter->destroy(); } catch (\Exception $e) { Logger::error($e); } } Model\Tool\Lock::release("video-transcoding"); if ($asset) { $customSetting = $asset->getCustomSetting("thumbnails"); $customSetting = is_array($customSetting) ? $customSetting : []; if (array_key_exists($instance->getConfig()->getName(), $customSetting) && array_key_exists("formats", $customSetting[$instance->getConfig()->getName()]) && is_array($customSetting[$instance->getConfig()->getName()]["formats"])) { $formats = array_merge($customSetting[$instance->getConfig()->getName()]["formats"], $formats); } $customSetting[$instance->getConfig()->getName()] = ["status" => $conversionStatus, "formats" => $formats]; $asset->setCustomSetting("thumbnails", $customSetting); $asset->save(); } TmpStore::delete($instance->getJobStoreId()); }
/** * @param $path */ public function setImage($path) { $userImageDir = PIMCORE_WEBSITE_VAR . "/user-image"; if (!is_dir($userImageDir)) { File::mkdir($userImageDir); } $destFile = $userImageDir . "/user-" . $this->getId() . ".png"; $thumb = PIMCORE_SYSTEM_TEMP_DIRECTORY . "/user-thumbnail-" . $this->getId() . ".png"; @unlink($destFile); @unlink($thumb); copy($path, $destFile); @chmod($destFile, File::getDefaultMode()); }
/** * @param $processId */ public static function execute($processId) { $instance = new self(); $instance->setProcessId($processId); $instanceItem = TmpStore::get($instance->getJobStoreId($processId)); $instance = $instanceItem->getData(); $formats = array(); $overallStatus = array(); $conversionStatus = "finished"; // set overall status for all formats to 0 foreach ($instance->queue as $converter) { $overallStatus[$converter->getFormat()] = 0; } // check if there is already a transcoding process running, wait if so ... Model\Tool\Lock::acquire("video-transcoding", 7200, 10); // expires after 2 hrs, refreshes every 10 secs // start converting foreach ($instance->queue as $converter) { try { \Logger::info("start video " . $converter->getFormat() . " to " . $converter->getDestinationFile()); $converter->save(); while (!$converter->isFinished()) { sleep(5); $overallStatus[$converter->getFormat()] = $converter->getConversionStatus(); $a = 0; foreach ($overallStatus as $f => $s) { $a += $s; } $a = $a / count($overallStatus); $instance->setStatus($a); $instance->save(); } \Logger::info("finished video " . $converter->getFormat() . " to " . $converter->getDestinationFile()); // set proper permissions @chmod($converter->getDestinationFile(), File::getDefaultMode()); if ($converter->getConversionStatus() !== "error") { $formats[$converter->getFormat()] = str_replace(PIMCORE_DOCUMENT_ROOT, "", $converter->getDestinationFile()); } else { $conversionStatus = "error"; } $converter->destroy(); } catch (\Exception $e) { \Logger::error($e); } } Model\Tool\Lock::release("video-transcoding"); $asset = Model\Asset::getById($instance->getAssetId()); if ($asset) { $customSetting = $asset->getCustomSetting("thumbnails"); $customSetting = is_array($customSetting) ? $customSetting : array(); if (array_key_exists($instance->getConfig()->getName(), $customSetting) && array_key_exists("formats", $customSetting[$instance->getConfig()->getName()]) && is_array($customSetting[$instance->getConfig()->getName()]["formats"])) { $formats = array_merge($customSetting[$instance->getConfig()->getName()]["formats"], $formats); } $customSetting[$instance->getConfig()->getName()] = array("status" => $conversionStatus, "formats" => $formats); $asset->setCustomSetting("thumbnails", $customSetting); $asset->save(); } TmpStore::delete($instance->getJobStoreId()); }
/** * @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); }