Added documents action and loop

This commit is contained in:
franck
2013-09-17 01:07:42 +02:00
parent bff08dd581
commit d097851522
26 changed files with 1507 additions and 217 deletions

View File

@@ -0,0 +1,178 @@
<?php
/*************************************************************************************/
/* */
/* Thelia */
/* */
/* Copyright (c) OpenStudio */
/* email : info@thelia.net */
/* web : http://www.thelia.net */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 3 of the License */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*************************************************************************************/
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/<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
{
/**
* @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;
}
}

View File

@@ -0,0 +1,139 @@
<?php
/*************************************************************************************/
/* */
/* Thelia */
/* */
/* Copyright (c) OpenStudio */
/* email : info@thelia.net */
/* web : http://www.thelia.net */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 3 of the License */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*************************************************************************************/
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 <franck@cqfdev.fr>
*
*/
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),
);
}
}

View File

@@ -71,7 +71,7 @@ use Thelia\Core\Event\TheliaEvents;
* @author Franck Allimant <franck@cqfdev.fr> * @author Franck Allimant <franck@cqfdev.fr>
* *
*/ */
class Image extends BaseAction implements EventSubscriberInterface class Image extends BaseCachedFile implements EventSubscriberInterface
{ {
// Resize mode constants // Resize mode constants
const EXACT_RATIO_WITH_BORDERS = 1; const EXACT_RATIO_WITH_BORDERS = 1;
@@ -79,38 +79,10 @@ class Image extends BaseAction implements EventSubscriberInterface
const KEEP_IMAGE_RATIO = 3; const KEEP_IMAGE_RATIO = 3;
/** /**
* Clear the image cache. Is a subdirectory is specified, only this directory is cleared. * @return string root of the image cache directory in web space
* If no directory is specified, the whole cache is cleared.
* Only files are deleted, directories will remain.
*
* @param ImageEvent $event
*/ */
public function clearCache(ImageEvent $event) protected function getCacheDirFromWebRoot() {
{ return ConfigQuery::read('image_cache_dir_from_web_root', 'cache');
$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());
}
}
} }
/** /**
@@ -138,9 +110,9 @@ class Image extends BaseAction implements EventSubscriberInterface
// echo basename($source_file).": "; // echo basename($source_file).": ";
// Find cached file path // 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)) { if (! file_exists($cacheFilePath)) {
@@ -359,94 +331,6 @@ class Image extends BaseAction implements EventSubscriberInterface
return $image; 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 * Create a new Imagine object using current driver configuration
* *

View File

@@ -0,0 +1,94 @@
<?php
/*************************************************************************************/
/* */
/* Thelia */
/* */
/* Copyright (c) OpenStudio */
/* email : info@thelia.net */
/* web : http://www.thelia.net */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 3 of the License */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*************************************************************************************/
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;
}
}

View File

@@ -0,0 +1,67 @@
<?php
/*************************************************************************************/
/* */
/* Thelia */
/* */
/* Copyright (c) OpenStudio */
/* email : info@thelia.net */
/* web : http://www.thelia.net */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 3 of the License */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*************************************************************************************/
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;
}
}

View File

@@ -23,22 +23,8 @@
namespace Thelia\Core\Event; 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 * @var string The absolute path of the cached image file
*/ */
@@ -121,6 +107,8 @@ class ImageEvent extends ActionEvent
public function setCategory($category) public function setCategory($category)
{ {
$this->category = $category; $this->category = $category;
return $this;
} }
public function getWidth() public function getWidth()
@@ -131,6 +119,8 @@ class ImageEvent extends ActionEvent
public function setWidth($width) public function setWidth($width)
{ {
$this->width = $width; $this->width = $width;
return $this;
} }
public function getHeight() public function getHeight()
@@ -141,6 +131,8 @@ class ImageEvent extends ActionEvent
public function setHeight($height) public function setHeight($height)
{ {
$this->height = $height; $this->height = $height;
return $this;
} }
public function getResizeMode() public function getResizeMode()
@@ -151,6 +143,8 @@ class ImageEvent extends ActionEvent
public function setResizeMode($resize_mode) public function setResizeMode($resize_mode)
{ {
$this->resize_mode = $resize_mode; $this->resize_mode = $resize_mode;
return $this;
} }
public function getBackgroundColor() public function getBackgroundColor()
@@ -161,6 +155,8 @@ class ImageEvent extends ActionEvent
public function setBackgroundColor($background_color) public function setBackgroundColor($background_color)
{ {
$this->background_color = $background_color; $this->background_color = $background_color;
return $this;
} }
public function getEffects() public function getEffects()
@@ -171,6 +167,8 @@ class ImageEvent extends ActionEvent
public function setEffects(array $effects) public function setEffects(array $effects)
{ {
$this->effects = $effects; $this->effects = $effects;
return $this;
} }
public function getRotation() public function getRotation()
@@ -181,46 +179,8 @@ class ImageEvent extends ActionEvent
public function setRotation($rotation) public function setRotation($rotation)
{ {
$this->rotation = $rotation; $this->rotation = $rotation;
}
public function getFileUrl() return $this;
{
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;
} }
public function getQuality() public function getQuality()
@@ -231,6 +191,8 @@ class ImageEvent extends ActionEvent
public function setQuality($quality) public function setQuality($quality)
{ {
$this->quality = $quality; $this->quality = $quality;
return $this;
} }
public function getOriginalFileUrl() public function getOriginalFileUrl()
@@ -241,6 +203,8 @@ class ImageEvent extends ActionEvent
public function setOriginalFileUrl($original_file_url) public function setOriginalFileUrl($original_file_url)
{ {
$this->original_file_url = $original_file_url; $this->original_file_url = $original_file_url;
return $this;
} }
public function getCacheOriginalFilepath() public function getCacheOriginalFilepath()
@@ -251,5 +215,7 @@ class ImageEvent extends ActionEvent
public function setCacheOriginalFilepath($cache_original_filepath) public function setCacheOriginalFilepath($cache_original_filepath)
{ {
$this->cache_original_filepath = $cache_original_filepath; $this->cache_original_filepath = $cache_original_filepath;
return $this;
} }
} }

View File

@@ -194,6 +194,11 @@ final class TheliaEvents
*/ */
const IMAGE_PROCESS = "action.processImage"; const IMAGE_PROCESS = "action.processImage";
/**
* Sent on document processing
*/
const DOCUMENT_PROCESS = "action.processDocument";
/** /**
* Sent on cimage cache clear request * Sent on cimage cache clear request
*/ */

View File

@@ -0,0 +1,277 @@
<?php
/*************************************************************************************/
/* */
/* Thelia */
/* */
/* Copyright (c) OpenStudio */
/* email : info@thelia.net */
/* web : http://www.thelia.net */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 3 of the License */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*************************************************************************************/
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 <franck@cqfdev.fr>
*/
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()."<br />";
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;
}
}

View File

@@ -123,8 +123,10 @@ class Image extends BaseI18nLoop
$search = $method->invoke(null); // Static ! $search = $method->invoke(null); // Static !
// $query->filterByXXX(id) // $query->filterByXXX(id)
if (! is_null($object_id)) {
$method = new \ReflectionMethod($queryClass, $filterMethod); $method = new \ReflectionMethod($queryClass, $filterMethod);
$method->invoke($search, $object_id); $method->invoke($search, $object_id);
}
$orders = $this->getOrder(); $orders = $this->getOrder();
@@ -171,11 +173,12 @@ class Image extends BaseI18nLoop
if (! is_null($source)) { if (! is_null($source)) {
$source_id = $this->getSourceId(); $source_id = $this->getSourceId();
$id = $this->getId();
// echo "source = ".$this->getSource().", id=".$source_id." - ".$this->getArg('source_id')->getValue()."<br />"; //echo "source = ".$this->getSource()."source_id=$source_id, id=$id<br />";
if (is_null($source_id)) { if (is_null($source_id) && is_null($id)) {
throw new \InvalidArgumentException("'source_id' argument cannot be null if 'source' argument is specified."); throw new \InvalidArgumentException("If 'source' argument is specified, 'id' or 'source_id' argument should be specified");
} }
$search = $this->createSearchQuery($source, $source_id); $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); $results = $this->search($search, $pagination);
$loopResult = new LoopResult($results); $loopResult = new LoopResult($results);
foreach ($results as $result) { foreach ($results as $result) {
// Create image processing event // Create image processing event
$event = new ImageEvent($this->request); $event = new ImageEvent($this->request);
@@ -282,7 +284,7 @@ class Image extends BaseI18nLoop
// Put source image file path // Put source image file path
$source_filepath = sprintf("%s%s/%s/%s", $source_filepath = sprintf("%s%s/%s/%s",
THELIA_ROOT, THELIA_ROOT,
ConfigQuery::read('documents_library_path', 'local/media/images'), ConfigQuery::read('images_library_path', 'local/media/images'),
$object_type, $object_type,
$result->getFile() $result->getFile()
); );

View File

@@ -0,0 +1,36 @@
<?php
/*************************************************************************************/
/* */
/* Thelia */
/* */
/* Copyright (c) OpenStudio */
/* email : info@thelia.net */
/* web : http://www.thelia.net */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 3 of the License */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*************************************************************************************/
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);
}
}

View File

@@ -3,8 +3,26 @@
namespace Thelia\Model; namespace Thelia\Model;
use Thelia\Model\Base\CategoryDocument as BaseCategoryDocument; 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;
}
} }

View File

@@ -3,8 +3,26 @@
namespace Thelia\Model; namespace Thelia\Model;
use Thelia\Model\Base\CategoryImage as BaseCategoryImage; 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;
}
} }

View File

@@ -3,8 +3,26 @@
namespace Thelia\Model; namespace Thelia\Model;
use Thelia\Model\Base\ContentDocument as BaseContentDocument; 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;
}
} }

View File

@@ -3,8 +3,26 @@
namespace Thelia\Model; namespace Thelia\Model;
use Thelia\Model\Base\ContentImage as BaseContentImage; 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;
}
} }

View File

@@ -3,8 +3,26 @@
namespace Thelia\Model; namespace Thelia\Model;
use Thelia\Model\Base\FolderDocument as BaseFolderDocument; 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;
}
} }

View File

@@ -3,8 +3,26 @@
namespace Thelia\Model; namespace Thelia\Model;
use Thelia\Model\Base\FolderImage as BaseFolderImage; 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;
}
} }

View File

@@ -3,8 +3,27 @@
namespace Thelia\Model; namespace Thelia\Model;
use Thelia\Model\Base\ProductDocument as BaseProductDocument; 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;
}
} }

View File

@@ -3,8 +3,26 @@
namespace Thelia\Model; namespace Thelia\Model;
use Thelia\Model\Base\ProductImage as BaseProductImage; 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;
}
} }

View File

@@ -0,0 +1,248 @@
<?php
/*************************************************************************************/
/* */
/* Thelia */
/* */
/* Copyright (c) OpenStudio */
/* email : info@thelia.net */
/* web : http://www.thelia.net */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 3 of the License */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*************************************************************************************/
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);
}
}

View File

@@ -0,0 +1 @@
This is a text document.

View File

@@ -0,0 +1 @@
This is a text document.

View File

@@ -0,0 +1,89 @@
<?php
/*************************************************************************************/
/* */
/* Thelia */
/* */
/* Copyright (c) OpenStudio */
/* email : info@thelia.net */
/* web : http://www.thelia.net */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 3 of the License */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*************************************************************************************/
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 <eroudeix@openstudio.fr>
*
*/
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);
}
}

View File

@@ -0,0 +1,89 @@
<?php
/*************************************************************************************/
/* */
/* Thelia */
/* */
/* Copyright (c) OpenStudio */
/* email : info@thelia.net */
/* web : http://www.thelia.net */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 3 of the License */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*************************************************************************************/
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 <eroudeix@openstudio.fr>
*
*/
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);
}
}

View File

@@ -43,13 +43,14 @@ class URL
protected static $instance = null; protected static $instance = null;
public function __construct(ContainerInterface $container) public function __construct(ContainerInterface $container = null)
{ {
// Allow singleton style calls once intanciated. // Allow singleton style calls once intanciated.
// For this to work, the URL service has to be instanciated very early. This is done manually // 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'); // in TheliaHttpKernel, by calling $this->container->get('thelia.url.manager');
self::$instance = $this; self::$instance = $this;
if ($container !== null)
$this->requestContext = $container->get('router.admin')->getContext(); $this->requestContext = $container->get('router.admin')->getContext();
$this->retriever = new RewritingRetriever(); $this->retriever = new RewritingRetriever();

View File

@@ -4,12 +4,7 @@ use Thelia\Constraint\Rule\AvailableForTotalAmountManager;
use Thelia\Constraint\Rule\AvailableForXArticlesManager; use Thelia\Constraint\Rule\AvailableForXArticlesManager;
use Thelia\Constraint\Rule\Operators; use Thelia\Constraint\Rule\Operators;
use Thelia\Coupon\CouponRuleCollection; 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'; require __DIR__ . '/../core/bootstrap.php';
@@ -23,12 +18,17 @@ $con = \Propel\Runtime\Propel::getConnection(
); );
$con->beginTransaction(); $con->beginTransaction();
// Intialize URL management
$url = new Thelia\Tools\URL();
$currency = \Thelia\Model\CurrencyQuery::create()->filterByCode('EUR')->findOne(); $currency = \Thelia\Model\CurrencyQuery::create()->filterByCode('EUR')->findOne();
try { try {
$stmt = $con->prepare("SET foreign_key_checks = 0"); $stmt = $con->prepare("SET foreign_key_checks = 0");
$stmt->execute(); $stmt->execute();
echo "Clearing tables\n";
$productAssociatedContent = Thelia\Model\ProductAssociatedContentQuery::create() $productAssociatedContent = Thelia\Model\ProductAssociatedContentQuery::create()
->find(); ->find();
$productAssociatedContent->delete(); $productAssociatedContent->delete();
@@ -125,9 +125,22 @@ try {
->find(); ->find();
$productPrice->delete(); $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 = $con->prepare("SET foreign_key_checks = 1");
$stmt->execute(); $stmt->execute();
echo "Creating customer\n";
//customer //customer
$customer = new Thelia\Model\Customer(); $customer = new Thelia\Model\Customer();
$customer->createOrUpdate( $customer->createOrUpdate(
@@ -185,6 +198,8 @@ try {
} }
} }
echo "Creating features\n";
//features and features_av //features and features_av
$featureList = array(); $featureList = array();
for($i=0; $i<4; $i++) { for($i=0; $i<4; $i++) {
@@ -208,6 +223,8 @@ try {
} }
} }
echo "Creating attributes\n";
//attributes and attributes_av //attributes and attributes_av
$attributeList = array(); $attributeList = array();
for($i=0; $i<4; $i++) { for($i=0; $i<4; $i++) {
@@ -230,6 +247,8 @@ try {
} }
} }
echo "Creating templates\n";
$template = new Thelia\Model\Template(); $template = new Thelia\Model\Template();
setI18n($faker, $template, array("Name" => 20)); setI18n($faker, $template, array("Name" => 20));
$template->save(); $template->save();
@@ -252,6 +271,8 @@ try {
->save(); ->save();
} }
echo "Creating folders and content\n";
//folders and contents //folders and contents
$contentIdList = array(); $contentIdList = array();
for($i=0; $i<4; $i++) { for($i=0; $i<4; $i++) {
@@ -263,10 +284,14 @@ try {
$folder->save(); $folder->save();
$image = new FolderImage(); $image = new \Thelia\Model\FolderImage();
$image->setFolderId($folder->getId()); $image->setFolderId($folder->getId());
generate_image($image, 1, 'folder', $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; $j<rand(0, 5); $j++) { for($j=1; $j<rand(0, 5); $j++) {
$subfolder = new Thelia\Model\Folder(); $subfolder = new Thelia\Model\Folder();
$subfolder->setParent($folder->getId()); $subfolder->setParent($folder->getId());
@@ -276,10 +301,14 @@ try {
$subfolder->save(); $subfolder->save();
$image = new FolderImage(); $image = new \Thelia\Model\FolderImage();
$image->setFolderId($subfolder->getId()); $image->setFolderId($subfolder->getId());
generate_image($image, 1, 'folder', $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; $k<rand(0, 5); $k++) { for($k=0; $k<rand(0, 5); $k++) {
$content = new Thelia\Model\Content(); $content = new Thelia\Model\Content();
$content->addFolder($subfolder); $content->addFolder($subfolder);
@@ -291,13 +320,20 @@ try {
$contentId = $content->getId(); $contentId = $content->getId();
$contentIdList[] = $contentId; $contentIdList[] = $contentId;
$image = new ContentImage(); $image = new \Thelia\Model\ContentImage();
$image->setContentId($content->getId()); $image->setContentId($contentId);
generate_image($image, 1, 'content', $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 //categories and products
$productIdList = array(); $productIdList = array();
$categoryIdList = array(); $categoryIdList = array();
@@ -405,6 +441,8 @@ try {
} }
} }
echo "Generating coupns fixtures\n";
generateCouponFixtures($thelia); generateCouponFixtures($thelia);
$con->commit(); $con->commit();
@@ -429,10 +467,14 @@ function createProduct($faker, $category, $position, $template, &$productIdList)
$productId = $product->getId(); $productId = $product->getId();
$productIdList[] = $productId; $productIdList[] = $productId;
$image = new ProductImage(); $image = new \Thelia\Model\ProductImage();
$image->setProductId($productId); $image->setProductId($productId);
generate_image($image, 1, 'product', $productId); generate_image($image, 1, 'product', $productId);
$document = new \Thelia\Model\ProductDocument();
$document->setProductId($productId);
generate_document($document, 1, 'product', $productId);
return $product; return $product;
} }
@@ -464,10 +506,14 @@ function createCategory($faker, $parent, $position, &$categoryIdList, $contentId
->save(); ->save();
} }
$image = new CategoryImage(); $image = new \Thelia\Model\CategoryImage();
$image->setCategoryId($categoryId); $image->setCategoryId($categoryId);
generate_image($image, 1, 'category', $categoryId); generate_image($image, 1, 'category', $categoryId);
$document = new \Thelia\Model\CategoryDocument();
$document->setCategoryId($categoryId);
generate_document($document, 1, 'category', $categoryId);
return $category; return $category;
} }
@@ -480,37 +526,36 @@ function generate_image($image, $position, $typeobj, $id) {
->setDescription($faker->text(250)) ->setDescription($faker->text(250))
->setChapo($faker->text(40)) ->setChapo($faker->text(40))
->setPostscriptum($faker->text(40)) ->setPostscriptum($faker->text(40))
->setPosition($position)
->setFile(sprintf("sample-image-%s.png", $id)) ->setFile(sprintf("sample-image-%s.png", $id))
->save() ->save()
; ;
// Generate images // Generate images
$imagine = new Imagine\Gd\Imagine(); $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); $font = $imagine->font(__DIR__.'/faker-assets/FreeSans.ttf', 14, $white);
$tbox = $font->box("THELIA"); $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)); $str = sprintf("%s sample image", ucfirst($typeobj));
$tbox = $font->box($str); $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); $font = $imagine->font(__DIR__.'/faker-assets/FreeSans.ttf', 18, $white);
$str = sprintf("%s ID %d", strtoupper($typeobj), $id); $str = sprintf("%s ID %d", strtoupper($typeobj), $id);
$tbox = $font->box($str); $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() $image->draw()
->line(new Point(0, 0), new Point(319, 0), $white) ->line(new Imagine\Image\Point(0, 0), new Imagine\Image\Point(319, 0), $white)
->line(new Point(319, 0), new Point(319, 239), $white) ->line(new Imagine\Image\Point(319, 0), new Imagine\Image\Point(319, 239), $white)
->line(new Point(319, 239), new Point(0,239), $white) ->line(new Imagine\Image\Point(319, 239), new Imagine\Image\Point(0,239), $white)
->line(new Point(0, 239), new Point(0, 0), $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); $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); $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) ) function setI18n($faker, &$object, $fields = array('Title' => 20, 'Description' => 50) )
{ {
$localeList = $localeList = array('fr_FR', 'en_US', 'es_ES', 'it_IT'); $localeList = $localeList = array('fr_FR', 'en_US', 'es_ES', 'it_IT');

View File

@@ -13,8 +13,11 @@ INSERT INTO `config` (`name`, `value`, `secured`, `hidden`, `created_at`, `updat
('imagine_graphic_driver', 'gd', 0, 0, NOW(), NOW()), ('imagine_graphic_driver', 'gd', 0, 0, NOW(), NOW()),
('default_images_quality_percent', '75', 0, 0, NOW(), NOW()), ('default_images_quality_percent', '75', 0, 0, NOW(), NOW()),
('original_image_delivery_mode', 'symlink', 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()), ('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()), ('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()), ('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()), ('page_not_found_view', '404.html', 0, 0, NOW(), NOW()),
('use_tax_free_amounts', 0, 0, 0, NOW(), NOW()), ('use_tax_free_amounts', 0, 0, 0, NOW(), NOW()),