core/lib/Thelia/Action/BaseCachedFile.php line 279

  1. <?php
  2. /*
  3.  * This file is part of the Thelia package.
  4.  * http://www.thelia.net
  5.  *
  6.  * (c) OpenStudio <info@thelia.net>
  7.  *
  8.  * For the full copyright and license information, please view the LICENSE
  9.  * file that was distributed with this source code.
  10.  */
  11. namespace Thelia\Action;
  12. use Propel\Runtime\Propel;
  13. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  14. use Thelia\Core\Event\CachedFileEvent;
  15. use Thelia\Core\Event\File\FileCreateOrUpdateEvent;
  16. use Thelia\Core\Event\File\FileDeleteEvent;
  17. use Thelia\Core\Event\File\FileToggleVisibilityEvent;
  18. use Thelia\Core\Event\UpdateFilePositionEvent;
  19. use Thelia\Exception\FileException;
  20. use Thelia\Files\FileManager;
  21. use Thelia\Model\ConfigQuery;
  22. use Thelia\Model\Map\ProductImageTableMap;
  23. use Thelia\Tools\URL;
  24. /**
  25.  * Cached file management actions. This class handles file caching in the web space.
  26.  *
  27.  * Basically, files are stored outside the web space (by default in local/media/<dirname>),
  28.  * and cached in the web space (by default in web/local/<dirname>).
  29.  *
  30.  * In the file cache directory, a subdirectory for files categories (eg. product, category, folder, etc.) is
  31.  * automatically created, and the cached file is created here. Plugin may use their own subdirectory as required.
  32.  *
  33.  * A copy (or symbolic link, by default) of the original file is created in the cache.
  34.  *
  35.  * @author Franck Allimant <franck@cqfdev.fr>
  36.  */
  37. abstract class BaseCachedFile extends BaseAction
  38. {
  39.     /**
  40.      * @var FileManager
  41.      */
  42.     protected $fileManager;
  43.     /** @var string|null */
  44.     protected $cdnBaseUrl;
  45.     public function __construct(FileManager $fileManager)
  46.     {
  47.         $this->fileManager $fileManager;
  48.         $this->cdnBaseUrl ConfigQuery::read('cdn.documents-base-url'null);
  49.     }
  50.     /**
  51.      * @return string root of the file cache directory in web space
  52.      */
  53.     abstract protected function getCacheDirFromWebRoot();
  54.     /**
  55.      * @param string $url the fully qualified CDN URL that will be used to create doucments URL
  56.      */
  57.     public function setCdnBaseUrl(string $url): void
  58.     {
  59.         $this->cdnBaseUrl $url;
  60.     }
  61.     /**
  62.      * Clear the file cache. Is a subdirectory is specified, only this directory is cleared.
  63.      * If no directory is specified, the whole cache is cleared.
  64.      * Only files are deleted, directories will remain.
  65.      */
  66.     public function clearCache(CachedFileEvent $event): void
  67.     {
  68.         $path $this->getCachePath($event->getCacheSubdirectory(), false);
  69.         $this->clearDirectory($path);
  70.     }
  71.     /**
  72.      * Recursively clears the specified directory.
  73.      *
  74.      * @param string $path the directory path
  75.      */
  76.     protected function clearDirectory(string $path): void
  77.     {
  78.         $iterator = new \DirectoryIterator($path);
  79.         /** @var \DirectoryIterator $fileinfo */
  80.         foreach ($iterator as $fileinfo) {
  81.             if ($fileinfo->isDot()) {
  82.                 continue;
  83.             }
  84.             if ($fileinfo->isFile() || $fileinfo->isLink()) {
  85.                 @unlink($fileinfo->getPathname());
  86.             } elseif ($fileinfo->isDir()) {
  87.                 $this->clearDirectory($fileinfo->getPathname());
  88.             }
  89.         }
  90.     }
  91.     /**
  92.      * Return the absolute URL to the cached file.
  93.      *
  94.      * @param string $subdir        the subdirectory related to cache base
  95.      * @param string $safe_filename the safe filename, as returned by getCacheFilePath()
  96.      *
  97.      * @return string the absolute URL to the cached file
  98.      */
  99.     protected function getCacheFileURL(string $subdirstring $safe_filename)
  100.     {
  101.         $path $this->getCachePathFromWebRoot($subdir);
  102.         return URL::getInstance()->absoluteUrl(sprintf('%s/%s'$path$safe_filename), nullURL::PATH_TO_FILE$this->cdnBaseUrl);
  103.     }
  104.     /**
  105.      * Return the full path of the cached file.
  106.      *
  107.      * @param string      $subdir            the subdirectory related to cache base
  108.      * @param string      $filename          the filename
  109.      * @param bool        $forceOriginalFile if true, the original file path in the cache dir is returned
  110.      * @param string|null $hashed_options    a hash of transformation options, or null if no transformations have been applied
  111.      *
  112.      * @return string the cache directory path relative to Web Root
  113.      */
  114.     protected function getCacheFilePath(string $subdirstring $filenamebool $forceOriginalFile falsestring $hashed_options null)
  115.     {
  116.         $path $this->getCachePath($subdir);
  117.         $safe_filename preg_replace("[^:alnum:\-\._]"'-'strtolower(basename($filename)));
  118.         // Keep original safe name if no tranformations are applied
  119.         if ($forceOriginalFile || $hashed_options == null) {
  120.             return sprintf('%s/%s'$path$safe_filename);
  121.         }
  122.         return sprintf('%s/%s-%s'$path$hashed_options$safe_filename);
  123.     }
  124.     /**
  125.      * Return the cache directory path relative to Web Root.
  126.      *
  127.      * @param string|null $subdir the subdirectory related to cache base, or null to get the cache directory only
  128.      *
  129.      * @return string the cache directory path relative to Web Root
  130.      */
  131.     protected function getCachePathFromWebRoot(string $subdir null)
  132.     {
  133.         $cache_dir_from_web_root $this->getCacheDirFromWebRoot();
  134.         if ($subdir != null) {
  135.             $safe_subdir basename($subdir);
  136.             $path sprintf('%s/%s'$cache_dir_from_web_root$safe_subdir);
  137.         } else {
  138.             $path $cache_dir_from_web_root;
  139.         }
  140.         // Check if path is valid, e.g. in the cache dir
  141.         return $path;
  142.     }
  143.     /**
  144.      * Return the absolute cache directory path.
  145.      *
  146.      * @param string|null $subdir               the subdirectory related to cache base, or null to get the cache base directory
  147.      * @param bool        $create_if_not_exists create the directory if it is not found
  148.      *
  149.      * @throws \RuntimeException         if cache directory cannot be created
  150.      * @throws \InvalidArgumentException ii path is invalid, e.g. not in the cache dir
  151.      *
  152.      * @return string the absolute cache directory path
  153.      */
  154.     protected function getCachePath(string $subdir nullbool $create_if_not_exists true)
  155.     {
  156.         $cache_base $this->getCachePathFromWebRoot($subdir);
  157.         $web_root rtrim(THELIA_WEB_DIR'/');
  158.         $path sprintf('%s/%s'$web_root$cache_base);
  159.         // Create directory (recursively) if it does not exists.
  160.         if ($create_if_not_exists && !is_dir($path)) {
  161.             if (!@mkdir($path0777true)) {
  162.                 throw new \RuntimeException(sprintf('Failed to create %s file in cache directory'$path));
  163.             }
  164.         }
  165.         // Check if path is valid, e.g. in the cache dir
  166.         $cache_base realpath(sprintf('%s/%s'$web_root$this->getCachePathFromWebRoot()));
  167.         if (strpos(realpath($path), $cache_base) !== 0) {
  168.             throw new \InvalidArgumentException(sprintf('Invalid cache path %s, with subdirectory %s'$path$subdir));
  169.         }
  170.         return $path;
  171.     }
  172.     /**
  173.      * Take care of saving a file in the database and file storage.
  174.      *
  175.      * @param FileCreateOrUpdateEvent $event Image event
  176.      *
  177.      * @throws \Thelia\Exception\FileException|\Exception
  178.      */
  179.     public function saveFile(FileCreateOrUpdateEvent $event): void
  180.     {
  181.         $model $event->getModel();
  182.         $model->setFile(sprintf('tmp/%s'$event->getUploadedFile()->getFilename()));
  183.         $con Propel::getWriteConnection(ProductImageTableMap::DATABASE_NAME);
  184.         $con->beginTransaction();
  185.         try {
  186.             $nbModifiedLines $model->save($con);
  187.             $event->setModel($model);
  188.             if (!$nbModifiedLines) {
  189.                 throw new FileException(
  190.                     sprintf(
  191.                         'File "%s" (type %s) with parent id %s failed to be saved',
  192.                         $event->getParentName(),
  193.                         \get_class($model),
  194.                         $event->getParentId()
  195.                     )
  196.                 );
  197.             }
  198.             $newUploadedFile $this->fileManager->copyUploadedFile($event->getModel(), $event->getUploadedFile());
  199.             $event->setUploadedFile($newUploadedFile);
  200.             $con->commit();
  201.         } catch (\Exception $e) {
  202.             $con->rollBack();
  203.             throw $e;
  204.         }
  205.     }
  206.     /**
  207.      * Take care of updating file in the database and file storage.
  208.      *
  209.      * @param FileCreateOrUpdateEvent $event Image event
  210.      *
  211.      * @throws \Thelia\Exception\FileException
  212.      */
  213.     public function updateFile(FileCreateOrUpdateEvent $event): void
  214.     {
  215.         // Copy and save file
  216.         if ($event->getUploadedFile()) {
  217.             // Remove old picture file from file storage
  218.             $url $event->getModel()->getUploadDir().'/'.$event->getOldModel()->getFile();
  219.             unlink(str_replace('..'''$url));
  220.             $newUploadedFile $this->fileManager->copyUploadedFile($event->getModel(), $event->getUploadedFile());
  221.             $event->setUploadedFile($newUploadedFile);
  222.         }
  223.         // Update image modifications
  224.         $event->getModel()->save();
  225.         $event->setModel($event->getModel());
  226.     }
  227.     /**
  228.      * Deleting file in the database and in storage.
  229.      *
  230.      * @param FileDeleteEvent $event Image event
  231.      */
  232.     public function deleteFile(FileDeleteEvent $event): void
  233.     {
  234.         $this->fileManager->deleteFile($event->getFileToDelete());
  235.     }
  236.     public function updatePosition(UpdateFilePositionEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  237.     {
  238.         $this->genericUpdatePosition($event->getQuery(), $event$dispatcher);
  239.     }
  240.     public function toggleVisibility(FileToggleVisibilityEvent $event$eventNameEventDispatcherInterface $dispatcher): void
  241.     {
  242.         $this->genericToggleVisibility($event->getQuery(), $event$dispatcher);
  243.     }
  244. }