Initial commit
This commit is contained in:
290
core/lib/Thelia/Action/BaseCachedFile.php
Normal file
290
core/lib/Thelia/Action/BaseCachedFile.php
Normal file
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
/*************************************************************************************/
|
||||
/* This file is part of the Thelia package. */
|
||||
/* */
|
||||
/* Copyright (c) OpenStudio */
|
||||
/* email : dev@thelia.net */
|
||||
/* web : http://www.thelia.net */
|
||||
/* */
|
||||
/* For the full copyright and license information, please view the LICENSE.txt */
|
||||
/* file that was distributed with this source code. */
|
||||
/*************************************************************************************/
|
||||
namespace Thelia\Action;
|
||||
|
||||
use Propel\Runtime\Propel;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Thelia\Core\Event\CachedFileEvent;
|
||||
use Thelia\Core\Event\File\FileCreateOrUpdateEvent;
|
||||
use Thelia\Core\Event\File\FileDeleteEvent;
|
||||
use Thelia\Core\Event\File\FileToggleVisibilityEvent;
|
||||
use Thelia\Core\Event\UpdateFilePositionEvent;
|
||||
use Thelia\Exception\FileException;
|
||||
use Thelia\Files\FileManager;
|
||||
use Thelia\Model\ConfigQuery;
|
||||
use Thelia\Model\Map\ProductImageTableMap;
|
||||
use Thelia\Tools\URL;
|
||||
|
||||
/**
|
||||
*
|
||||
* Cached file management actions. This class handles file caching in the web space
|
||||
*
|
||||
* Basically, files are stored outside the web space (by default in local/media/<dirname>),
|
||||
* and cached in the web space (by default in web/local/<dirname>).
|
||||
*
|
||||
* In the file cache directory, a subdirectory for files categories (eg. product, category, folder, etc.) is
|
||||
* automatically created, and the cached file is created here. Plugin may use their own subdirectory as required.
|
||||
*
|
||||
* A copy (or symbolic link, by default) of the original file is created in the cache.
|
||||
*
|
||||
* @package Thelia\Action
|
||||
* @author Franck Allimant <franck@cqfdev.fr>
|
||||
*
|
||||
*/
|
||||
abstract class BaseCachedFile extends BaseAction
|
||||
{
|
||||
/**
|
||||
* @var FileManager
|
||||
*/
|
||||
protected $fileManager;
|
||||
|
||||
/** @var null|string */
|
||||
protected $cdnBaseUrl;
|
||||
|
||||
public function __construct(FileManager $fileManager)
|
||||
{
|
||||
$this->fileManager = $fileManager;
|
||||
|
||||
$this->cdnBaseUrl = ConfigQuery::read('cdn.documents-base-url', null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string root of the file cache directory in web space
|
||||
*/
|
||||
abstract protected function getCacheDirFromWebRoot();
|
||||
|
||||
/**
|
||||
* @param string $url the fully qualified CDN URL that will be used to create doucments URL.
|
||||
*/
|
||||
public function setCdnBaseUrl($url)
|
||||
{
|
||||
$this->cdnBaseUrl = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the file cache. Is a subdirectory is specified, only this directory is cleared.
|
||||
* If no directory is specified, the whole cache is cleared.
|
||||
* Only files are deleted, directories will remain.
|
||||
*
|
||||
* @param CachedFileEvent $event
|
||||
*/
|
||||
public function clearCache(CachedFileEvent $event)
|
||||
{
|
||||
$path = $this->getCachePath($event->getCacheSubdirectory(), false);
|
||||
|
||||
$this->clearDirectory($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively clears the specified directory.
|
||||
*
|
||||
* @param string $path the directory path
|
||||
*/
|
||||
protected function clearDirectory($path)
|
||||
{
|
||||
$iterator = new \DirectoryIterator($path);
|
||||
|
||||
/** @var \DirectoryIterator $fileinfo */
|
||||
foreach ($iterator as $fileinfo) {
|
||||
if ($fileinfo->isDot()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($fileinfo->isFile() || $fileinfo->isLink()) {
|
||||
@unlink($fileinfo->getPathname());
|
||||
} elseif ($fileinfo->isDir()) {
|
||||
$this->clearDirectory($fileinfo->getPathname());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the absolute URL to the cached file
|
||||
*
|
||||
* @param string $subdir the subdirectory related to cache base
|
||||
* @param string $safe_filename the safe filename, as returned by getCacheFilePath()
|
||||
* @return string the absolute URL to the cached file
|
||||
*/
|
||||
protected function getCacheFileURL($subdir, $safe_filename)
|
||||
{
|
||||
$path = $this->getCachePathFromWebRoot($subdir);
|
||||
|
||||
return URL::getInstance()->absoluteUrl(sprintf("%s/%s", $path, $safe_filename), null, URL::PATH_TO_FILE, $this->cdnBaseUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full path of the cached file
|
||||
*
|
||||
* @param string $subdir the subdirectory related to cache base
|
||||
* @param string $filename the filename
|
||||
* @param boolean $forceOriginalFile if true, the original file path in the cache dir is returned.
|
||||
* @param string $hashed_options a hash of transformation options, or null if no transformations have been applied
|
||||
* @return string the cache directory path relative to Web Root
|
||||
*/
|
||||
protected function getCacheFilePath($subdir, $filename, $forceOriginalFile = false, $hashed_options = null)
|
||||
{
|
||||
$path = $this->getCachePath($subdir);
|
||||
|
||||
$safe_filename = preg_replace("[^:alnum:\-\._]", "-", strtolower(basename($filename)));
|
||||
|
||||
// Keep original safe name if no tranformations are applied
|
||||
if ($forceOriginalFile || $hashed_options == null) {
|
||||
return sprintf("%s/%s", $path, $safe_filename);
|
||||
} else {
|
||||
return sprintf("%s/%s-%s", $path, $hashed_options, $safe_filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cache directory path relative to Web Root
|
||||
*
|
||||
* @param string $subdir the subdirectory related to cache base, or null to get the cache directory only.
|
||||
* @return string the cache directory path relative to Web Root
|
||||
*/
|
||||
protected function getCachePathFromWebRoot($subdir = null)
|
||||
{
|
||||
$cache_dir_from_web_root = $this->getCacheDirFromWebRoot();
|
||||
|
||||
if ($subdir != null) {
|
||||
$safe_subdir = basename($subdir);
|
||||
|
||||
$path = sprintf("%s/%s", $cache_dir_from_web_root, $safe_subdir);
|
||||
} else {
|
||||
$path = $cache_dir_from_web_root;
|
||||
}
|
||||
|
||||
// Check if path is valid, e.g. in the cache dir
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the absolute cache directory path
|
||||
*
|
||||
* @param string $subdir the subdirectory related to cache base, or null to get the cache base directory.
|
||||
* @param bool $create_if_not_exists create the directory if it is not found
|
||||
*
|
||||
* @throws \RuntimeException if cache directory cannot be created
|
||||
* @throws \InvalidArgumentException ii path is invalid, e.g. not in the cache dir
|
||||
*
|
||||
* @return string the absolute cache directory path
|
||||
*/
|
||||
protected function getCachePath($subdir = null, $create_if_not_exists = true)
|
||||
{
|
||||
$cache_base = $this->getCachePathFromWebRoot($subdir);
|
||||
|
||||
$web_root = rtrim(THELIA_WEB_DIR, '/');
|
||||
|
||||
$path = sprintf("%s/%s", $web_root, $cache_base);
|
||||
|
||||
// Create directory (recursively) if it does not exists.
|
||||
if ($create_if_not_exists && !is_dir($path)) {
|
||||
if (!@mkdir($path, 0777, true)) {
|
||||
throw new \RuntimeException(sprintf("Failed to create %s file in cache directory", $path));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if path is valid, e.g. in the cache dir
|
||||
$cache_base = realpath(sprintf("%s/%s", $web_root, $this->getCachePathFromWebRoot()));
|
||||
|
||||
if (strpos(realpath($path), $cache_base) !== 0) {
|
||||
throw new \InvalidArgumentException(sprintf("Invalid cache path %s, with subdirectory %s", $path, $subdir));
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take care of saving a file in the database and file storage
|
||||
*
|
||||
* @param FileCreateOrUpdateEvent $event Image event
|
||||
*
|
||||
* @throws \Thelia\Exception\FileException|\Exception
|
||||
*
|
||||
*/
|
||||
public function saveFile(FileCreateOrUpdateEvent $event)
|
||||
{
|
||||
$model = $event->getModel();
|
||||
$model->setFile(sprintf("tmp/%s", $event->getUploadedFile()->getFilename()));
|
||||
$con = Propel::getWriteConnection(ProductImageTableMap::DATABASE_NAME);
|
||||
$con->beginTransaction();
|
||||
|
||||
try {
|
||||
$nbModifiedLines = $model->save($con);
|
||||
$event->setModel($model);
|
||||
|
||||
if (!$nbModifiedLines) {
|
||||
throw new FileException(
|
||||
sprintf(
|
||||
'File "%s" (type %s) with parent id %s failed to be saved',
|
||||
$event->getParentName(),
|
||||
\get_class($model),
|
||||
$event->getParentId()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$newUploadedFile = $this->fileManager->copyUploadedFile($event->getModel(), $event->getUploadedFile());
|
||||
|
||||
$event->setUploadedFile($newUploadedFile);
|
||||
$con->commit();
|
||||
} catch (\Exception $e) {
|
||||
$con->rollBack();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take care of updating file in the database and file storage
|
||||
*
|
||||
* @param FileCreateOrUpdateEvent $event Image event
|
||||
*
|
||||
* @throws \Thelia\Exception\FileException
|
||||
*/
|
||||
public function updateFile(FileCreateOrUpdateEvent $event)
|
||||
{
|
||||
// Copy and save file
|
||||
if ($event->getUploadedFile()) {
|
||||
// Remove old picture file from file storage
|
||||
$url = $event->getModel()->getUploadDir() . '/' . $event->getOldModel()->getFile();
|
||||
unlink(str_replace('..', '', $url));
|
||||
|
||||
$newUploadedFile = $this->fileManager->copyUploadedFile($event->getModel(), $event->getUploadedFile());
|
||||
$event->setUploadedFile($newUploadedFile);
|
||||
}
|
||||
|
||||
// Update image modifications
|
||||
$event->getModel()->save();
|
||||
|
||||
$event->setModel($event->getModel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deleting file in the database and in storage
|
||||
*
|
||||
* @param FileDeleteEvent $event Image event
|
||||
*/
|
||||
public function deleteFile(FileDeleteEvent $event)
|
||||
{
|
||||
$this->fileManager->deleteFile($event->getFileToDelete());
|
||||
}
|
||||
|
||||
public function updatePosition(UpdateFilePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
$this->genericUpdatePosition($event->getQuery(), $event, $dispatcher);
|
||||
}
|
||||
|
||||
public function toggleVisibility(FileToggleVisibilityEvent $event, $eventName, EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
$this->genericToggleVisibility($event->getQuery(), $event, $dispatcher);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user