diff --git a/core/lib/Thelia/Action/BaseCachedFile.php b/core/lib/Thelia/Action/BaseCachedFile.php new file mode 100644 index 000000000..b66496e07 --- /dev/null +++ b/core/lib/Thelia/Action/BaseCachedFile.php @@ -0,0 +1,178 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Action; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +use Thelia\Core\Event\CachedFileEvent; +use Thelia\Model\ConfigQuery; +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/), + * and cached in the web space (by default in web/local/). + * + * 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 + * + */ +abstract class BaseCachedFile extends BaseAction +{ + /** + * @return string root of the file cache directory in web space + */ + protected abstract function getCacheDirFromWebRoot(); + + /** + * 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); + + 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 $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); + } + + /** + * Return the full path of the cached file + * + * @param string $subdir the subdirectory related to cache base + * @param string $filename the filename + * @param string $hashed_options a hash of transformation options, or null if no transformations have been applied + * @param boolean $forceOriginalDocument if true, the original file path in the cache dir is returned. + * @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. + * @throws \RuntimeException if cache directory cannot be created + * @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/%s file in cache directory", $cache_base)); + } + } + + // 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; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Action/Document.php b/core/lib/Thelia/Action/Document.php new file mode 100644 index 000000000..86fc51ac5 --- /dev/null +++ b/core/lib/Thelia/Action/Document.php @@ -0,0 +1,139 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Action; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +use Thelia\Core\Event\DocumentEvent; +use Thelia\Model\ConfigQuery; +use Thelia\Tools\URL; + +use Imagine\Document\ImagineInterface; +use Imagine\Document\DocumentInterface; +use Imagine\Document\Box; +use Imagine\Document\Color; +use Imagine\Document\Point; +use Thelia\Exception\DocumentException; +use Thelia\Core\Event\TheliaEvents; + +/** + * + * Document management actions. This class handles document processing an caching. + * + * Basically, documents are stored outside the web space (by default in local/media/documents), + * and cached in the web space (by default in web/local/documents). + * + * In the documents caches directory, a subdirectory for documents categories (eg. product, category, folder, etc.) is + * automatically created, and the cached document is created here. Plugin may use their own subdirectory as required. + * + * The cached document name contains a hash of the processing options, and the original (normalized) name of the document. + * + * A copy (or symbolic link, by default) of the original document is always created in the cache, so that the full + * resolution document is always available. + * + * Various document processing options are available : + * + * - resizing, with border, crop, or by keeping document aspect ratio + * - rotation, in degrees, positive or negative + * - background color, applyed to empty background when creating borders or rotating + * - effects. The effects are applied in the specified order. The following effects are available: + * - gamma:value : change the document Gamma to the specified value. Example: gamma:0.7 + * - grayscale or greyscale: switch document to grayscale + * - colorize:color : apply a color mask to the document. Exemple: colorize:#ff2244 + * - negative : transform the document in its negative equivalent + * - vflip or vertical_flip : vertical flip + * - hflip or horizontal_flip : horizontal flip + * + * If a problem occurs, an DocumentException may be thrown. + * + * @package Thelia\Action + * @author Franck Allimant + * + */ +class Document extends BaseCachedFile implements EventSubscriberInterface +{ + /** + * @return string root of the document cache directory in web space + */ + protected function getCacheDirFromWebRoot() { + return ConfigQuery::read('document_cache_dir_from_web_root', 'cache'); + } + + /** + * Process document and write the result in the document cache. + * + * When the original document is required, create either a symbolic link with the + * original document in the cache dir, or copy it in the cache dir if it's not already done. + * + * This method updates the cache_file_path and file_url attributes of the event + * + * @param DocumentEvent $event + * @throws \InvalidArgumentException, DocumentException + */ + public function processDocument(DocumentEvent $event) + { + $subdir = $event->getCacheSubdirectory(); + $source_file = $event->getSourceFilepath(); + + if (null == $subdir || null == $source_file) { + throw new \InvalidArgumentException("Cache sub-directory and source file path cannot be null"); + } + + $originalDocumentPathInCache = $this->getCacheFilePath($subdir, $source_file, true); + + if (! file_exists($originalDocumentPathInCache)) { + + if (! file_exists($source_file)) { + throw new DocumentException(sprintf("Source document file %s does not exists.", $source_file)); + } + + $mode = ConfigQuery::read('original_document_delivery_mode', 'symlink'); + + if ($mode == 'symlink') { + if (false == symlink($source_file, $originalDocumentPathInCache)) { + throw new DocumentException(sprintf("Failed to create symbolic link for %s in %s document cache directory", basename($source_file), $subdir)); + } + } else {// mode = 'copy' + if (false == @copy($source_file, $originalDocumentPathInCache)) { + throw new DocumentException(sprintf("Failed to copy %s in %s document cache directory", basename($source_file), $subdir)); + } + } + } + + // Compute the document URL + $document_url = $this->getCacheFileURL($subdir, basename($originalDocumentPathInCache)); + + // Update the event with file path and file URL + $event->setDocumentPath($originalDocumentPathInCache); + $event->setDocumentUrl(URL::getInstance()->absoluteUrl($document_url, null, URL::PATH_TO_FILE)); + } + + public static function getSubscribedEvents() + { + return array( + TheliaEvents::DOCUMENT_PROCESS => array("processDocument", 128), + TheliaEvents::DOCUMENT_CLEAR_CACHE => array("clearCache", 128), + ); + } +} diff --git a/core/lib/Thelia/Action/Image.php b/core/lib/Thelia/Action/Image.php index 66baffdbb..4660f93b8 100755 --- a/core/lib/Thelia/Action/Image.php +++ b/core/lib/Thelia/Action/Image.php @@ -71,7 +71,7 @@ use Thelia\Core\Event\TheliaEvents; * @author Franck Allimant * */ -class Image extends BaseAction implements EventSubscriberInterface +class Image extends BaseCachedFile implements EventSubscriberInterface { // Resize mode constants const EXACT_RATIO_WITH_BORDERS = 1; @@ -79,38 +79,10 @@ class Image extends BaseAction implements EventSubscriberInterface const KEEP_IMAGE_RATIO = 3; /** - * Clear the image 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 ImageEvent $event + * @return string root of the image cache directory in web space */ - public function clearCache(ImageEvent $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); - - foreach ($iterator as $fileinfo) { - - if ($fileinfo->isDot()) continue; - - if ($fileinfo->isFile() || $fileinfo->isLink()) { - @unlink($fileinfo->getPathname()); - } elseif ($fileinfo->isDir()) { - $this->clearDirectory($fileinfo->getPathname()); - } - } + protected function getCacheDirFromWebRoot() { + return ConfigQuery::read('image_cache_dir_from_web_root', 'cache'); } /** @@ -138,9 +110,9 @@ class Image extends BaseAction implements EventSubscriberInterface // echo basename($source_file).": "; // Find cached file path - $cacheFilePath = $this->getCacheFilePath($subdir, $source_file, $event); + $cacheFilePath = $this->getCacheFilePath($subdir, $source_file, $event->isOriginalImage(), $event->getOptionsHash()); - $originalImagePathInCache = $this->getCacheFilePath($subdir, $source_file, $event, true); + $originalImagePathInCache = $this->getCacheFilePath($subdir, $source_file, true); if (! file_exists($cacheFilePath)) { @@ -359,94 +331,6 @@ class Image extends BaseAction implements EventSubscriberInterface return $image; } - /** - * Return the absolute URL to the cached image - * - * @param string $subdir the subdirectory related to cache base - * @param string $filename the safe filename, as returned by getCacheFilePath() - * @return string the absolute URL to the cached image - */ - 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); - } - - /** - * Return the full path of the cached file - * - * @param string $subdir the subdirectory related to cache base - * @param string $filename the filename - * @param boolean $forceOriginalImage if true, the origiunal image path in the cache dir is returned. - * @return string the cache directory path relative to Web Root - */ - protected function getCacheFilePath($subdir, $filename, ImageEvent $event, $forceOriginalImage = false) - { - $path = $this->getCachePath($subdir); - - $safe_filename = preg_replace("[^:alnum:\-\._]", "-", strtolower(basename($filename))); - - // Keep original safe name if no tranformations are applied - if ($forceOriginalImage || $event->isOriginalImage()) - return sprintf("%s/%s", $path, $safe_filename); - else - return sprintf("%s/%s-%s", $path, $event->getOptionsHash(), $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 = ConfigQuery::read('image_cache_dir_from_web_root', 'cache'); - - 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. - * @throws \RuntimeException if cache directory cannot be created - * @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 ImageException(sprintf("Failed to create %s/%s image cache directory", $cache_base)); - } - } - - // 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; - } - /** * Create a new Imagine object using current driver configuration * diff --git a/core/lib/Thelia/Core/Event/CachedFileEvent.php b/core/lib/Thelia/Core/Event/CachedFileEvent.php new file mode 100644 index 000000000..8f3a026d9 --- /dev/null +++ b/core/lib/Thelia/Core/Event/CachedFileEvent.php @@ -0,0 +1,94 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; + +class CachedFileEvent extends ActionEvent +{ + /** + * @var string The complete file name (with path) of the source file + */ + protected $source_filepath = null; + /** + * @var string The target subdirectory in the cache + */ + protected $cache_subdirectory = null; + + /** + * @var string The absolute URL of the cached file (in the web space) + */ + protected $file_url = null; + + /** + * @var string The absolute path of the cached file + */ + protected $cache_filepath = null; + + public function getFileUrl() + { + return $this->file_url; + } + + public function setFileUrl($file_url) + { + $this->file_url = $file_url; + + return $this; + } + + public function getCacheFilepath() + { + return $this->cache_filepath; + } + + public function setCacheFilepath($cache_filepath) + { + $this->cache_filepath = $cache_filepath; + + return $this; + } + + public function getSourceFilepath() + { + return $this->source_filepath; + } + + public function setSourceFilepath($source_filepath) + { + $this->source_filepath = $source_filepath; + + return $this; + } + + public function getCacheSubdirectory() + { + return $this->cache_subdirectory; + } + + public function setCacheSubdirectory($cache_subdirectory) + { + $this->cache_subdirectory = $cache_subdirectory; + + return $this; + } +} diff --git a/core/lib/Thelia/Core/Event/DocumentEvent.php b/core/lib/Thelia/Core/Event/DocumentEvent.php new file mode 100644 index 000000000..f58d51083 --- /dev/null +++ b/core/lib/Thelia/Core/Event/DocumentEvent.php @@ -0,0 +1,67 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; + +class DocumentEvent extends CachedFileEvent +{ + protected $document_path; + protected $document_url; + + /** + * @return the document file path + */ + public function getDocumentPath() + { + return $this->document_path; + } + + /** + * @param string $document_path the document file path + */ + public function setDocumentPath($document_path) + { + $this->document_path = $document_path; + + return $this; + } + + /** + * @return the document URL + */ + public function getDocumentUrl() + { + return $this->document_url; + } + + /** + * @param string $document_url the document URL + */ + public function setDocumentUrl($document_url) + { + $this->document_url = $document_url; + + return $this; + } + +} diff --git a/core/lib/Thelia/Core/Event/ImageEvent.php b/core/lib/Thelia/Core/Event/ImageEvent.php index 5ea1581cd..462fa012d 100755 --- a/core/lib/Thelia/Core/Event/ImageEvent.php +++ b/core/lib/Thelia/Core/Event/ImageEvent.php @@ -23,22 +23,8 @@ namespace Thelia\Core\Event; -class ImageEvent extends ActionEvent +class ImageEvent extends CachedFileEvent { - /** - * @var string The complete file name (with path) of the source image - */ - protected $source_filepath = null; - /** - * @var string The target subdirectory in the image cache - */ - protected $cache_subdirectory = null; - - /** - * @var string The absolute URL of the cached image (in the web space) - */ - protected $file_url = null; - /** * @var string The absolute path of the cached image file */ @@ -121,6 +107,8 @@ class ImageEvent extends ActionEvent public function setCategory($category) { $this->category = $category; + + return $this; } public function getWidth() @@ -131,6 +119,8 @@ class ImageEvent extends ActionEvent public function setWidth($width) { $this->width = $width; + + return $this; } public function getHeight() @@ -141,6 +131,8 @@ class ImageEvent extends ActionEvent public function setHeight($height) { $this->height = $height; + + return $this; } public function getResizeMode() @@ -151,6 +143,8 @@ class ImageEvent extends ActionEvent public function setResizeMode($resize_mode) { $this->resize_mode = $resize_mode; + + return $this; } public function getBackgroundColor() @@ -161,6 +155,8 @@ class ImageEvent extends ActionEvent public function setBackgroundColor($background_color) { $this->background_color = $background_color; + + return $this; } public function getEffects() @@ -171,6 +167,8 @@ class ImageEvent extends ActionEvent public function setEffects(array $effects) { $this->effects = $effects; + + return $this; } public function getRotation() @@ -181,46 +179,8 @@ class ImageEvent extends ActionEvent public function setRotation($rotation) { $this->rotation = $rotation; - } - public function getFileUrl() - { - return $this->file_url; - } - - public function setFileUrl($file_url) - { - $this->file_url = $file_url; - } - - public function getCacheFilepath() - { - return $this->cache_filepath; - } - - public function setCacheFilepath($cache_filepath) - { - $this->cache_filepath = $cache_filepath; - } - - public function getSourceFilepath() - { - return $this->source_filepath; - } - - public function setSourceFilepath($source_filepath) - { - $this->source_filepath = $source_filepath; - } - - public function getCacheSubdirectory() - { - return $this->cache_subdirectory; - } - - public function setCacheSubdirectory($cache_subdirectory) - { - $this->cache_subdirectory = $cache_subdirectory; + return $this; } public function getQuality() @@ -231,6 +191,8 @@ class ImageEvent extends ActionEvent public function setQuality($quality) { $this->quality = $quality; + + return $this; } public function getOriginalFileUrl() @@ -241,6 +203,8 @@ class ImageEvent extends ActionEvent public function setOriginalFileUrl($original_file_url) { $this->original_file_url = $original_file_url; + + return $this; } public function getCacheOriginalFilepath() @@ -251,5 +215,7 @@ class ImageEvent extends ActionEvent public function setCacheOriginalFilepath($cache_original_filepath) { $this->cache_original_filepath = $cache_original_filepath; + + return $this; } } diff --git a/core/lib/Thelia/Core/Event/TheliaEvents.php b/core/lib/Thelia/Core/Event/TheliaEvents.php index ffa87f11c..1fc91dacb 100755 --- a/core/lib/Thelia/Core/Event/TheliaEvents.php +++ b/core/lib/Thelia/Core/Event/TheliaEvents.php @@ -194,6 +194,11 @@ final class TheliaEvents */ const IMAGE_PROCESS = "action.processImage"; + /** + * Sent on document processing + */ + const DOCUMENT_PROCESS = "action.processDocument"; + /** * Sent on cimage cache clear request */ diff --git a/core/lib/Thelia/Core/Template/Loop/Document.php b/core/lib/Thelia/Core/Template/Loop/Document.php new file mode 100644 index 000000000..0e7e979c9 --- /dev/null +++ b/core/lib/Thelia/Core/Template/Loop/Document.php @@ -0,0 +1,277 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Template\Loop; +use Thelia\Core\Template\Element\BaseI18nLoop; +use Thelia\Core\Template\Loop\Argument\Argument; +use Thelia\Core\Event\DocumentEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\Template\Loop\Argument\ArgumentCollection; +use Thelia\Type\TypeCollection; +use Thelia\Type\EnumListType; +use Propel\Runtime\ActiveQuery\Criteria; +use Thelia\Model\ConfigQuery; +use Thelia\Core\Template\Element\LoopResultRow; +use Thelia\Core\Template\Element\LoopResult; +use Thelia\Type\EnumType; +use Thelia\Log\Tlog; + +/** + * The document loop + * + * @author Franck Allimant + */ +class Document extends BaseI18nLoop +{ + public $timestampable = true; + + /** + * @var array Possible document sources + */ + protected $possible_sources = array('category', 'product', 'folder', 'content'); + + /** + * @return \Thelia\Core\Template\Loop\Argument\ArgumentCollection + */ + protected function getArgDefinitions() + { + $collection = new ArgumentCollection( + + Argument::createIntListTypeArgument('id'), + Argument::createIntListTypeArgument('exclude'), + new Argument( + 'order', + new TypeCollection( + new EnumListType(array('alpha', 'alpha-reverse', 'manual', 'manual-reverse', 'random')) + ), + 'manual' + ), + Argument::createIntTypeArgument('lang'), + + Argument::createIntTypeArgument('category'), + Argument::createIntTypeArgument('product'), + Argument::createIntTypeArgument('folder'), + Argument::createIntTypeArgument('content'), + + new Argument( + 'source', + new TypeCollection( + new EnumType($this->possible_sources) + ) + ), + Argument::createIntTypeArgument('source_id') + ); + + // Add possible document sources + foreach ($this->possible_sources as $source) { + $collection->addArgument(Argument::createIntTypeArgument($source)); + } + + return $collection; + } + + /** + * Dynamically create the search query, and set the proper filter and order + * + * @param string $source a valid source identifier (@see $possible_sources) + * @param int $object_id the source object ID + * @return ModelCriteria the propel Query object + */ + protected function createSearchQuery($source, $object_id) + { + $object = ucfirst($source); + + $queryClass = sprintf("\Thelia\Model\%sDocumentQuery", $object); + $filterMethod = sprintf("filterBy%sId", $object); + + // xxxDocumentQuery::create() + $method = new \ReflectionMethod($queryClass, 'create'); + $search = $method->invoke(null); // Static ! + + // $query->filterByXXX(id) + if (! is_null($object_id)) { + $method = new \ReflectionMethod($queryClass, $filterMethod); + $method->invoke($search, $object_id); + } + + $orders = $this->getOrder(); + + // Results ordering + foreach ($orders as $order) { + switch ($order) { + case "alpha": + $search->addAscendingOrderByColumn('i18n_TITLE'); + break; + case "alpha-reverse": + $search->addDescendingOrderByColumn('i18n_TITLE'); + break; + case "manual-reverse": + $search->orderByPosition(Criteria::DESC); + break; + case "manual": + $search->orderByPosition(Criteria::ASC); + break; + case "random": + $search->clearOrderByColumns(); + $search->addAscendingOrderByColumn('RAND()'); + break(2); + break; + } + } + + return $search; + } + + /** + * Dynamically create the search query, and set the proper filter and order + * + * @param string $object_type (returned) the a valid source identifier (@see $possible_sources) + * @param string $object_id (returned) the ID of the source object + * @return ModelCriteria the propel Query object + */ + protected function getSearchQuery(&$object_type, &$object_id) + { + $search = null; + + // Check form source="product" source_id="123" style arguments + $source = $this->getSource(); + + if (! is_null($source)) { + + $source_id = $this->getSourceId(); + $id = $this->getId(); + + // echo "source = ".$this->getSource().", id=".$source_id." - ".$this->getArg('source_id')->getValue()."
"; + + if (is_null($source_id) && is_null($id)) { + throw new \InvalidArgumentException("If 'source' argument is specified, 'id' or 'source_id' argument should be specified"); + } + + $search = $this->createSearchQuery($source, $source_id); + + $object_type = $source; + $object_id = $source_id; + } else { + // Check for product="id" folder="id", etc. style arguments + foreach ($this->possible_sources as $source) { + + $argValue = intval($this->getArgValue($source)); + + if ($argValue > 0) { + + $search = $this->createSearchQuery($source, $argValue); + + $object_type = $source; + $object_id = $argValue; + + break; + } + } + } + + if ($search == null) + throw new \InvalidArgumentException(sprintf("Unable to find document source. Valid sources are %s", implode(',', $this->possible_sources))); + + return $search; + } + + /** + * @param unknown $pagination + */ + public function exec(&$pagination) + { + // Select the proper query to use, and get the object type + $object_type = $object_id = null; + + $search = $this->getSearchQuery($object_type, $object_id); + + /* manage translations */ + $locale = $this->configureI18nProcessing($search); + + $id = $this->getId(); + + if (! is_null($id)) { + $search->filterById($id, Criteria::IN); + } + + $exclude = $this->getExclude(); + if (!is_null($exclude)) + $search->filterById($exclude, Criteria::NOT_IN); + + // Create document processing event + $event = new DocumentEvent($this->request); + + // echo "sql=".$search->toString(); + + $results = $this->search($search, $pagination); + + $loopResult = new LoopResult($results); + + foreach ($results as $result) { + + // Create document processing event + $event = new DocumentEvent($this->request); + + // Put source document file path + $source_filepath = sprintf("%s%s/%s/%s", + THELIA_ROOT, + ConfigQuery::read('documents_library_path', 'local/media/documents'), + $object_type, + $result->getFile() + ); + + $event->setSourceFilepath($source_filepath); + $event->setCacheSubdirectory($object_type); + + try { + // Dispatch document processing event + $this->dispatcher->dispatch(TheliaEvents::DOCUMENT_PROCESS, $event); + + $loopResultRow = new LoopResultRow($loopResult, $result, $this->versionable, $this->timestampable, $this->countable); + + $loopResultRow + ->set("ID" , $result->getId()) + ->set("LOCALE" ,$locale) + ->set("DOCUMENT_URL" , $event->getFileUrl()) + ->set("DOCUMENT_PATH" , $event->getCacheFilepath()) + ->set("ORIGINAL_DOCUMENT_PATH", $source_filepath) + ->set("TITLE" , $result->getVirtualColumn('i18n_TITLE')) + ->set("CHAPO" , $result->getVirtualColumn('i18n_CHAPO')) + ->set("DESCRIPTION" , $result->getVirtualColumn('i18n_DESCRIPTION')) + ->set("POSTSCRIPTUM" , $result->getVirtualColumn('i18n_POSTSCRIPTUM')) + ->set("POSITION" , $result->getPosition()) + ->set("OBJECT_TYPE" , $object_type) + ->set("OBJECT_ID" , $object_id) + ; + + $loopResult->addRow($loopResultRow); + } + catch (\Exception $ex) { + // Ignore the result and log an error + Tlog::getInstance()->addError("Failed to process document in document loop: ", $this->args); + } + } + + return $loopResult; + } +} diff --git a/core/lib/Thelia/Core/Template/Loop/Image.php b/core/lib/Thelia/Core/Template/Loop/Image.php index c7714731b..c85d41fc7 100755 --- a/core/lib/Thelia/Core/Template/Loop/Image.php +++ b/core/lib/Thelia/Core/Template/Loop/Image.php @@ -123,8 +123,10 @@ class Image extends BaseI18nLoop $search = $method->invoke(null); // Static ! // $query->filterByXXX(id) - $method = new \ReflectionMethod($queryClass, $filterMethod); - $method->invoke($search, $object_id); + if (! is_null($object_id)) { + $method = new \ReflectionMethod($queryClass, $filterMethod); + $method->invoke($search, $object_id); + } $orders = $this->getOrder(); @@ -171,11 +173,12 @@ class Image extends BaseI18nLoop if (! is_null($source)) { $source_id = $this->getSourceId(); + $id = $this->getId(); - // echo "source = ".$this->getSource().", id=".$source_id." - ".$this->getArg('source_id')->getValue()."
"; + //echo "source = ".$this->getSource()."source_id=$source_id, id=$id
"; - if (is_null($source_id)) { - throw new \InvalidArgumentException("'source_id' argument cannot be null if 'source' argument is specified."); + if (is_null($source_id) && is_null($id)) { + throw new \InvalidArgumentException("If 'source' argument is specified, 'id' or 'source_id' argument should be specified"); } $search = $this->createSearchQuery($source, $source_id); @@ -259,14 +262,13 @@ class Image extends BaseI18nLoop } - // echo "sql=".$search->toString(); + //echo "sql=".$search->toString(); $results = $this->search($search, $pagination); $loopResult = new LoopResult($results); foreach ($results as $result) { - // Create image processing event $event = new ImageEvent($this->request); @@ -282,7 +284,7 @@ class Image extends BaseI18nLoop // Put source image file path $source_filepath = sprintf("%s%s/%s/%s", THELIA_ROOT, - ConfigQuery::read('documents_library_path', 'local/media/images'), + ConfigQuery::read('images_library_path', 'local/media/images'), $object_type, $result->getFile() ); diff --git a/core/lib/Thelia/Exception/DocumentException.php b/core/lib/Thelia/Exception/DocumentException.php new file mode 100644 index 000000000..9727a4267 --- /dev/null +++ b/core/lib/Thelia/Exception/DocumentException.php @@ -0,0 +1,36 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Exception; + +use Thelia\Log\Tlog; + +class DocumentException extends \RuntimeException +{ + public function __construct($message, $code = null, $previous = null) + { + Tlog::getInstance()->addError($message); + + parent::__construct($message, $code, $previous); + } +} diff --git a/core/lib/Thelia/Model/CategoryDocument.php b/core/lib/Thelia/Model/CategoryDocument.php index bd86f1d1e..73d99f591 100755 --- a/core/lib/Thelia/Model/CategoryDocument.php +++ b/core/lib/Thelia/Model/CategoryDocument.php @@ -3,8 +3,26 @@ namespace Thelia\Model; use Thelia\Model\Base\CategoryDocument as BaseCategoryDocument; +use Propel\Runtime\Connection\ConnectionInterface; - class CategoryDocument extends BaseCategoryDocument +class CategoryDocument extends BaseCategoryDocument { + use \Thelia\Model\Tools\PositionManagementTrait; + /** + * Calculate next position relative to our parent + */ + protected function addCriteriaToPositionQuery($query) { + $query->filterByCategory($this->getCategory()); + } + + /** + * {@inheritDoc} + */ + public function preInsert(ConnectionInterface $con = null) + { + $this->setPosition($this->getNextPosition()); + + return true; + } } diff --git a/core/lib/Thelia/Model/CategoryImage.php b/core/lib/Thelia/Model/CategoryImage.php index ef555f83e..5bf964e10 100755 --- a/core/lib/Thelia/Model/CategoryImage.php +++ b/core/lib/Thelia/Model/CategoryImage.php @@ -3,8 +3,26 @@ namespace Thelia\Model; use Thelia\Model\Base\CategoryImage as BaseCategoryImage; +use Propel\Runtime\Connection\ConnectionInterface; - class CategoryImage extends BaseCategoryImage +class CategoryImage extends BaseCategoryImage { + use \Thelia\Model\Tools\PositionManagementTrait; + /** + * Calculate next position relative to our parent + */ + protected function addCriteriaToPositionQuery($query) { + $query->filterByCategory($this->getCategory()); + } + + /** + * {@inheritDoc} + */ + public function preInsert(ConnectionInterface $con = null) + { + $this->setPosition($this->getNextPosition()); + + return true; + } } diff --git a/core/lib/Thelia/Model/ContentDocument.php b/core/lib/Thelia/Model/ContentDocument.php index 933a089cb..8ecf3a3a9 100755 --- a/core/lib/Thelia/Model/ContentDocument.php +++ b/core/lib/Thelia/Model/ContentDocument.php @@ -3,8 +3,26 @@ namespace Thelia\Model; use Thelia\Model\Base\ContentDocument as BaseContentDocument; +use Propel\Runtime\Connection\ConnectionInterface; - class ContentDocument extends BaseContentDocument +class ContentDocument extends BaseContentDocument { + use \Thelia\Model\Tools\PositionManagementTrait; + /** + * Calculate next position relative to our parent + */ + protected function addCriteriaToPositionQuery($query) { + $query->filterByContent($this->getContent()); + } + + /** + * {@inheritDoc} + */ + public function preInsert(ConnectionInterface $con = null) + { + $this->setPosition($this->getNextPosition()); + + return true; + } } diff --git a/core/lib/Thelia/Model/ContentImage.php b/core/lib/Thelia/Model/ContentImage.php index 3020e48f3..ac1dcf755 100755 --- a/core/lib/Thelia/Model/ContentImage.php +++ b/core/lib/Thelia/Model/ContentImage.php @@ -3,8 +3,26 @@ namespace Thelia\Model; use Thelia\Model\Base\ContentImage as BaseContentImage; +use Propel\Runtime\Connection\ConnectionInterface; - class ContentImage extends BaseContentImage +class ContentImage extends BaseContentImage { + use \Thelia\Model\Tools\PositionManagementTrait; -} + /** + * Calculate next position relative to our parent + */ + protected function addCriteriaToPositionQuery($query) { + $query->filterByContent($this->getContent()); + } + + /** + * {@inheritDoc} + */ + public function preInsert(ConnectionInterface $con = null) + { + $this->setPosition($this->getNextPosition()); + + return true; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Model/FolderDocument.php b/core/lib/Thelia/Model/FolderDocument.php index c9644835e..0a86995d2 100755 --- a/core/lib/Thelia/Model/FolderDocument.php +++ b/core/lib/Thelia/Model/FolderDocument.php @@ -3,8 +3,26 @@ namespace Thelia\Model; use Thelia\Model\Base\FolderDocument as BaseFolderDocument; +use Propel\Runtime\Connection\ConnectionInterface; - class FolderDocument extends BaseFolderDocument +class FolderDocument extends BaseFolderDocument { + use \Thelia\Model\Tools\PositionManagementTrait; + /** + * Calculate next position relative to our parent + */ + protected function addCriteriaToPositionQuery($query) { + $query->filterByFolder($this->getFolder()); + } + + /** + * {@inheritDoc} + */ + public function preInsert(ConnectionInterface $con = null) + { + $this->setPosition($this->getNextPosition()); + + return true; + } } diff --git a/core/lib/Thelia/Model/FolderImage.php b/core/lib/Thelia/Model/FolderImage.php index 4e6d285f8..58d8f928e 100755 --- a/core/lib/Thelia/Model/FolderImage.php +++ b/core/lib/Thelia/Model/FolderImage.php @@ -3,8 +3,26 @@ namespace Thelia\Model; use Thelia\Model\Base\FolderImage as BaseFolderImage; +use Propel\Runtime\Connection\ConnectionInterface; - class FolderImage extends BaseFolderImage +class FolderImage extends BaseFolderImage { + use \Thelia\Model\Tools\PositionManagementTrait; + /** + * Calculate next position relative to our parent + */ + protected function addCriteriaToPositionQuery($query) { + $query->filterByFolder($this->getFolder()); + } + + /** + * {@inheritDoc} + */ + public function preInsert(ConnectionInterface $con = null) + { + $this->setPosition($this->getNextPosition()); + + return true; + } } diff --git a/core/lib/Thelia/Model/ProductDocument.php b/core/lib/Thelia/Model/ProductDocument.php index a49c4f11e..53515ff3c 100755 --- a/core/lib/Thelia/Model/ProductDocument.php +++ b/core/lib/Thelia/Model/ProductDocument.php @@ -3,8 +3,27 @@ namespace Thelia\Model; use Thelia\Model\Base\ProductDocument as BaseProductDocument; +use Propel\Runtime\Connection\ConnectionInterface; - class ProductDocument extends BaseProductDocument +class ProductDocument extends BaseProductDocument { + use \Thelia\Model\Tools\PositionManagementTrait; + + /** + * Calculate next position relative to our parent + */ + protected function addCriteriaToPositionQuery($query) { + $query->filterByProduct($this->getProduct()); + } + + /** + * {@inheritDoc} + */ + public function preInsert(ConnectionInterface $con = null) + { + $this->setPosition($this->getNextPosition()); + + return true; + } } diff --git a/core/lib/Thelia/Model/ProductImage.php b/core/lib/Thelia/Model/ProductImage.php index 9fe5b78e0..4bf0c40a6 100755 --- a/core/lib/Thelia/Model/ProductImage.php +++ b/core/lib/Thelia/Model/ProductImage.php @@ -3,8 +3,26 @@ namespace Thelia\Model; use Thelia\Model\Base\ProductImage as BaseProductImage; +use Propel\Runtime\Connection\ConnectionInterface; - class ProductImage extends BaseProductImage +class ProductImage extends BaseProductImage { + use \Thelia\Model\Tools\PositionManagementTrait; + /** + * Calculate next position relative to our parent + */ + protected function addCriteriaToPositionQuery($query) { + $query->filterByProduct($this->getProduct()); + } + + /** + * {@inheritDoc} + */ + public function preInsert(ConnectionInterface $con = null) + { + $this->setPosition($this->getNextPosition()); + + return true; + } } diff --git a/core/lib/Thelia/Tests/Action/DocumentTest.php b/core/lib/Thelia/Tests/Action/DocumentTest.php new file mode 100644 index 000000000..39aece1f4 --- /dev/null +++ b/core/lib/Thelia/Tests/Action/DocumentTest.php @@ -0,0 +1,248 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Tests\Action\DocumentTest; + +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\HttpFoundation\Session\Session; + +use Thelia\Action\Document; +use Thelia\Core\Event\DocumentEvent; +use Thelia\Model\ConfigQuery; + +/** + * Class DocumentTest + * + * @package Thelia\Tests\Action\DocumentTest + */ +class DocumentTest extends \Thelia\Tests\TestCaseWithURLToolSetup +{ + protected $request; + + protected $session; + + public function getContainer() + { + $container = new \Symfony\Component\DependencyInjection\ContainerBuilder(); + + $dispatcher = $this->getMock("Symfony\Component\EventDispatcher\EventDispatcherInterface"); + + $container->set("event_dispatcher", $dispatcher); + + $request = new Request(); + $request->setSession($this->session); + + $container->set("request", $request); + + return $container; + } + + public function setUp() + { + $this->session = new Session(new MockArraySessionStorage()); + $this->request = new Request(); + + $this->request->setSession($this->session); + + // mock cache configuration. + $config = ConfigQuery::create()->filterByName('document_cache_dir_from_web_root')->findOne(); + + if ($config != null) { + $this->cache_dir_from_web_root = $config->getValue(); + + $config->setValue(__DIR__."/assets/documents/cache"); + + $config->setValue($this->cache_dir_from_web_root)->save(); + } + } + + public static function setUpBeforeClass() + { + $dir = THELIA_WEB_DIR."/cache/tests"; + if ($dh = @opendir($dir)) { + while ($file = readdir($dh)) { + if ($file == '.' || $file == '..') continue; + + unlink(sprintf("%s/%s", $dir, $file)); + } + + closedir($dh); + } + } + + public function tearDown() + { + // restore cache configuration. + $config = ConfigQuery::create()->filterByName('document_cache_dir_from_web_root')->findOne(); + + if ($config != null) { + $config->setValue($this->cache_dir_from_web_root)->save(); + } + } + + /** + * + * Documentevent is empty, mandatory parameters not specified. + * + * @expectedException \InvalidArgumentException + */ + public function testProcessEmptyDocumentEvent() + { + $event = new DocumentEvent($this->request); + + $document = new Document($this->getContainer()); + + $document->processDocument($event); + } + + /** + * + * Try to process a non-existent file + * + * @expectedException \InvalidArgumentException + */ + public function testProcessNonExistentDocument() + { + $event = new DocumentEvent($this->request); + + $document = new Document($this->getContainer()); + + $event->setCacheFilepath("blablabla.txt"); + $event->setCacheSubdirectory("tests"); + + $document->processDocument($event); + } + + /** + * + * Try to process a file outside of the cache + * + * @expectedException \InvalidArgumentException + */ + public function testProcessDocumentOutsideValidPath() + { + $event = new DocumentEvent($this->request); + + $document = new Document($this->getContainer()); + + $event->setCacheFilepath("blablabla.pdf"); + $event->setCacheSubdirectory("../../../"); + + $document->processDocument($event); + } + + /** + * No operation done on source file -> copie ! + */ + public function testProcessDocumentCopy() + { + $event = new DocumentEvent($this->request); + + $event->setSourceFilepath(__DIR__."/assets/documents/sources/test-document-1.txt"); + $event->setCacheSubdirectory("tests"); + + $document = new Document($this->getContainer()); + + // mock cache configuration. + $config = ConfigQuery::create()->filterByName('original_document_delivery_mode')->findOne(); + + if ($config != null) { + $oldval = $config->getValue(); + $config->setValue('copy')->save(); + } + + $document->processDocument($event); + + if ($config != null) $config->setValue($oldval)->save(); + + $imgdir = ConfigQuery::read('document_cache_dir_from_web_root'); + + $this->assertFileExists(THELIA_WEB_DIR."/$imgdir/tests/test-document-1.txt"); + } + + /** + * No operation done on source file -> link ! + */ + public function testProcessDocumentSymlink() + { + $event = new DocumentEvent($this->request); + + $event->setSourceFilepath(__DIR__."/assets/documents/sources/test-document-2.txt"); + $event->setCacheSubdirectory("tests"); + + $document = new Document($this->getContainer()); + + // mock cache configuration. + $config = ConfigQuery::create()->filterByName('original_document_delivery_mode')->findOne(); + + if ($config != null) { + $oldval = $config->getValue(); + $config->setValue('symlink')->save(); + } + + $document->processDocument($event); + + if ($config != null) $config->setValue($oldval)->save(); + + $imgdir = ConfigQuery::read('document_cache_dir_from_web_root'); + + $this->assertFileExists(THELIA_WEB_DIR."/$imgdir/tests/test-document-2.txt"); + } + + public function testClearTestsCache() + { + $event = new DocumentEvent($this->request); + + $event->setCacheSubdirectory('tests'); + + $document = new Document($this->getContainer()); + + $document->clearCache($event); + } + + public function testClearWholeCache() + { + $event = new DocumentEvent($this->request); + + $document = new Document($this->getContainer()); + + $document->clearCache($event); + } + + /** + * Try to clear directory ouside of the cache + * + * @expectedException \InvalidArgumentException + */ + public function testClearUnallowedPathCache() + { + $event = new DocumentEvent($this->request); + + $event->setCacheSubdirectory('../../../..'); + + $document = new Document($this->getContainer()); + + $document->clearCache($event); + } +} diff --git a/core/lib/Thelia/Tests/Action/assets/documents/sources/test-document-1.txt b/core/lib/Thelia/Tests/Action/assets/documents/sources/test-document-1.txt new file mode 100644 index 000000000..82537f32d --- /dev/null +++ b/core/lib/Thelia/Tests/Action/assets/documents/sources/test-document-1.txt @@ -0,0 +1 @@ +This is a text document. \ No newline at end of file diff --git a/core/lib/Thelia/Tests/Action/assets/documents/sources/test-document-2.txt b/core/lib/Thelia/Tests/Action/assets/documents/sources/test-document-2.txt new file mode 100644 index 000000000..82537f32d --- /dev/null +++ b/core/lib/Thelia/Tests/Action/assets/documents/sources/test-document-2.txt @@ -0,0 +1 @@ +This is a text document. \ No newline at end of file diff --git a/core/lib/Thelia/Tests/Core/Template/Loop/DocumentTest.php b/core/lib/Thelia/Tests/Core/Template/Loop/DocumentTest.php new file mode 100644 index 000000000..04e41b6f8 --- /dev/null +++ b/core/lib/Thelia/Tests/Core/Template/Loop/DocumentTest.php @@ -0,0 +1,89 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Tests\Core\Template\Loop; + +use Thelia\Model\DocumentQuery; +use Thelia\Tests\Core\Template\Element\BaseLoopTestor; + +use Thelia\Core\Template\Loop\Document; +use Thelia\Model\ProductDocumentQuery; +use Thelia\Model\CategoryDocumentQuery; +use Thelia\Model\ContentDocumentQuery; +use Thelia\Model\FolderDocumentQuery; + +/** + * + * @author Etienne Roudeix + * + */ +class DocumentTest extends BaseLoopTestor +{ + public function getTestedClassName() + { + return 'Thelia\Core\Template\Loop\Document'; + } + + public function getTestedInstance() + { + return new Document($this->container); + } + + public function getMandatoryArguments() + { + return array('source' => 'product', 'id' => 1); + } + + public function testSearchByProductId() + { + $document = ProductDocumentQuery::create()->findOne(); + + $this->baseTestSearchById($document->getId()); + } + + public function testSearchByFolderId() + { + $document = FolderDocumentQuery::create()->findOne(); + + $this->baseTestSearchById($document->getId()); + } + + public function testSearchByContentId() + { + $document = ContentDocumentQuery::create()->findOne(); + + $this->baseTestSearchById($document->getId()); + } + + public function testSearchByCategoryId() + { + $document = CategoryDocumentQuery::create()->findOne(); + + $this->baseTestSearchById($document->getId()); + } + + public function testSearchLimit() + { + $this->baseTestSearchWithLimit(1); + } +} diff --git a/core/lib/Thelia/Tests/Core/Template/Loop/ImageTest.php b/core/lib/Thelia/Tests/Core/Template/Loop/ImageTest.php new file mode 100644 index 000000000..3d2517c0a --- /dev/null +++ b/core/lib/Thelia/Tests/Core/Template/Loop/ImageTest.php @@ -0,0 +1,89 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Tests\Core\Template\Loop; + +use Thelia\Model\ImageQuery; +use Thelia\Tests\Core\Template\Element\BaseLoopTestor; + +use Thelia\Core\Template\Loop\Image; +use Thelia\Model\ProductImageQuery; +use Thelia\Model\CategoryImageQuery; +use Thelia\Model\ContentImageQuery; +use Thelia\Model\FolderImageQuery; + +/** + * + * @author Etienne Roudeix + * + */ +class ImageTest extends BaseLoopTestor +{ + public function getTestedClassName() + { + return 'Thelia\Core\Template\Loop\Image'; + } + + public function getTestedInstance() + { + return new Image($this->container); + } + + public function getMandatoryArguments() + { + return array('source' => 'product', 'id' => 1); + } + + public function testSearchByProductId() + { + $image = ProductImageQuery::create()->findOne(); + + $this->baseTestSearchById($image->getId()); + } + + public function testSearchByFolderId() + { + $image = FolderImageQuery::create()->findOne(); + + $this->baseTestSearchById($image->getId()); + } + + public function testSearchByContentId() + { + $image = ContentImageQuery::create()->findOne(); + + $this->baseTestSearchById($image->getId()); + } + + public function testSearchByCategoryId() + { + $image = CategoryImageQuery::create()->findOne(); + + $this->baseTestSearchById($image->getId()); + } + + public function testSearchLimit() + { + $this->baseTestSearchWithLimit(1); + } +} diff --git a/core/lib/Thelia/Tools/URL.php b/core/lib/Thelia/Tools/URL.php index 495250f99..363860064 100755 --- a/core/lib/Thelia/Tools/URL.php +++ b/core/lib/Thelia/Tools/URL.php @@ -43,14 +43,15 @@ class URL protected static $instance = null; - public function __construct(ContainerInterface $container) + public function __construct(ContainerInterface $container = null) { // Allow singleton style calls once intanciated. // For this to work, the URL service has to be instanciated very early. This is done manually // in TheliaHttpKernel, by calling $this->container->get('thelia.url.manager'); self::$instance = $this; - $this->requestContext = $container->get('router.admin')->getContext(); + if ($container !== null) + $this->requestContext = $container->get('router.admin')->getContext(); $this->retriever = new RewritingRetriever(); $this->resolver = new RewritingResolver(); diff --git a/install/faker.php b/install/faker.php index 66303f05a..aadfc8759 100755 --- a/install/faker.php +++ b/install/faker.php @@ -4,12 +4,7 @@ use Thelia\Constraint\Rule\AvailableForTotalAmountManager; use Thelia\Constraint\Rule\AvailableForXArticlesManager; use Thelia\Constraint\Rule\Operators; use Thelia\Coupon\CouponRuleCollection; -use Thelia\Model\ProductImage; -use Thelia\Model\CategoryImage; -use Thelia\Model\FolderImage; -use Thelia\Model\ContentImage; -use Imagine\Image\Color; -use Imagine\Image\Point; + require __DIR__ . '/../core/bootstrap.php'; @@ -23,12 +18,17 @@ $con = \Propel\Runtime\Propel::getConnection( ); $con->beginTransaction(); +// Intialize URL management +$url = new Thelia\Tools\URL(); + $currency = \Thelia\Model\CurrencyQuery::create()->filterByCode('EUR')->findOne(); try { $stmt = $con->prepare("SET foreign_key_checks = 0"); $stmt->execute(); + echo "Clearing tables\n"; + $productAssociatedContent = Thelia\Model\ProductAssociatedContentQuery::create() ->find(); $productAssociatedContent->delete(); @@ -125,9 +125,22 @@ try { ->find(); $productPrice->delete(); + \Thelia\Model\ProductImageQuery::create()->find()->delete(); + \Thelia\Model\CategoryImageQuery::create()->find()->delete(); + \Thelia\Model\FolderImageQuery::create()->find()->delete(); + \Thelia\Model\ContentImageQuery::create()->find()->delete(); + + \Thelia\Model\ProductDocumentQuery::create()->find()->delete(); + \Thelia\Model\CategoryDocumentQuery::create()->find()->delete(); + \Thelia\Model\FolderDocumentQuery::create()->find()->delete(); + \Thelia\Model\ContentDocumentQuery::create()->find()->delete(); + $stmt = $con->prepare("SET foreign_key_checks = 1"); + $stmt->execute(); + echo "Creating customer\n"; + //customer $customer = new Thelia\Model\Customer(); $customer->createOrUpdate( @@ -185,6 +198,8 @@ try { } } + echo "Creating features\n"; + //features and features_av $featureList = array(); for($i=0; $i<4; $i++) { @@ -208,6 +223,8 @@ try { } } + echo "Creating attributes\n"; + //attributes and attributes_av $attributeList = array(); for($i=0; $i<4; $i++) { @@ -230,6 +247,8 @@ try { } } + echo "Creating templates\n"; + $template = new Thelia\Model\Template(); setI18n($faker, $template, array("Name" => 20)); $template->save(); @@ -252,6 +271,8 @@ try { ->save(); } + echo "Creating folders and content\n"; + //folders and contents $contentIdList = array(); for($i=0; $i<4; $i++) { @@ -263,10 +284,14 @@ try { $folder->save(); - $image = new FolderImage(); + $image = new \Thelia\Model\FolderImage(); $image->setFolderId($folder->getId()); generate_image($image, 1, 'folder', $folder->getId()); + $document = new \Thelia\Model\FolderDocument(); + $document->setFolderId($folder->getId()); + generate_document($document, 1, 'folder', $folder->getId()); + for($j=1; $jsetParent($folder->getId()); @@ -276,10 +301,14 @@ try { $subfolder->save(); - $image = new FolderImage(); + $image = new \Thelia\Model\FolderImage(); $image->setFolderId($subfolder->getId()); generate_image($image, 1, 'folder', $subfolder->getId()); + $document = new \Thelia\Model\FolderDocument(); + $document->setFolderId($folder->getId()); + generate_document($document, 1, 'folder', $subfolder->getId()); + for($k=0; $kaddFolder($subfolder); @@ -291,13 +320,20 @@ try { $contentId = $content->getId(); $contentIdList[] = $contentId; - $image = new ContentImage(); - $image->setContentId($content->getId()); + $image = new \Thelia\Model\ContentImage(); + $image->setContentId($contentId); generate_image($image, 1, 'content', $contentId); + + $document = new \Thelia\Model\ContentDocument(); + $document->setContentId($contentId); + generate_document($document, 1, 'content', $contentId); + } } } + echo "Creating categories and products\n"; + //categories and products $productIdList = array(); $categoryIdList = array(); @@ -405,6 +441,8 @@ try { } } + echo "Generating coupns fixtures\n"; + generateCouponFixtures($thelia); $con->commit(); @@ -429,10 +467,14 @@ function createProduct($faker, $category, $position, $template, &$productIdList) $productId = $product->getId(); $productIdList[] = $productId; - $image = new ProductImage(); + $image = new \Thelia\Model\ProductImage(); $image->setProductId($productId); generate_image($image, 1, 'product', $productId); + $document = new \Thelia\Model\ProductDocument(); + $document->setProductId($productId); + generate_document($document, 1, 'product', $productId); + return $product; } @@ -464,10 +506,14 @@ function createCategory($faker, $parent, $position, &$categoryIdList, $contentId ->save(); } - $image = new CategoryImage(); + $image = new \Thelia\Model\CategoryImage(); $image->setCategoryId($categoryId); generate_image($image, 1, 'category', $categoryId); + $document = new \Thelia\Model\CategoryDocument(); + $document->setCategoryId($categoryId); + generate_document($document, 1, 'category', $categoryId); + return $category; } @@ -480,37 +526,36 @@ function generate_image($image, $position, $typeobj, $id) { ->setDescription($faker->text(250)) ->setChapo($faker->text(40)) ->setPostscriptum($faker->text(40)) - ->setPosition($position) ->setFile(sprintf("sample-image-%s.png", $id)) ->save() ; // Generate images $imagine = new Imagine\Gd\Imagine(); - $image = $imagine->create(new Imagine\Image\Box(320,240), new Color('#E9730F')); + $image = $imagine->create(new Imagine\Image\Box(320,240), new Imagine\Image\Color('#E9730F')); - $white = new Color('#FFF'); + $white = new Imagine\Image\Color('#FFF'); $font = $imagine->font(__DIR__.'/faker-assets/FreeSans.ttf', 14, $white); $tbox = $font->box("THELIA"); - $image->draw()->text("THELIA", $font, new Point((320 - $tbox->getWidth()) / 2, 30)); + $image->draw()->text("THELIA", $font, new Imagine\Image\Point((320 - $tbox->getWidth()) / 2, 30)); $str = sprintf("%s sample image", ucfirst($typeobj)); $tbox = $font->box($str); - $image->draw()->text($str, $font, new Point((320 - $tbox->getWidth()) / 2, 80)); + $image->draw()->text($str, $font, new Imagine\Image\Point((320 - $tbox->getWidth()) / 2, 80)); $font = $imagine->font(__DIR__.'/faker-assets/FreeSans.ttf', 18, $white); $str = sprintf("%s ID %d", strtoupper($typeobj), $id); $tbox = $font->box($str); - $image->draw()->text($str, $font, new Point((320 - $tbox->getWidth()) / 2, 180)); + $image->draw()->text($str, $font, new Imagine\Image\Point((320 - $tbox->getWidth()) / 2, 180)); $image->draw() - ->line(new Point(0, 0), new Point(319, 0), $white) - ->line(new Point(319, 0), new Point(319, 239), $white) - ->line(new Point(319, 239), new Point(0,239), $white) - ->line(new Point(0, 239), new Point(0, 0), $white) + ->line(new Imagine\Image\Point(0, 0), new Imagine\Image\Point(319, 0), $white) + ->line(new Imagine\Image\Point(319, 0), new Imagine\Image\Point(319, 239), $white) + ->line(new Imagine\Image\Point(319, 239), new Imagine\Image\Point(0,239), $white) + ->line(new Imagine\Image\Point(0, 239), new Imagine\Image\Point(0, 0), $white) ; $image_file = sprintf("%s/../local/media/images/%s/sample-image-%s.png", __DIR__, $typeobj, $id); @@ -520,6 +565,26 @@ function generate_image($image, $position, $typeobj, $id) { $image->save($image_file); } +function generate_document($document, $position, $typeobj, $id) { + + global $faker; + + $document + ->setTitle($faker->text(20)) + ->setDescription($faker->text(250)) + ->setChapo($faker->text(40)) + ->setPostscriptum($faker->text(40)) + ->setFile(sprintf("sample-document-%s.txt", $id)) + ->save() + ; + + $document_file = sprintf("%s/../local/media/documents/%s/sample-document-%s.txt", __DIR__, $typeobj, $id); + + if (! is_dir(dirname($document_file))) mkdir(dirname($document_file), 0777, true); + + file_put_contents($document_file, $faker->text(256)); +} + function setI18n($faker, &$object, $fields = array('Title' => 20, 'Description' => 50) ) { $localeList = $localeList = array('fr_FR', 'en_US', 'es_ES', 'it_IT'); diff --git a/install/insert.sql b/install/insert.sql index 1b8327a10..fbb09889b 100755 --- a/install/insert.sql +++ b/install/insert.sql @@ -13,8 +13,11 @@ INSERT INTO `config` (`name`, `value`, `secured`, `hidden`, `created_at`, `updat ('imagine_graphic_driver', 'gd', 0, 0, NOW(), NOW()), ('default_images_quality_percent', '75', 0, 0, NOW(), NOW()), ('original_image_delivery_mode', 'symlink', 0, 0, NOW(), NOW()), +('original_document_delivery_mode', 'symlink', 0, 0, NOW(), NOW()), ('images_library_path', 'local/media/images', 0, 0, NOW(), NOW()), +('documents_library_path', 'local/media/documents', 0, 0, NOW(), NOW()), ('image_cache_dir_from_web_root', 'cache/images', 0, 0, NOW(), NOW()), +('document_cache_dir_from_web_root', 'cache/documents', 0, 0, NOW(), NOW()), ('currency_rate_update_url', 'http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml', 0, 0, NOW(), NOW()), ('page_not_found_view', '404.html', 0, 0, NOW(), NOW()), ('use_tax_free_amounts', 0, 0, 0, NOW(), NOW()),