Merge branch 'master' into template

Conflicts:
	core/lib/Thelia/Core/Event/TheliaEvents.php
	core/lib/Thelia/Model/Base/Module.php
	core/lib/Thelia/Model/Base/ModuleQuery.php
	core/lib/Thelia/Model/Map/ModuleTableMap.php
	install/thelia.sql
	local/config/schema.xml
This commit is contained in:
Etienne Roudeix
2013-09-17 12:44:34 +02:00
70 changed files with 2822 additions and 1461 deletions

View File

@@ -12,14 +12,33 @@ Here is the most recent developed code for the next major version (v2). You can
Most part of the code can possibly change, a large part will be refactor soon, graphical setup does not exist yet.
Requirements
------------
* php 5.4
* apache 2
* mysql 5
If you use Mac OSX, it still doesn't use php 5.4 as default php version... There are many solutions for you :
* use linux (the best one)
* use last MAMP version and put the php bin directory in your path :
```bash
export PATH=/Applications/MAMP/bin/php/php5.4.x/bin/:$PATH
```
* configure a complete development environment : http://php-osx.liip.ch/
* use a virtual machine with vagrant and puppet : https://puphpet.com/
Installation
------------
``` bash
$ git clone --recursive https://github.com/thelia/thelia.git
$ cd thelia
$ wget http://getcomposer.org/composer.phar
$ php composer.phar install
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar install --optimize-autoloader
```
Finish the installation using cli tools :

View File

@@ -36,7 +36,8 @@
"simplepie/simplepie": "dev-master",
"imagine/imagine": "dev-master",
"symfony/icu": "1.0"
"symfony/icu": "1.0",
"swiftmailer/swiftmailer": "5.0.*"
},
"require-dev" : {
"phpunit/phpunit": "3.7.*",
@@ -53,9 +54,5 @@
"": "local/modules/",
"Thelia" : "core/lib/"
}
},
"scripts" : {
"post-update-cmd": "composer dump-autoload -o",
"post-install-cmd": "composer dump-autoload -o"
}
}

51
composer.lock generated
View File

@@ -3,7 +3,7 @@
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
],
"hash": "28dfdc7a840f9e70df422581f82a871f",
"hash": "a40be01c82e68ba0c446dc204d2667da",
"packages": [
{
"name": "imagine/imagine",
@@ -445,6 +445,55 @@
],
"time": "2013-07-02 16:38:47"
},
{
"name": "swiftmailer/swiftmailer",
"version": "v5.0.2",
"source": {
"type": "git",
"url": "https://github.com/swiftmailer/swiftmailer.git",
"reference": "f3917ecef35a4e4d98b303eb9fee463bc983f379"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/f3917ecef35a4e4d98b303eb9fee463bc983f379",
"reference": "f3917ecef35a4e4d98b303eb9fee463bc983f379",
"shasum": ""
},
"require": {
"php": ">=5.2.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.1-dev"
}
},
"autoload": {
"files": [
"lib/swift_required.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Chris Corbyn"
}
],
"description": "Swiftmailer, free feature-rich PHP mailer",
"homepage": "http://swiftmailer.org",
"keywords": [
"mail",
"mailer"
],
"time": "2013-08-30 12:35:21"
},
{
"name": "symfony-cmf/routing",
"version": "1.0.0",

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

@@ -24,52 +24,88 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\Category as CategoryModel;
use Thelia\Model\CategoryQuery;
use Thelia\Model\Category as CategoryModel;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Propel;
use Thelia\Model\Map\CategoryTableMap;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\CategoryUpdateEvent;
use Thelia\Core\Event\CategoryCreateEvent;
use Thelia\Core\Event\CategoryDeleteEvent;
use Thelia\Model\ConfigQuery;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\CategoryToggleVisibilityEvent;
use Thelia\Core\Event\CategoryChangePositionEvent;
class Category extends BaseAction implements EventSubscriberInterface
{
/**
* Create a new category entry
*
* @param CategoryCreateEvent $event
*/
public function create(CategoryCreateEvent $event)
{
$category = new CategoryModel();
$category
->setDispatcher($this->getDispatcher())
->create(
$event->getTitle(),
$event->getParent(),
$event->getLocale()
);
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setParent($event->getParent())
->setVisible($event->getVisible())
->save()
;
$event->setCategory($category);
}
public function update(CategoryChangeEvent $event)
/**
* Change a category
*
* @param CategoryUpdateEvent $event
*/
public function update(CategoryUpdateEvent $event)
{
$search = CategoryQuery::create();
if (null !== $category = CategoryQuery::create()->findPk($event->getCategoryId())) {
$category
->setDispatcher($this->getDispatcher())
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setDescription($event->getDescription())
->setChapo($event->getChapo())
->setPostscriptum($event->getPostscriptum())
->setParent($event->getParent())
->setVisible($event->getVisible())
->save();
$event->setCategory($category);
}
}
/**
* Delete a category
* Delete a category entry
*
* @param ActionEvent $event
* @param CategoryDeleteEvent $event
*/
public function delete(CategoryDeleteEvent $event)
{
$category = CategoryQuery::create()->findPk($event->getCategoryId());
if (null !== $category = CategoryQuery::create()->findPk($event->getCategoryId())) {
if ($category !== null) {
$category
->setDispatcher($this->getDispatcher())
->delete()
;
$category->setDispatcher($this->getDispatcher())->delete();
$event->setCategory($category);
}
}
@@ -80,178 +116,48 @@ class Category extends BaseAction implements EventSubscriberInterface
*/
public function toggleVisibility(CategoryToggleVisibilityEvent $event)
{
$category = CategoryQuery::create()->findPk($event->getCategoryId());
$category = $event->getCategory();
if ($category !== null) {
$category
->setDispatcher($this->getDispatcher())
->setVisible($category->getVisible() ? false : true)
->save()
$category
->setDispatcher($this->getDispatcher())
->setVisible($category->getVisible() ? false : true)
->save()
;
}
/**
* Changes position, selecting absolute ou relative change.
*
* @param CategoryChangePositionEvent $event
*/
public function updatePosition(UpdatePositionEvent $event)
{
if (null !== $category = CategoryQuery::create()->findPk($event->getObjectId())) {
$category->setDispatcher($this->getDispatcher());
$mode = $event->getMode();
if ($mode == UpdatePositionEvent::POSITION_ABSOLUTE)
return $category->changeAbsolutePosition($event->getPosition());
else if ($mode == UpdatePositionEvent::POSITION_UP)
return $category->movePositionUp();
else if ($mode == UpdatePositionEvent::POSITION_DOWN)
return $category->movePositionDown();
}
}
/**
* Changes category position, selecting absolute ou relative change.
*
* @param CategoryChangePositionEvent $event
*/
public function changePosition(CategoryChangePositionEvent $event)
{
if ($event->getMode() == CategoryChangePositionEvent::POSITION_ABSOLUTE)
return $this->changeAbsolutePosition($event);
else
return $this->exchangePosition($event);
}
/**
* Move up or down a category
*
* @param CategoryChangePositionEvent $event
*/
protected function exchangePosition(CategoryChangePositionEvent $event)
{
$category = CategoryQuery::create()->findPk($event->getCategoryId());
if ($category !== null) {
// The current position of the category
$my_position = $category->getPosition();
// Find category to exchange position with
$search = CategoryQuery::create()
->filterByParent($category->getParent());
// Up or down ?
if ($event->getMode() == CategoryChangePositionEvent::POSITION_UP) {
// Find the category immediately before me
$search->filterByPosition(array('max' => $my_position-1))->orderByPosition(Criteria::DESC);
} elseif ($event->getMode() == CategoryChangePositionEvent::POSITION_DOWN) {
// Find the category immediately after me
$search->filterByPosition(array('min' => $my_position+1))->orderByPosition(Criteria::ASC);
} else
return;
$result = $search->findOne();
// If we found the proper category, exchange their positions
if ($result) {
$cnx = Propel::getWriteConnection(CategoryTableMap::DATABASE_NAME);
$cnx->beginTransaction();
try {
$category
->setDispatcher($this->getDispatcher())
->setPosition($result->getPosition())
->save()
;
$result->setPosition($my_position)->save();
$cnx->commit();
} catch (Exception $e) {
$cnx->rollback();
}
}
}
}
/**
* Changes category position
*
* @param CategoryChangePositionEvent $event
*/
protected function changeAbsolutePosition(CategoryChangePositionEvent $event)
{
$category = CategoryQuery::create()->findPk($event->getCategoryId());
if ($category !== null) {
// The required position
$new_position = $event->getPosition();
// The current position
$current_position = $category->getPosition();
if ($new_position != null && $new_position > 0 && $new_position != $current_position) {
// Find categories to offset
$search = CategoryQuery::create()->filterByParent($category->getParent());
if ($new_position > $current_position) {
// The new position is after the current position -> we will offset + 1 all categories located between us and the new position
$search->filterByPosition(array('min' => 1+$current_position, 'max' => $new_position));
$delta = -1;
} else {
// The new position is brefore the current position -> we will offset - 1 all categories located between us and the new position
$search->filterByPosition(array('min' => $new_position, 'max' => $current_position - 1));
$delta = 1;
}
$results = $search->find();
$cnx = Propel::getWriteConnection(CategoryTableMap::DATABASE_NAME);
$cnx->beginTransaction();
try {
foreach ($results as $result) {
$result->setPosition($result->getPosition() + $delta)->save($cnx);
}
$category
->setDispatcher($this->getDispatcher())
->setPosition($new_position)
->save($cnx)
;
$cnx->commit();
} catch (Exception $e) {
$cnx->rollback();
}
}
}
}
/**
* Returns an array of event names this subscriber listens to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* {@inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::CATEGORY_CREATE => array("create", 128),
TheliaEvents::CATEGORY_UPDATE => array("update", 128),
TheliaEvents::CATEGORY_DELETE => array("delete", 128),
TheliaEvents::CATEGORY_CREATE => array("create", 128),
TheliaEvents::CATEGORY_UPDATE => array("update", 128),
TheliaEvents::CATEGORY_DELETE => array("delete", 128),
TheliaEvents::CATEGORY_TOGGLE_VISIBILITY => array("toggleVisibility", 128),
TheliaEvents::CATEGORY_CHANGE_POSITION => array("changePosition", 128),
"action.updateCategoryPositionU" => array("changePositionUp", 128),
"action.updateCategoryPositionDown" => array("changePositionDown", 128),
"action.updateCategoryPosition" => array("changePosition", 128),
TheliaEvents::CATEGORY_UPDATE_POSITION => array("updatePosition", 128)
);
}
}

View File

@@ -22,7 +22,6 @@
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Model\ConfigQuery;
@@ -45,18 +44,9 @@ class Config extends BaseAction implements EventSubscriberInterface
{
$config = new ConfigModel();
$config
->setDispatcher($this->getDispatcher())
->setName($event->getEventName())
->setValue($event->getValue())
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setHidden($event->getHidden())
->setSecured($event->getSecured())
->save()
;
$config->setDispatcher($this->getDispatcher())->setName($event->getEventName())->setValue($event->getValue())
->setLocale($event->getLocale())->setTitle($event->getTitle())->setHidden($event->getHidden())
->setSecured($event->getSecured())->save();
$event->setConfig($config);
}
@@ -70,18 +60,13 @@ class Config extends BaseAction implements EventSubscriberInterface
{
$search = ConfigQuery::create();
if (null !== $config = $search->findOneById($event->getConfigId())) {
if (null !== $config = $search->findPk($event->getConfigId())) {
if ($event->getValue() !== $config->getValue()) {
$config
->setDispatcher($this->getDispatcher())
$config->setDispatcher($this->getDispatcher())->setValue($event->getValue())->save();
->setValue($event->getValue())
->save()
;
$event->setConfig($config);
$event->setConfig($config);
}
}
}
@@ -95,23 +80,12 @@ class Config extends BaseAction implements EventSubscriberInterface
{
$search = ConfigQuery::create();
if (null !== $config = ConfigQuery::create()->findOneById($event->getConfigId())) {
if (null !== $config = ConfigQuery::create()->findPk($event->getConfigId())) {
$config
->setDispatcher($this->getDispatcher())
->setName($event->getEventName())
->setValue($event->getValue())
->setHidden($event->getHidden())
->setSecured($event->getSecured())
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setDescription($event->getDescription())
->setChapo($event->getChapo())
->setPostscriptum($event->getPostscriptum())
->save();
$config->setDispatcher($this->getDispatcher())->setName($event->getEventName())->setValue($event->getValue())
->setHidden($event->getHidden())->setSecured($event->getSecured())->setLocale($event->getLocale())
->setTitle($event->getTitle())->setDescription($event->getDescription())->setChapo($event->getChapo())
->setPostscriptum($event->getPostscriptum())->save();
$event->setConfig($config);
}
@@ -125,14 +99,11 @@ class Config extends BaseAction implements EventSubscriberInterface
public function delete(ConfigDeleteEvent $event)
{
if (null !== ($config = ConfigQuery::create()->findOneById($event->getConfigId()))) {
if (null !== ($config = ConfigQuery::create()->findPk($event->getConfigId()))) {
if (! $config->getSecured()) {
if (!$config->getSecured()) {
$config
->setDispatcher($this->getDispatcher())
->delete()
;
$config->setDispatcher($this->getDispatcher())->delete();
$event->setConfig($config);
}
@@ -145,10 +116,15 @@ class Config extends BaseAction implements EventSubscriberInterface
public static function getSubscribedEvents()
{
return array(
TheliaEvents::CONFIG_CREATE => array("create", 128),
TheliaEvents::CONFIG_SETVALUE => array("setValue", 128),
TheliaEvents::CONFIG_UPDATE => array("modify", 128),
TheliaEvents::CONFIG_DELETE => array("delete", 128),
TheliaEvents::CONFIG_CREATE => array(
"create", 128
), TheliaEvents::CONFIG_SETVALUE => array(
"setValue", 128
), TheliaEvents::CONFIG_UPDATE => array(
"modify", 128
), TheliaEvents::CONFIG_DELETE => array(
"delete", 128
),
);
}
}

View File

@@ -71,7 +71,7 @@ class Currency extends BaseAction implements EventSubscriberInterface
{
$search = CurrencyQuery::create();
if (null !== $currency = CurrencyQuery::create()->findOneById($event->getCurrencyId())) {
if (null !== $currency = CurrencyQuery::create()->findPk($event->getCurrencyId())) {
$currency
->setDispatcher($this->getDispatcher())
@@ -97,7 +97,7 @@ class Currency extends BaseAction implements EventSubscriberInterface
{
$search = CurrencyQuery::create();
if (null !== $currency = CurrencyQuery::create()->findOneById($event->getCurrencyId())) {
if (null !== $currency = CurrencyQuery::create()->findPk($event->getCurrencyId())) {
if ($currency->getByDefault() != $event->getIsDefault()) {
@@ -123,7 +123,7 @@ class Currency extends BaseAction implements EventSubscriberInterface
public function delete(CurrencyDeleteEvent $event)
{
if (null !== ($currency = CurrencyQuery::create()->findOneById($event->getCurrencyId()))) {
if (null !== ($currency = CurrencyQuery::create()->findPk($event->getCurrencyId()))) {
$currency
->setDispatcher($this->getDispatcher())

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>
*
*/
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
*

View File

@@ -70,7 +70,7 @@ class Message extends BaseAction implements EventSubscriberInterface
{
$search = MessageQuery::create();
if (null !== $message = MessageQuery::create()->findOneById($event->getMessageId())) {
if (null !== $message = MessageQuery::create()->findPk($event->getMessageId())) {
$message
->setDispatcher($this->getDispatcher())
@@ -99,7 +99,7 @@ class Message extends BaseAction implements EventSubscriberInterface
public function delete(MessageDeleteEvent $event)
{
if (null !== ($message = MessageQuery::create()->findOneById($event->getMessageId()))) {
if (null !== ($message = MessageQuery::create()->findPk($event->getMessageId()))) {
$message
->setDispatcher($this->getDispatcher())

View File

@@ -51,7 +51,7 @@
<form name="thelia.address.update" class="Thelia\Form\AddressUpdateForm" />
<form name="thelia.admin.category.creation" class="Thelia\Form\CategoryCreationForm"/>
<form name="thelia.admin.category.deletion" class="Thelia\Form\CategoryModificationForm"/>
<form name="thelia.admin.category.modification" class="Thelia\Form\CategoryModificationForm"/>
<form name="thelia.admin.product.creation" class="Thelia\Form\ProductCreationForm"/>
<form name="thelia.admin.product.deletion" class="Thelia\Form\ProductModificationForm"/>
@@ -269,6 +269,10 @@
<tag name="thelia.coupon.addCoupon"/>
</service>
<service id="mailer" class="Thelia\Mailer\MailerFactory">
<argument type="service" id="event_dispatcher"/>
</service>
</services>

View File

@@ -85,7 +85,7 @@
</route>
<route id="admin.categories.set-default" path="/admin/categories/toggle-online">
<default key="_controller">Thelia\Controller\Admin\CategoryController::toggleOnlineAction</default>
<default key="_controller">Thelia\Controller\Admin\CategoryController::setToggleVisibilityAction</default>
</route>
<route id="admin.categories.delete" path="/admin/categories/delete">

View File

@@ -240,6 +240,17 @@ abstract class AbstractCrudController extends BaseAdminController
return null;
}
/**
* Put in this method post object position change processing if required.
*
* @param unknown $deleteEvent the delete event
* @return Response a response, or null to continue normal processing
*/
protected function performAdditionalUpdatePositionAction($positionChangeEvent)
{
return null;
}
/**
* Return the current list order identifier, updating it in the same time.
*/
@@ -309,14 +320,18 @@ abstract class AbstractCrudController extends BaseAdminController
$this->adminLogAppend(sprintf("%s %s (ID %s) created", ucfirst($this->objectName), $this->getObjectLabel($createdObject), $this->getObjectId($createdObject)));
}
$this->performAdditionalCreateAction($createEvent);
$response = $this->performAdditionalCreateAction($createEvent);
// Substitute _ID_ in the URL with the ID of the created object
$successUrl = str_replace('_ID_', $this->getObjectId($createdObject), $creationForm->getSuccessUrl());
// Redirect to the success URL
$this->redirect($successUrl);
if ($response == null) {
// Substitute _ID_ in the URL with the ID of the created object
$successUrl = str_replace('_ID_', $this->getObjectId($createdObject), $creationForm->getSuccessUrl());
// Redirect to the success URL
$this->redirect($successUrl);
}
else {
return $response;
}
}
catch (FormValidationException $ex) {
// Form cannot be validated
@@ -396,16 +411,21 @@ abstract class AbstractCrudController extends BaseAdminController
$this->adminLogAppend(sprintf("%s %s (ID %s) modified", ucfirst($this->objectName), $this->getObjectLabel($changedObject), $this->getObjectId($changedObject)));
}
$this->performAdditionalUpdateAction($changeEvent);
$response = $this->performAdditionalUpdateAction($changeEvent);
// If we have to stay on the same page, do not redirect to the succesUrl,
// just redirect to the edit page again.
if ($this->getRequest()->get('save_mode') == 'stay') {
$this->redirectToEditionTemplate($this->getRequest());
if ($response == null) {
// If we have to stay on the same page, do not redirect to the succesUrl,
// just redirect to the edit page again.
if ($this->getRequest()->get('save_mode') == 'stay') {
$this->redirectToEditionTemplate($this->getRequest());
}
// Redirect to the success URL
$this->redirect($changeForm->getSuccessUrl());
}
else {
return $response;
}
// Redirect to the success URL
$this->redirect($changeForm->getSuccessUrl());
}
catch (FormValidationException $ex) {
// Form cannot be validated
@@ -452,7 +472,14 @@ abstract class AbstractCrudController extends BaseAdminController
return $this->errorPage($ex);
}
$this->redirectToListTemplate();
$response = $this->performAdditionalUpdatePositionAction($event);
if ($response == null) {
$this->redirectToListTemplate();
}
else {
return $response;
}
}
/**
@@ -475,7 +502,7 @@ abstract class AbstractCrudController extends BaseAdminController
return $this->errorPage($ex);
}
$this->redirectToRoute('admin.categories.default');
$this->redirectToListTemplate();
}
/**

View File

@@ -33,7 +33,7 @@ use Thelia\Form\AttributeAvCreationForm;
use Thelia\Core\Event\UpdatePositionEvent;
/**
* Manages attributes-av sent by mail
* Manages attributes-av
*
* @author Franck Allimant <franck@cqfdev.fr>
*/

View File

@@ -37,7 +37,7 @@ use Thelia\Core\Event\AttributeAvUpdateEvent;
use Thelia\Core\Event\AttributeEvent;
/**
* Manages attributes sent by mail
* Manages attributes
*
* @author Franck Allimant <franck@cqfdev.fr>
*/

View File

@@ -23,226 +23,160 @@
namespace Thelia\Controller\Admin;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\CategoryCreateEvent;
use Thelia\Form\CategoryCreationForm;
use Thelia\Core\Event\CategoryDeleteEvent;
use Thelia\Core\Event\CategoryUpdatePositionEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\CategoryUpdateEvent;
use Thelia\Core\Event\CategoryCreateEvent;
use Thelia\Model\CategoryQuery;
use Thelia\Form\CategoryModificationForm;
use Thelia\Form\CategoryCreationForm;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\CategoryToggleVisibilityEvent;
class CategoryController extends BaseAdminController
/**
* Manages categories
*
* @author Franck Allimant <franck@cqfdev.fr>
*/
class CategoryController extends AbstractCrudController
{
/**
* Render the categories list, ensuring the sort order is set.
*
* @return Symfony\Component\HttpFoundation\Response the response
*/
protected function renderList()
{
return $this->render('categories', $this->getTemplateArgs());
public function __construct() {
parent::__construct(
'category',
'manual',
'category_order',
'admin.categories.default',
'admin.categories.create',
'admin.categories.update',
'admin.categories.delete',
TheliaEvents::CATEGORY_CREATE,
TheliaEvents::CATEGORY_UPDATE,
TheliaEvents::CATEGORY_DELETE,
TheliaEvents::CATEGORY_TOGGLE_VISIBILITY,
TheliaEvents::CATEGORY_UPDATE_POSITION
);
}
protected function getTemplateArgs()
{
// Get the category ID
$category_id = $this->getRequest()->get('category_id', 0);
protected function getCreationForm() {
return new CategoryCreationForm($this->getRequest());
}
// Find the current category order
$category_order = $this->getRequest()->get(
'order',
$this->getSession()->get('admin.category_order', 'manual')
protected function getUpdateForm() {
return new CategoryModificationForm($this->getRequest());
}
protected function getCreationEvent($formData) {
$createEvent = new CategoryCreateEvent();
$createEvent
->setTitle($formData['title'])
->setLocale($formData["locale"])
->setParent($formData['parent'])
->setVisible($formData['visible'])
;
return $createEvent;
}
protected function getUpdateEvent($formData) {
$changeEvent = new CategoryUpdateEvent($formData['id']);
// Create and dispatch the change event
$changeEvent
->setLocale($formData['locale'])
->setTitle($formData['title'])
->setChapo($formData['chapo'])
->setDescription($formData['description'])
->setPostscriptum($formData['postscriptum'])
->setVisible($formData['visible'])
->setUrl($formData['url'])
->setParent($formData['parent'])
;
return $changeEvent;
}
protected function createUpdatePositionEvent($positionChangeMode, $positionValue) {
return new UpdatePositionEvent(
$this->getRequest()->get('category_id', null),
$positionChangeMode,
$positionValue
);
}
protected function getDeleteEvent() {
return new CategoryDeleteEvent($this->getRequest()->get('category_id', 0));
}
protected function eventContainsObject($event) {
return $event->hasCategory();
}
protected function hydrateObjectForm($object) {
// Prepare the data that will hydrate the form
$data = array(
'id' => $object->getId(),
'locale' => $object->getLocale(),
'title' => $object->getTitle(),
'chapo' => $object->getChapo(),
'description' => $object->getDescription(),
'postscriptum' => $object->getPostscriptum(),
'visible' => $object->getVisible(),
'url' => $object->getRewritenUrl($this->getCurrentEditionLocale()),
'parent' => $object->getParent()
);
$args = array(
'current_category_id' => $category_id,
'category_order' => $category_order,
// Setup the object form
return new CategoryModificationForm($this->getRequest(), "form", $data);
}
protected function getObjectFromEvent($event) {
return $event->hasCategory() ? $event->getCategory() : null;
}
protected function getExistingObject() {
return CategoryQuery::create()
->joinWithI18n($this->getCurrentEditionLocale())
->findOneById($this->getRequest()->get('category_id', 0));
}
protected function getObjectLabel($object) {
return $object->getTitle();
}
protected function getObjectId($object) {
return $object->getId();
}
protected function renderListTemplate($currentOrder) {
return $this->render('categories',
array(
'category_order' => $currentOrder,
'category_id' => $this->getRequest()->get('category_id', 0)
)
);
}
protected function renderEditionTemplate() {
return $this->render('category-edit', array('category_id' => $this->getRequest()->get('category_id', 0)));
}
protected function redirectToEditionTemplate() {
$this->redirectToRoute(
"admin.categories.update",
array('category_id' => $this->getRequest()->get('category_id', 0))
);
// Store the current sort order in session
$this->getSession()->set('admin.category_order', $category_order);
return $args;
}
/**
* The default action is displaying the categories list.
*
* @return Symfony\Component\HttpFoundation\Response the response
*/
public function defaultAction()
{
if (null !== $response = $this->checkAuth("admin.categories.view")) return $response;
return $this->renderList();
}
/**
* Create a new category object
*
* @return Symfony\Component\HttpFoundation\Response the response
*/
public function createAction()
{
// Check current user authorization
if (null !== $response = $this->checkAuth("admin.categories.create")) return $response;
$error_msg = false;
// Create the Creation Form
$creationForm = new CategoryCreationForm($this->getRequest());
try {
// Validate the form, create the CategoryCreation event and dispatch it.
$form = $this->validateForm($creationForm, "POST");
$data = $form->getData();
$createEvent = new CategoryCreateEvent(
$data["title"],
$data["parent"],
$data["locale"]
);
$this->dispatch(TheliaEvents::CATEGORY_CREATE, $createEvent);
if (! $createEvent->hasCategory()) throw new \LogicException($this->getTranslator()->trans("No category was created."));
$createdObject = $createEvent->getCategory();
// Log category creation
$this->adminLogAppend(sprintf("Category %s (ID %s) created", $createdObject->getTitle(), $createdObject->getId()));
// Substitute _ID_ in the URL with the ID of the created object
$successUrl = str_replace('_ID_', $createdObject->getId(), $creationForm->getSuccessUrl());
// Redirect to the success URL
$this->redirect($successUrl);
} catch (FormValidationException $ex) {
// Form cannot be validated
$error_msg = $this->createStandardFormValidationErrorMessage($ex);
} catch (\Exception $ex) {
// Any other error
$error_msg = $ex->getMessage();
}
$this->setupFormErrorContext("category creation", $error_msg, $creationForm, $ex);
// At this point, the form has error, and should be redisplayed.
return $this->renderList();
}
/**
* Load a category object for modification, and display the edit template.
*
* @return Symfony\Component\HttpFoundation\Response the response
*/
public function changeAction()
{
// Check current user authorization
if (null !== $response = $this->checkAuth("admin.categories.update")) return $response;
// Load the category object
$category = CategoryQuery::create()
->joinWithI18n($this->getCurrentEditionLocale())
->findOneById($this->getRequest()->get('category_id'));
if ($category != null) {
// Prepare the data that will hydrate the form
$data = array(
'id' => $category->getId(),
'locale' => $category->getLocale(),
'title' => $category->getTitle(),
'chapo' => $category->getChapo(),
'description' => $category->getDescription(),
'postscriptum' => $category->getPostscriptum(),
'parent' => $category->getParent(),
'visible' => $category->getVisible() ? true : false,
'url' => $category->getUrl($this->getCurrentEditionLocale())
// tbc !!!
);
// Setup the object form
$changeForm = new CategoryModificationForm($this->getRequest(), "form", $data);
// Pass it to the parser
$this->getParserContext()->addForm($changeForm);
}
// Render the edition template.
return $this->render('category-edit', $this->getTemplateArgs());
}
/**
* Save changes on a modified category object, and either go back to the category list, or stay on the edition page.
*
* @return Symfony\Component\HttpFoundation\Response the response
*/
public function saveChangeAction()
{
// Check current user authorization
if (null !== $response = $this->checkAuth("admin.categories.update")) return $response;
$error_msg = false;
// Create the form from the request
$changeForm = new CategoryModificationForm($this->getRequest());
// Get the category ID
$category_id = $this->getRequest()->get('category_id');
try {
// Check the form against constraints violations
$form = $this->validateForm($changeForm, "POST");
// Get the form field values
$data = $form->getData();
$changeEvent = new CategoryUpdateEvent($data['id']);
// Create and dispatch the change event
$changeEvent
->setCategoryName($data['name'])
->setLocale($data["locale"])
->setSymbol($data['symbol'])
->setCode($data['code'])
->setRate($data['rate'])
;
$this->dispatch(TheliaEvents::CATEGORY_UPDATE, $changeEvent);
if (! $createEvent->hasCategory()) throw new \LogicException($this->getTranslator()->trans("No category was updated."));
// Log category modification
$changedObject = $changeEvent->getCategory();
$this->adminLogAppend(sprintf("Category %s (ID %s) modified", $changedObject->getTitle(), $changedObject->getId()));
// If we have to stay on the same page, do not redirect to the succesUrl,
// just redirect to the edit page again.
if ($this->getRequest()->get('save_mode') == 'stay') {
$this->redirectToRoute(
"admin.categories.update",
array('category_id' => $category_id)
protected function redirectToListTemplate() {
$this->redirectToRoute(
'admin.categories.default',
array('category_id' => $this->getRequest()->get('category_id', 0))
);
}
// Redirect to the success URL
$this->redirect($changeForm->getSuccessUrl());
} catch (FormValidationException $ex) {
// Form cannot be validated
$error_msg = $this->createStandardFormValidationErrorMessage($ex);
} catch (\Exception $ex) {
// Any other error
$error_msg = $ex->getMessage();
}
$this->setupFormErrorContext("category modification", $error_msg, $changeForm, $ex);
// At this point, the form has errors, and should be redisplayed.
return $this->render('category-edit', array('category_id' => $category_id));
}
/**
@@ -253,74 +187,41 @@ class CategoryController extends BaseAdminController
// Check current user authorization
if (null !== $response = $this->checkAuth("admin.categories.update")) return $response;
$changeEvent = new CategoryUpdateEvent($this->getRequest()->get('category_id', 0));
// Create and dispatch the change event
$changeEvent->setIsDefault(true);
$event = new CategoryToggleVisibilityEvent($this->getExistingObject());
try {
$this->dispatch(TheliaEvents::CATEGORY_SET_DEFAULT, $changeEvent);
$this->dispatch(TheliaEvents::CATEGORY_TOGGLE_VISIBILITY, $event);
} catch (\Exception $ex) {
// Any error
return $this->errorPage($ex);
}
$this->redirectToRoute('admin.categories.default');
// Ajax response -> no action
return $this->nullResponse();
}
/**
* Update categoryposition
*/
public function updatePositionAction()
protected function performAdditionalDeleteAction($deleteEvent)
{
// Check current user authorization
if (null !== $response = $this->checkAuth("admin.categories.update")) return $response;
// Redirect to parent category list
$this->redirectToRoute(
'admin.categories.default',
array('category_id' => $deleteEvent->getCategory()->getParent())
);
}
try {
$mode = $this->getRequest()->get('mode', null);
protected function performAdditionalUpdatePositionAction($event)
{
if ($mode == 'up')
$mode = CategoryUpdatePositionEvent::POSITION_UP;
else if ($mode == 'down')
$mode = CategoryUpdatePositionEvent::POSITION_DOWN;
else
$mode = CategoryUpdatePositionEvent::POSITION_ABSOLUTE;
$category = CategoryQuery::create()->findPk($event->getObjectId());
$position = $this->getRequest()->get('position', null);
$event = new CategoryUpdatePositionEvent(
$this->getRequest()->get('category_id', null),
$mode,
$this->getRequest()->get('position', null)
if ($category != null) {
// Redirect to parent category list
$this->redirectToRoute(
'admin.categories.default',
array('category_id' => $category->getParent())
);
$this->dispatch(TheliaEvents::CATEGORY_UPDATE_POSITION, $event);
} catch (\Exception $ex) {
// Any error
return $this->errorPage($ex);
}
$this->redirectToRoute('admin.categories.default');
}
/**
* Delete a category object
*
* @return Symfony\Component\HttpFoundation\Response the response
*/
public function deleteAction()
{
// Check current user authorization
if (null !== $response = $this->checkAuth("admin.categories.delete")) return $response;
// Get the category id, and dispatch the deleted request
$event = new CategoryDeleteEvent($this->getRequest()->get('category_id'));
$this->dispatch(TheliaEvents::CATEGORY_DELETE, $event);
if ($event->hasCategory())
$this->adminLogAppend(sprintf("Category %s (ID %s) deleted", $event->getCategory()->getTitle(), $event->getCategory()->getId()));
$this->redirectToRoute('admin.categories.default');
return null;
}
}

View File

@@ -33,7 +33,7 @@ use Thelia\Form\ConfigCreationForm;
use Thelia\Core\Event\UpdatePositionEvent;
/**
* Manages variables sent by mail
* Manages variables
*
* @author Franck Allimant <franck@cqfdev.fr>
*/

View File

@@ -33,7 +33,7 @@ use Thelia\Form\CurrencyCreationForm;
use Thelia\Core\Event\UpdatePositionEvent;
/**
* Manages currencies sent by mail
* Manages currencies
*
* @author Franck Allimant <franck@cqfdev.fr>
*/

View File

@@ -33,7 +33,7 @@ use Thelia\Form\FeatureAvCreationForm;
use Thelia\Core\Event\UpdatePositionEvent;
/**
* Manages features-av sent by mail
* Manages features-av
*
* @author Franck Allimant <franck@cqfdev.fr>
*/

View File

@@ -37,7 +37,7 @@ use Thelia\Core\Event\FeatureAvUpdateEvent;
use Thelia\Core\Event\FeatureEvent;
/**
* Manages features sent by mail
* Manages features
*
* @author Franck Allimant <franck@cqfdev.fr>
*/

View File

@@ -41,7 +41,7 @@ use Thelia\Core\Event\TemplateAddFeatureEvent;
use Thelia\Core\Event\TemplateDeleteFeatureEvent;
/**
* Manages templates sent by mail
* Manages product templates
*
* @author Franck Allimant <franck@cqfdev.fr>
*/

View File

@@ -281,4 +281,16 @@ class BaseController extends ContainerAware
$this->accessDenied();
}
}
/**
*
* return an instance of \Swift_Mailer with good Transporter configured.
*
* @return \Swift_Mailer
*/
public function getMailer()
{
$mailer = $this->container->get('mailer');
return $mailer->getSwiftMailer();
}
}

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

@@ -28,13 +28,7 @@ class CategoryCreateEvent extends CategoryEvent
protected $title;
protected $parent;
protected $locale;
public function __construct($title, $parent, $locale)
{
$this->title = $title;
$this->parent = $parent;
$this->locale = $locale;
}
protected $visible;
public function getTitle()
{
@@ -71,4 +65,16 @@ class CategoryCreateEvent extends CategoryEvent
return $this;
}
public function getVisible()
{
return $this->visible;
}
public function setVisible($visible)
{
$this->visible = $visible;
return $this;
}
}

View File

@@ -0,0 +1,28 @@
<?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 CategoryToggleVisibilityEvent extends CategoryEvent
{
}

View File

@@ -32,7 +32,6 @@ class CategoryUpdateEvent extends CategoryCreateEvent
protected $postscriptum;
protected $url;
protected $visibility;
protected $parent;
public function __construct($category_id)
@@ -100,18 +99,6 @@ class CategoryUpdateEvent extends CategoryCreateEvent
return $this;
}
public function getVisibility()
{
return $this->visibility;
}
public function setVisibility($visibility)
{
$this->visibility = $visibility;
return $this;
}
public function getParent()
{
return $this->parent;

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;
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;
}
}

View File

@@ -0,0 +1,52 @@
<?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 MailTransporterEvent
* @package Thelia\Core\Event
* @author Manuel Raynaud <mraynaud@openstudio.fr>
*/
class MailTransporterEvent extends ActionEvent {
/**
* @var \Swift_Transport
*/
protected $transporter;
public function setMailerTransporter(\Swift_Transport $transporter)
{
$this->transporter = $transporter;
}
public function getTransporter()
{
return $this->transporter;
}
public function hasTransporter()
{
return null !== $this->transporter;
}
}

View File

@@ -145,50 +145,21 @@ final class TheliaEvents
// -- END ADDRESS MANAGEMENT ---------------------------------------------------------
/**
* Sent once the category creation form has been successfully validated, and before category insertion in the database.
*/
const BEFORE_CREATECATEGORY = "action.before_createcategory";
// -- Categories management -----------------------------------------------
/**
* Create, change or delete a category
*/
const CATEGORY_CREATE = "action.createCategory";
const CATEGORY_UPDATE = "action.updateCategory";
const CATEGORY_DELETE = "action.deleteCategory";
/**
* Toggle category visibility
*/
const CATEGORY_CREATE = "action.createCategory";
const CATEGORY_UPDATE = "action.updateCategory";
const CATEGORY_DELETE = "action.deleteCategory";
const CATEGORY_TOGGLE_VISIBILITY = "action.toggleCategoryVisibility";
const CATEGORY_UPDATE_POSITION = "action.updateCategoryPosition";
/**
* Change category position
*/
const CATEGORY_CHANGE_POSITION = "action.updateCategoryPosition";
/**
* Sent just after a successful insert of a new category in the database.
*/
const BEFORE_CREATECATEGORY = "action.before_createcategory";
const AFTER_CREATECATEGORY = "action.after_createcategory";
/**
* Sent befonre deleting a category
*/
const BEFORE_DELETECATEGORY = "action.before_deletecategory";
/**
* Sent just after a successful delete of a category from the database.
*/
const BEFORE_DELETECATEGORY = "action.before_deletecategory";
const AFTER_DELETECATEGORY = "action.after_deletecategory";
/**
* Sent just before a successful change of a category in the database.
*/
const BEFORE_UPDATECATEGORY = "action.before_updateCategory";
/**
* Sent just after a successful change of a category in the database.
*/
const AFTER_UPDATECATEGORY = "action.after_updateCategory";
/**
@@ -230,6 +201,11 @@ final class TheliaEvents
*/
const IMAGE_PROCESS = "action.processImage";
/**
* Sent on document processing
*/
const DOCUMENT_PROCESS = "action.processDocument";
/**
* Sent on image cache clear request
*/
@@ -437,4 +413,9 @@ final class TheliaEvents
const BEFORE_DELETEFEATURE_AV = "action.before_deleteFeatureAv";
const AFTER_DELETEFEATURE_AV = "action.after_deleteFeatureAv";
/**
* sent when system find a mailer transporter.
*/
const MAILTRANSPORTER_CONFIG = 'action.mailertransporter.config';
}

View File

@@ -173,6 +173,22 @@ class Category extends BaseI18nLoop
$loopResult = new LoopResult($categories);
foreach ($categories as $category) {
// Find previous and next category
$previous = CategoryQuery::create()
->filterByParent($category->getParent())
->filterByPosition($category->getPosition(), Criteria::LESS_THAN)
->orderByPosition(Criteria::DESC)
->findOne()
;
$next = CategoryQuery::create()
->filterByParent($category->getParent())
->filterByPosition($category->getPosition(), Criteria::GREATER_THAN)
->orderByPosition(Criteria::ASC)
->findOne()
;
/*
* no cause pagination lost :
* if ($this->getNotEmpty() && $category->countAllProducts() == 0) continue;
@@ -193,7 +209,13 @@ class Category extends BaseI18nLoop
->set("PRODUCT_COUNT", $category->countChild())
->set("VISIBLE", $category->getVisible() ? "1" : "0")
->set("POSITION", $category->getPosition())
;
->set("HAS_PREVIOUS", $previous != null ? 1 : 0)
->set("HAS_NEXT" , $next != null ? 1 : 0)
->set("PREVIOUS", $previous != null ? $previous->getId() : -1)
->set("NEXT" , $next != null ? $next->getId() : -1)
;
$loopResult->addRow($loopResultRow);
}

View File

@@ -59,7 +59,7 @@ class CategoryTree extends BaseI18nLoop
}
// changement de rubrique
protected function buildCategoryTree($parent, $visible, $level, $max_level, array $exclude, LoopResult &$loopResult)
protected function buildCategoryTree($parent, $visible, $level, $max_level, $exclude, LoopResult &$loopResult)
{
if ($level > $max_level) return;
@@ -73,7 +73,7 @@ class CategoryTree extends BaseI18nLoop
if ($visible != BooleanOrBothType::ANY) $search->filterByVisible($visible);
$search->filterById($exclude, Criteria::NOT_IN);
if ($exclude != null) $search->filterById($exclude, Criteria::NOT_IN);
$search->orderByPosition(Criteria::ASC);

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 !
// $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()."<br />";
//echo "source = ".$this->getSource()."source_id=$source_id, id=$id<br />";
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()
);

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

@@ -39,7 +39,8 @@ class CategoryCreationForm extends BaseForm
"for" => "title"
)
))
->add("parent", "integer", array(
->add("parent", "text", array(
"label" => Translator::getInstance()->trans("Parent category *"),
"constraints" => array(
new NotBlank()
)
@@ -49,6 +50,9 @@ class CategoryCreationForm extends BaseForm
new NotBlank()
)
))
->add("visible", "integer", array(
"label" => Translator::getInstance()->trans("This category is online on the front office.")
))
;
}

View File

@@ -24,6 +24,7 @@ namespace Thelia\Form;
use Symfony\Component\Validator\Constraints\GreaterThan;
use Thelia\Core\Translation\Translator;
use Symfony\Component\Validator\Constraints\NotBlank;
class CategoryModificationForm extends CategoryCreationForm
{
@@ -36,12 +37,13 @@ class CategoryModificationForm extends CategoryCreationForm
$this->formBuilder
->add("id", "hidden", array("constraints" => array(new GreaterThan(array('value' => 0)))))
->add("visible", "checkbox", array(
"label" => Translator::getInstance()->trans("This category is online on the front office.")
->add("url", "text", array(
"label" => Translator::getInstance()->trans("Rewriten URL *"),
"constraints" => array(new NotBlank())
))
;
// Add standard description fields
// Add standard description fields, excluding title and locale, which a re defined in parent class
$this->addStandardDescFields(array('title', 'locale'));
}

View File

@@ -0,0 +1,91 @@
<?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\Mailer;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Thelia\Core\Event\MailTransporterEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\ConfigQuery;
/**
* Class MailerFactory
* @package Thelia\Mailer
* @author Manuel Raynaud <mraynaud@openstudio.fr>
*/
class MailerFactory {
/**
* @var \Swift_Mailer
*/
protected $swiftMailer;
protected $dispatcher;
public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
$transporterEvent = new MailTransporterEvent();
$this->dispatcher->dispatch(TheliaEvents::MAILTRANSPORTER_CONFIG, $transporterEvent);
if($transporterEvent->hasTransporter()) {
$transporter = $transporterEvent->getTransporter();
} else {
if (ConfigQuery::read("smtp.enabled")) {
$transporter = $this->configureSmtp();
} else {
$transporter = \Swift_MailTransport::newInstance();
}
}
$this->swiftMailer = new \Swift_Mailer($transporter);
}
private function configureSmtp()
{
$smtpTransporter = new \Swift_SmtpTransport();
$smtpTransporter->setHost(Configquery::read('smtp.host', 'localhost'))
->setPort(ConfigQuery::read('smtp.host'))
->setEncryption(ConfigQuery::read('smtp.encryption'))
->setUsername(ConfigQuery::read('smtp.username'))
->setPassword(ConfigQuery::read('smtp.password'))
->setAuthMode(ConfigQuery::read('smtp.authmode'))
->setTimeout(ConfigQuery::read('smtp.timeout', 30))
->setSourceIp(ConfigQuery::read('smtp.sourceip'))
;
return $smtpTransporter;
}
public function send(\Swift_Mime_Message $message, &$failedRecipients = null)
{
$this->swiftMailer->send($message, $failedRecipients);
}
public function getSwiftMailer()
{
return $this->swiftMailer;
}
}

View File

@@ -15,6 +15,8 @@ class Category extends BaseCategory
use \Thelia\Model\Tools\PositionManagementTrait;
use \Thelia\Model\Tools\UrlRewritingTrait;
/**
* @return int number of child for the current category
*/
@@ -23,30 +25,12 @@ class Category extends BaseCategory
return CategoryQuery::countChild($this->getId());
}
public function getUrl($locale)
{
return URL::getInstance()->retrieve('category', $this->getId(), $locale)->toString();
}
/**
* Create a new category.
*
* @param string $title the category title
* @param int $parent the ID of the parent category
* @param string $locale the locale of the title
* {@inheritDoc}
*/
public function create($title, $parent, $locale)
{
$this
->setLocale($locale)
->setTitle($title)
->setParent($parent)
->setVisible(1)
->setPosition($this->getNextPosition($parent))
;
$this->save();
}
protected function getRewritenUrlViewName() {
return 'category';
}
/**
*
@@ -71,18 +55,38 @@ class Category extends BaseCategory
return $countProduct;
}
/**
* Calculate next position relative to our parent
*/
protected function addCriteriaToPositionQuery($query) {
$query->filterByParent($this->getParent());
}
/**
* {@inheritDoc}
*/
public function preInsert(ConnectionInterface $con = null)
{
$this->setPosition($this->getNextPosition());
$this->generateRewritenUrl($this->getLocale());
$this->dispatchEvent(TheliaEvents::BEFORE_CREATECATEGORY, new CategoryEvent($this));
return true;
}
/**
* {@inheritDoc}
*/
public function postInsert(ConnectionInterface $con = null)
{
$this->dispatchEvent(TheliaEvents::AFTER_CREATECATEGORY, new CategoryEvent($this));
}
/**
* {@inheritDoc}
*/
public function preUpdate(ConnectionInterface $con = null)
{
$this->dispatchEvent(TheliaEvents::BEFORE_UPDATECATEGORY, new CategoryEvent($this));
@@ -90,16 +94,27 @@ class Category extends BaseCategory
return true;
}
/**
* {@inheritDoc}
*/
public function postUpdate(ConnectionInterface $con = null)
{
$this->dispatchEvent(TheliaEvents::AFTER_UPDATECATEGORY, new CategoryEvent($this));
}
/**
* {@inheritDoc}
*/
public function preDelete(ConnectionInterface $con = null)
{
$this->dispatchEvent(TheliaEvents::BEFORE_DELETECATEGORY, new CategoryEvent($this));
return true;
}
/**
* {@inheritDoc}
*/
public function postDelete(ConnectionInterface $con = null)
{
$this->dispatchEvent(TheliaEvents::AFTER_DELETECATEGORY, new CategoryEvent($this));

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -4,11 +4,41 @@ namespace Thelia\Model;
use Thelia\Model\Base\Content as BaseContent;
use Thelia\Tools\URL;
use Propel\Runtime\Connection\ConnectionInterface;
class Content extends BaseContent
{
public function getUrl($locale)
use \Thelia\Model\Tools\ModelEventDispatcherTrait;
use \Thelia\Model\Tools\PositionManagementTrait;
use \Thelia\Model\Tools\UrlRewritingTrait;
/**
* {@inheritDoc}
*/
protected function getRewritenUrlViewName() {
return 'content';
}
/**
* Calculate next position relative to our parent
*/
protected function addCriteriaToPositionQuery($query) {
// TODO: Find the default folder for this content,
// and generate the position relative to this folder
}
/**
* {@inheritDoc}
*/
public function preInsert(ConnectionInterface $con = null)
{
return URL::getInstance()->retrieve('content', $this->getId(), $locale)->toString();
$this->setPosition($this->getNextPosition());
$this->generateRewritenUrl($this->getLocale());
return true;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -4,9 +4,23 @@ namespace Thelia\Model;
use Thelia\Model\Base\Folder as BaseFolder;
use Thelia\Tools\URL;
use Propel\Runtime\Connection\ConnectionInterface;
class Folder extends BaseFolder
{
use \Thelia\Model\Tools\ModelEventDispatcherTrait;
use \Thelia\Model\Tools\PositionManagementTrait;
use \Thelia\Model\Tools\UrlRewritingTrait;
/**
* {@inheritDoc}
*/
protected function getRewritenUrlViewName() {
return 'folder';
}
/**
* @return int number of contents for the folder
*/
@@ -15,11 +29,6 @@ class Folder extends BaseFolder
return FolderQuery::countChild($this->getId());
}
public function getUrl($locale)
{
return URL::getInstance()->retrieve('folder', $this->getId(), $locale)->toString();
}
/**
*
* count all products for current category and sub categories
@@ -43,4 +52,23 @@ class Folder extends BaseFolder
return $contentsCount;
}
/**
* Calculate next position relative to our parent
*/
protected function addCriteriaToPositionQuery($query) {
$query->filterByParent($this->getParent());
}
/**
* {@inheritDoc}
*/
public function preInsert(ConnectionInterface $con = null)
{
$this->setPosition($this->getNextPosition());
$this->generateRewritenUrl($this->getLocale());
return true;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -6,19 +6,29 @@ use Propel\Runtime\Exception\PropelException;
use Thelia\Model\Base\Product as BaseProduct;
use Thelia\Tools\URL;
use Thelia\TaxEngine\Calculator;
use Propel\Runtime\Connection\ConnectionInterface;
class Product extends BaseProduct
{
public function getUrl($locale)
{
return URL::getInstance()->retrieve('product', $this->getId(), $locale)->toString();
use \Thelia\Model\Tools\ModelEventDispatcherTrait;
use \Thelia\Model\Tools\PositionManagementTrait;
use \Thelia\Model\Tools\UrlRewritingTrait;
/**
* {@inheritDoc}
*/
protected function getRewritenUrlViewName() {
return 'product';
}
public function getRealLowestPrice($virtualColumnName = 'real_lowest_price')
{
try {
$amount = $this->getVirtualColumn($virtualColumnName);
} catch(PropelException $e) {
}
catch(PropelException $e) {
throw new PropelException("Virtual column `$virtualColumnName` does not exist in Product::getRealLowestPrice");
}
@@ -30,4 +40,26 @@ class Product extends BaseProduct
$taxCalculator = new Calculator();
return round($taxCalculator->load($this, $country)->getTaxedPrice($this->getRealLowestPrice()), 2);
}
/**
* Calculate next position relative to our default category
*/
protected function addCriteriaToPositionQuery($query) {
// TODO: Find the default category for this product,
// and generate the position relative to this category
}
/**
* {@inheritDoc}
*/
public function preInsert(ConnectionInterface $con = null)
{
$this->setPosition($this->getNextPosition());
$this->generateRewritenUrl($this->getLocale());
return true;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,78 @@
<?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\Model\Tools;
use Thelia\Tools\URL;
/**
* A trait for managing Rewriten URLs from model classes
*/
trait UrlRewritingTrait {
/**
* @returns string the view name of the rewriten object (e.g., 'category', 'product')
*/
protected abstract function getRewritenUrlViewName();
/**
* Get the object URL for the given locale, rewriten if rewriting is enabled.
*
* @param string $locale a valid locale (e.g. en_US)
*/
public function getUrl($locale)
{
return URL::getInstance()->retrieve($this->getRewritenUrlViewName(), $this->getId(), $locale)->toString();
}
/**
* Generate a rewriten URL from the object title, and store it in the rewriting table
*
* @param string $locale a valid locale (e.g. en_US)
*/
public function generateRewritenUrl($locale)
{
URL::getInstance()->generateRewritenUrl($this->getRewritenUrlViewName(), $this->getId(), $locale, $this->getTitle());
}
/**
* return the rewriten URL for the given locale
*
* @param string $locale a valid locale (e.g. en_US)
*/
public function getRewritenUrl($locale)
{
return "fake url - TODO";
}
/**
* Set the rewriten URL for the given locale
*
* @param string $locale a valid locale (e.g. en_US)
*/
public function setRewritenUrl($locale, $url)
{
// TODO - code me !
return $this;
}
}

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,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();
@@ -183,6 +184,7 @@ class URL
return $this->absoluteUrl($path, $parameters);
}
/**
* Retrieve a rewritten URL from a view, a view id and a locale
*
@@ -261,4 +263,50 @@ class URL
return $this->resolver;
}
protected function sanitize($string, $force_lowercase = true, $alphabetic_only = false)
{
static $strip = array("~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "=", "+", "[", "{", "]",
"}", "\\", "|", ";", ":", "\"", "'", "&#8216;", "&#8217;", "&#8220;", "&#8221;", "&#8211;", "&#8212;",
"—", "–", ",", "<", ".", ">", "/", "?");
$clean = trim(str_replace($strip, "", strip_tags($string)));
$clean = preg_replace('/\s+/', "-", $clean);
$clean = ($alphabetic_only) ? preg_replace("/[^a-zA-Z0-9]/", "", $clean) : $clean ;
return ($force_lowercase) ?
(function_exists('mb_strtolower')) ?
mb_strtolower($clean, 'UTF-8') :
strtolower($clean) :
$clean;
}
/**
* Genenerate the file part of a rewriten URL from a given baseString, using a view, a view id and a locale
*
* @param $view
* @param $viewId
* @param $viewLocale
* @param $baseString the string to be converted in a valid URL
*
* @return A valid file part URL.
*/
public function generateRewritenUrl($view, $viewId, $viewLocale, $baseString)
{
// Borrowed from http://stackoverflow.com/questions/2668854/sanitizing-strings-to-make-them-url-and-filename-safe
// Replace all weird characters with dashes
$string = preg_replace('/[^\w\-~_\.]+/u', '-', $baseString);
// Only allow one dash separator at a time (and make string lowercase)
$cleanString = mb_strtolower(preg_replace('/--+/u', '-', $string), 'UTF-8');
$urlFilePart = $cleanString . ".html";
// TODO :
// check if URL url already exists, and add a numeric suffix, or the like
// insert the URL in the rewriting table
}
}

View File

@@ -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,24 @@ 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();
\Thelia\Model\CouponQuery::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 +200,8 @@ try {
}
}
echo "Creating features\n";
//features and features_av
$featureList = array();
for($i=0; $i<4; $i++) {
@@ -208,6 +225,8 @@ try {
}
}
echo "Creating attributes\n";
//attributes and attributes_av
$attributeList = array();
for($i=0; $i<4; $i++) {
@@ -230,6 +249,8 @@ try {
}
}
echo "Creating templates\n";
$template = new Thelia\Model\Template();
setI18n($faker, $template, array("Name" => 20));
$template->save();
@@ -252,6 +273,8 @@ try {
->save();
}
echo "Creating folders and content\n";
//folders and contents
$contentIdList = array();
for($i=0; $i<4; $i++) {
@@ -263,10 +286,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; $j<rand(0, 5); $j++) {
$subfolder = new Thelia\Model\Folder();
$subfolder->setParent($folder->getId());
@@ -276,10 +303,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; $k<rand(0, 5); $k++) {
$content = new Thelia\Model\Content();
$content->addFolder($subfolder);
@@ -291,13 +322,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 +443,8 @@ try {
}
}
echo "Generating coupns fixtures\n";
generateCouponFixtures($thelia);
$con->commit();
@@ -429,10 +469,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 +508,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 +528,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 +567,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');

View File

@@ -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()),

View File

@@ -8,7 +8,7 @@ echo -e "\n\e[01;34m[INFO] Clearing caches\e[00m\n"
php Thelia cache:clear
echo -e "\n\e[01;34m[INFO] Downloading vendors\e[00m\n"
composer install --prefer-dist
composer install --prefer-dist --optimize-autoloader
cd local/config/

View File

@@ -34,7 +34,7 @@
.topbar {
background: url("@{imgDir}/top.jpg") repeat-x;
background: url("@{imgDir}/top.jpg") repeat-x;
font-weight: bold;
.version-info {
@@ -202,7 +202,7 @@
border-bottom: 2px solid #A5CED8;
margin-bottom: 0.5em;
}
// The action bar on the right
.actions {
text-align: right;
@@ -217,6 +217,10 @@
text-transform: none;
}
}
.inner-actions {
margin-top: 0.5em;
}
}
// The overall form container

View File

@@ -1,396 +1,260 @@
{extends file="admin-layout.tpl"}
{block name="page-title"}{intl l='Catalog'}{/block}
{block name="page-title"}{intl l='Categories'}{/block}
{block name="check-permissions"}admin.catalog.view{/block}
{block name="check-permissions"}admin.categories.view{/block}
{block name="main-content"}
<div class="catalog">
<div id="wrapper" class="container">
<div class="categories">
{include file="includes/catalog-breadcrumb.html"}
<div id="wrapper" class="container">
{module_include location='catalog_top'}
{include file="includes/catalog-breadcrumb.html"}
<div class="row">
<div class="col-md-12">
<div class="general-block-decorator">
<table class="table table-striped table-condensed" id="category_list">
<caption>
{* display parent category name, and get current cat ID *}
{loop name="category_title" type="category" visible="*" id=$current_category_id}
{intl l="Categories in %cat" cat=$TITLE}
{$cat_id = $ID}
{/loop}
{elseloop rel="category_title"}
{intl l="Top level categories"}
{/elseloop}
{module_include location='categories_top'}
{module_include location='category_list_caption'}
<div class="row">
<div class="col-md-12">
<div class="general-block-decorator">
<table class="table table-striped table-condensed" id="category_list">
<caption>
{* display parent category name, and get current cat ID *}
{loop name="category_title" type="category" visible="*" id=$category_id}
{intl l="Categories in %cat" cat=$TITLE}
{$cat_id = $ID}
{/loop}
{elseloop rel="category_title"}
{intl l="Top level categories"}
{/elseloop}
{loop type="auth" name="can_create" roles="ADMIN" permissions="admin.categories.create"}
<a class="btn btn-default btn-primary action-btn" title="{intl l='Add a new category'}" href="#add_category_dialog" data-toggle="modal">
<span class="glyphicon glyphicon-plus-sign"></span>
</a>
{/loop}
</caption>
{module_include location='category_list_caption'}
{ifloop rel="category_list"}
<thead>
<tr>
<th class="object-title">
{admin_sortable_header
current_order=$category_order
order='id'
reverse_order='id_reverse'
path={url path='/admin/catalog' id_category=$current_category_id}
label="{intl l='ID'}"
}
</th>
{loop type="auth" name="can_create" roles="ADMIN" permissions="admin.categories.create"}
<a class="btn btn-default btn-primary action-btn" title="{intl l='Add a new category'}" href="#category_creation_dialog" data-toggle="modal">
<span class="glyphicon glyphicon-plus-sign"></span>
</a>
{/loop}
</caption>
<th class="object-image">&nbsp;</th>
{ifloop rel="category_list"}
<thead>
<tr>
<th class="object-title">
{admin_sortable_header
current_order=$category_order
order='id'
reverse_order='id_reverse'
path={url path='/admin/categories' id_category=$category_id}
request_parameter_name='category_order'
label="{intl l='ID'}"
}
</th>
<th class="object-title">
{admin_sortable_header
current_order=$category_order
order='alpha'
reverse_order='alpha_reverse'
path={url path='/admin/catalog' id_category=$current_category_id}
label="{intl l='Category title'}"
}
</th>
<th class="object-image">&nbsp;</th>
{module_include location='category_list_header'}
<th class="object-title">
{admin_sortable_header
current_order=$category_order
order='alpha'
reverse_order='alpha_reverse'
path={url path='/admin/categories' id_category=$category_id}
request_parameter_name='category_order'
label="{intl l='Category title'}"
}
</th>
<th>
{admin_sortable_header
current_order=$category_order
order='visible'
reverse_order='visible_reverse'
path={url path='/admin/catalog' id_category=$current_category_id}
label="{intl l='Online'}"
}
</th>
{module_include location='category_list_header'}
<th>
{admin_sortable_header
current_order=$category_order
order='manual'
reverse_order='manual_reverse'
path={url path='/admin/catalog' id_category=$current_category_id}
label="{intl l='Position'}"
}
</th>
<th>
{admin_sortable_header
current_order=$category_order
order='visible'
reverse_order='visible_reverse'
path={url path='/admin/categories' id_category=$category_id}
request_parameter_name='category_order'
label="{intl l='Online'}"
}
</th>
<th class="actions">{intl l='Actions'}</th>
</tr>
</thead>
<th>
{admin_sortable_header
current_order=$category_order
order='manual'
reverse_order='manual_reverse'
path={url path='/admin/categories' id_category=$category_id}
request_parameter_name='category_order'
label="{intl l='Position'}"
}
</th>
<tbody>
{loop name="category_list" type="category" visible="*" parent=$current_category_id order=$category_order backend_context="1" lang=$lang_id}
<tr>
<td>{$ID}</td>
<th class="actions">{intl l='Actions'}</th>
</tr>
</thead>
<td>
{loop type="image" name="cat_image" source="category" source_id="$ID" limit="1" width="50" height="50" resize_mode="crop" backend_context="1"}
<a href="{url path='admin/catalog' category_id=$ID}" title="{intl l='Browse this category'}"><img class="img-thumbnail" src="{$IMAGE_URL}" alt="{$TITLE}" /></a>
{/loop}
</td>
<tbody>
{loop name="category_list" type="category" visible="*" parent=$category_id order=$category_order backend_context="1" lang=$lang_id}
<tr>
<td>{$ID}</td>
<td class="object-title">
<a href="{url path='admin/catalog' category_id=$ID}" title="{intl l='Browse this category'}">
{$TITLE}
</a>
</td>
{module_include location='category_list_row'}
<td>
{loop type="auth" name="can_change" roles="ADMIN" permissions="admin.categories.edit"}
<div class="make-switch switch-small" data-on="success" data-off="danger" data-on-label="<i class='glyphicon glyphicon-ok'></i>" data-off-label="<i class='glyphicon glyphicon-remove'></i>">
<input type="checkbox" data-id="{$ID}" class="categoryVisibleToggle" {if $VISIBLE == 1}checked="checked"{/if}>
</div>
{/loop}
{elseloop rel="can_change"}
<div class="make-switch switch-small" data-on="success" data-off="danger" data-on-label="<i class='glyphicon glyphicon-ok'></i>" data-off-label="<i class='glyphicon glyphicon-remove'></i>">
<input type="checkbox" class="disabled" disabled="disabled" {if $VISIBLE == 1}checked="checked"{/if}>
</div>
{/elseloop}
</td>
<td>
{admin_position_block
permission="admin.categories.edit"
path={url path='admin/category/update-position' category_id=$ID}
url_parameter="category_id"
in_place_edit_class="categoryPositionChange"
position=$POSITION
id=$ID
}
</td>
<td>
<div class="btn-group">
<a class="btn btn-default btn-xs" title="{intl l='Browse this category'}" href="{url path='admin/category' category_id=$ID}"><i class="glyphicon glyphicon-folder-open"></i></a>
{loop type="auth" name="can_change" roles="ADMIN" permissions="admin.categories.edit"}
<a class="btn btn-default btn-xs" title="{intl l='Edit this category'}" href="{url path='/admin/categories/update' category_id=$ID}"><i class="glyphicon glyphicon-edit"></i></a>
{/loop}
{loop type="auth" name="can_delete" roles="ADMIN" permissions="admin.categories.delete"}
<a class="btn btn-default btn-xs category-delete" title="{intl l='Delete this category and all its contents'}" href="#delete_category_dialog" data-id="{$ID}" data-toggle="modal"><i class="glyphicon glyphicon-trash"></i></a>
{/loop}
</div>
</td>
</tr>
{/loop}
</tbody>
{/ifloop}
{elseloop rel="category_list"}
<thead>
<tr>
<td class="message">
<div class="alert alert-info">
{loop type="auth" name="can_create" roles="ADMIN" permissions="admin.categories.create"}
{intl l="This category has no sub-categories. To create a new one, click the + button above."}
{/loop}
{elseloop rel="can_create"}
{intl l="This category has no sub-categories."}
{/elseloop}
</div>
</td>
</tr>
</thead>
{/elseloop}
</table>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="general-block-decorator">
<table class="table table-striped table-condensed">
<caption>
{* display parent category name *}
{loop name="category_title" type="category" visible="*" id=$current_category_id}
{intl l="Products in %cat" cat=$TITLE}
{/loop}
{elseloop rel="category_title"}
{intl l="Top level Products"}
{/elseloop}
{module_include location='product_list_caption'}
<a class="btn btn-default btn-primary action-btn" title="{intl l='Add a new product'}" href="#productAddModal" data-toggle="modal">
<span class="glyphicon glyphicon-plus-sign"></span>
</a>
</caption>
{ifloop rel="product_list"}
<thead>
<tr>
<th class="object-title">
{admin_sortable_header
current_order=$product_order
order='id'
reverse_order='id_reverse'
path={url path='/admin/product' category_id=$current_category_id}
label="{intl l='ID'}"
}
<th>&nbsp;</th>
<th class="object-title">
{admin_sortable_header
current_order=$product_order
order='ref'
reverse_order='ref_reverse'
path={url path='/admin/product' category_id=$current_category_id}
label="{intl l='Reference'}"
}
</th>
<th class="object-title">
{admin_sortable_header
current_order=$product_order
order='alpha'
reverse_order='alpha_reverse'
path={url path='/admin/product' category_id=$current_category_id}
label="{intl l='Product title'}"
}
{module_include location='product_list_header'}
<th>
{admin_sortable_header
current_order=$product_order
order='visible'
reverse_order='visible_reverse'
path={url path='/admin/product' category_id=$current_category_id}
label="{intl l='Online'}"
}
</th>
<th>
{admin_sortable_header
current_order=$product_order
order='manual'
reverse_order='manual_reverse'
path={url path='/admin/product' category_id=$current_category_id}
label="{intl l='Position'}"
}
</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{loop name="product_list" type="product" category=$current_category_id order="manual"}
<tr>
<td>{$ID}</td>
<td>
{loop type="image" name="cat_image" source="product" source_id="$ID" limit="1" width="50" height="50" resize_mode="crop" backend_context="1"}
<a href="{url path='admin/product/edit' id=$ID}" title="{intl l='Edit this product'}">
<img src="{$IMAGE_URL}" alt="{$TITLE}" />
</a>
{/loop}
<td class="object-title"><a href="{url path='admin/product/edit' id=$ID}" title="{intl l='Edit this product'}">{$REF}</a></td>
<td class="object-title"><a href="{url path='admin/product/edit' id=$ID}" title="{intl l='Edit this product'}">{$TITLE}</a></td>
{module_include location='product_list_row'}
<td>
{loop type="auth" name="can_change" roles="ADMIN" permissions="admin.products.edit"}
<div class="make-switch switch-small" data-on="success" data-off="danger" data-on-label="<i class='glyphicon glyphicon-ok'></i>" data-off-label="<i class='glyphicon glyphicon-remove'></i>">
<input type="checkbox" data-id="{$ID}" class="productVisibleToggle" {if $VISIBLE == 1}checked="checked"{/if}>
</div>
{/loop}
{elseloop rel="can_change"}
<div class="make-switch switch-small" data-on="success" data-off="danger" data-on-label="<i class='glyphicon glyphicon-ok'></i>" data-off-label="<i class='glyphicon glyphicon-remove'></i>">
<input type="checkbox" data-id="{$ID}" class="displayToggle" {if $VISIBLE == 1}checked="checked"{/if}>
</div>
{/elseloop}
</td>
<td>
{admin_position_block
permission="admin.product.edit"
path={url path='admin/product' category_id=$ID}
url_parameter="product_id"
in_place_edit_class="productPositionChange"
position=$POSITION
id=$ID
}
</td>
<td>
<div class="btn-group">
{loop type="auth" name="can_change" roles="ADMIN" permissions="admin.product.edit"}
<a class="btn btn-default btn-xs" title="{intl l='Edit this product'}" href="{url path='admin/product/edit' product_id=$ID}"><i class="glyphicon glyphicon-edit"></i></a>
{/loop}
{loop type="auth" name="can_change" roles="ADMIN" permissions="admin.product.delete"}
<a class="btn btn-default btn-xs product-delete" title="{intl l='Delete this product'}" href="{url path='admin/product/delete' id=$ID}"><i class="glyphicon glyphicon-trash"></i></a>
<td>
{loop type="image" name="cat_image" source="category" source_id="$ID" limit="1" width="50" height="50" resize_mode="crop" backend_context="1"}
<a href="{url path='admin/catalog' category_id=$ID}" title="{intl l='Browse this category'}"><img class="img-thumbnail" src="{$IMAGE_URL}" alt="{$TITLE}" /></a>
{/loop}
</div>
</td>
</tr>
{/loop}
</tbody>
{/ifloop}
</td>
{elseloop rel="product_list"}
<thead>
<tr>
<td class="message"><div class="alert alert-info">{intl l="This category doesn't have any products. To add a new product, <strong>click the + button</strong> above."}</div></td>
</tr>
</thead>
{/elseloop}
</table>
</div>
</div>
</div>
<td class="object-title">
<a href="{url path='admin/catalog' category_id=$ID}" title="{intl l='Browse this category'}">
{$TITLE}
</a>
</td>
{module_include location='catalog_bottom'}
</div>
</div>
{module_include location='category_list_row'}
{* Adding a new Category *}
<td>
{loop type="auth" name="can_change" roles="ADMIN" permissions="admin.categories.edit"}
<div class="make-switch switch-small categoryVisibleToggle" data-id="{$ID}" data-on="success" data-off="danger" data-on-label="<i class='glyphicon glyphicon-ok'></i>" data-off-label="<i class='glyphicon glyphicon-remove'></i>">
<input type="checkbox" class="categoryVisibleToggle" {if $VISIBLE == 1}checked="checked"{/if}>
</div>
{/loop}
{form name="thelia.admin.category.creation"}
{elseloop rel="can_change"}
<div class="make-switch switch-small" data-on="success" data-off="danger" data-on-label="<i class='glyphicon glyphicon-ok'></i>" data-off-label="<i class='glyphicon glyphicon-remove'></i>">
<input type="checkbox" class="disabled" disabled="disabled" {if $VISIBLE == 1}checked="checked"{/if}>
</div>
{/elseloop}
</td>
<td>
{admin_position_block
permission="admin.categories.edit"
path={url path='admin/categories/update-position' category_id=$ID}
url_parameter="category_id"
in_place_edit_class="categoryPositionChange"
position=$POSITION
id=$ID
}
</td>
<td class="actions">
<div class="btn-group">
<a class="btn btn-default btn-xs" title="{intl l='Browse this category'}" href="{url path='admin/categories' category_id=$ID}"><i class="glyphicon glyphicon-folder-open"></i></a>
{loop type="auth" name="can_change" roles="ADMIN" permissions="admin.categories.edit"}
<a class="btn btn-default btn-xs" title="{intl l='Edit this category'}" href="{url path='/admin/categories/update' category_id=$ID}"><i class="glyphicon glyphicon-edit"></i></a>
{/loop}
{loop type="auth" name="can_delete" roles="ADMIN" permissions="admin.categories.delete"}
<a class="btn btn-default btn-xs category-delete" title="{intl l='Delete this category and all its contents'}" href="#category_delete_dialog" data-id="{$ID}" data-toggle="modal"><i class="glyphicon glyphicon-trash"></i></a>
{/loop}
</div>
</td>
</tr>
{/loop}
</tbody>
{/ifloop}
{elseloop rel="category_list"}
<thead>
<tr>
<td class="message">
<div class="alert alert-info">
{loop type="auth" name="can_create" roles="ADMIN" permissions="admin.categories.create"}
{intl l="This category has no sub-categories. To create a new one, click the + button above."}
{/loop}
{elseloop rel="can_create"}
{intl l="This category has no sub-categories."}
{/elseloop}
</div>
</td>
</tr>
</thead>
{/elseloop}
</table>
</div>
</div>
</div>
{module_include location='categories_bottom'}
</div>
</div>
{* Adding a new category *}
{form name="thelia.admin.category.creation"}
{* Capture the dialog body, to pass it to the generic dialog *}
{capture "category_creation_dialog"}
{form_hidden_fields form=$form}
{form_field form=$form field='success_url'}
{* on success, redirect to the edition page, _ID_ is replaced with the created object ID, see controller *}
<input type="hidden" name="{$name}" value="{url path='/admin/categories/update' category_id='_ID_'}" />
{/form_field}
{form_field form=$form field='success_url'}
{* on success, redirect to the edition page, _ID_ is replaced with the created object ID, see controller *}
<input type="hidden" name="{$name}" value="{url path='/admin/categories/update' category_id='_ID_'}" />
{/form_field}
{form_field form=$form field='parent'}
<input type="hidden" name="{$name}" value="{$current_category_id}" />
{/form_field}
{form_field form=$form field='parent'}
<input type="hidden" name="{$name}" value="{$category_id}" />
{/form_field}
{form_field form=$form field='title'}
<div class="form-group {if $error}has-error{/if}">
<label for="{$label_attr.for}" class="control-label">{$label} : </label>
{form_field form=$form field='title'}
<div class="form-group {if $error}has-error{/if}">
<label for="{$label_attr.for}" class="control-label">{$label} : </label>
{loop type="lang" name="default-lang" default_only="1"}
<div class="input-group">
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" class="form-control" value="{$value}" title="{intl l='Currency name'}" placeholder="{intl l='Name'}">
<span class="input-group-addon"><img src="{image file="assets/img/flags/{$CODE}.gif"}" alt="$TITLE" /></span>
</div>
{loop type="lang" name="default-lang" default_only="1"}
<div class="help-block">{intl l='Enter here the category name in the default language (%title)' title="$TITLE"}</div>
<div class="input-group">
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" class="form-control" value="{$value}" title="{intl l='Currency name'}" placeholder="{intl l='Name'}">
<span class="input-group-addon"><img src="{image file="assets/img/flags/{$CODE}.gif"}" alt="$TITLE" /></span>
</div>
{* Switch edition to the current locale *}
<input type="hidden" name="edit_language_id" value="{$ID}" />
<div class="help-block">{intl l='Enter here the category name in the default language (%title)' title="$TITLE"}</div>
{form_field form=$form field='locale'}
<input type="hidden" name="{$name}" value="{$LOCALE}" />
{/form_field}
{/loop}
</div>
{/form_field}
{* Switch edition to the current locale *}
<input type="hidden" name="edit_language_id" value="{$ID}" />
{form_field form=$form field='locale'}
<input type="hidden" name="{$name}" value="{$LOCALE}" />
{/form_field}
{/loop}
</div>
{/form_field}
{form_field form=$form field='visible'}
<div class="form-group {if $error}has-error{/if}">
<div class="checkbox">
<label>
<input type="checkbox" id="{$label_attr.for}" name="{$name}" value="1" checked="checked">
{$label}
</label>
</div>
</div>
{/form_field}
{module_include location='category_create_form'}
{/capture}
{/capture}
{include
file = "includes/generic-create-dialog.html"
{include
file = "includes/generic-create-dialog.html"
dialog_id = "add_category_dialog"
dialog_title = {intl l="Create a new category"}
dialog_body = {$smarty.capture.category_creation_dialog nofilter}
dialog_id = "category_creation_dialog"
dialog_title = {intl l="Create a new category"}
dialog_body = {$smarty.capture.category_creation_dialog nofilter}
dialog_ok_label = {intl l="Create this category"}
dialog_cancel_label = {intl l="Cancel"}
dialog_ok_label = {intl l="Create this category"}
form_action = {url path='/admin/categories/create'}
form_enctype = {form_enctype form=$form}
form_error_message = $form_error_message
}
form_action = {url path='/admin/categories/create'}
form_enctype = {form_enctype form=$form}
form_error_message = $form_error_message
}
{/form}
{* Delete category confirmation dialog *}
{* Delete confirmation dialog *}
{capture "category_delete_dialog"}
<input type="hidden" name="current_category_id" value="{$current_category_id}" />
<input type="hidden" name="category_id" id="delete_category_id" value"" />
<input type="hidden" name="category_id" id="category_delete_id" value="" />
{module_include location='category_delete_form'}
@@ -399,9 +263,9 @@
{include
file = "includes/generic-confirm-dialog.html"
dialog_id = "delete_category_dialog"
dialog_title = {intl l="Delete a category"}
dialog_message = {intl l="Do you really want to delete this category, and <strong>all</strong> its contents ?"}
dialog_id = "category_delete_dialog"
dialog_title = {intl l="Delete category"}
dialog_message = {intl l="Do you really want to delete this category and all its content ?"}
form_action = {url path='/admin/categories/delete'}
form_content = {$smarty.capture.category_delete_dialog nofilter}
@@ -410,108 +274,76 @@
{block name="javascript-initialization"}
{javascripts file='assets/js/bootstrap-switch/bootstrap-switch.js'}
<script src="{$asset_url}"></script>
{/javascripts}
{javascripts file='assets/js/bootstrap-switch/bootstrap-switch.js'}
<script src="{$asset_url}"></script>
{/javascripts}
{javascripts file='assets/js/bootstrap-editable/bootstrap-editable.js'}
<script src="{$asset_url}"></script>
{/javascripts}
{javascripts file='assets/js/bootstrap-editable/bootstrap-editable.js'}
<script src="{$asset_url}"></script>
{/javascripts}
<script>
$(function() {
<script>
$(function() {
// JS stuff for category creation form
{include
file = "includes/generic-js-dialog.html"
// Set proper category ID in delete from
$('a.category-delete').click(function(ev) {
$('#category_delete_id').val($(this).data('id'));
});
dialog_id = "add_category_dialog"
form_name = "thelia.admin.category.creation"
}
// JS stuff for creation form
{include
file = "includes/generic-js-dialog.html"
dialog_id = "category_creation_dialog"
form_name = "thelia.admin.category.creation"
}
// JS stuff for product creation form
{include
file = "includes/generic-js-dialog.html"
dialog_id = "add_product_dialog"
form_name = "thelia.admin.product.creation"
}
{* Toggle object visibility *}
{* Set the proper ID in the delete confirmation dialog *}
$(".categoryVisibleToggle").on('switch-change', function(event, data) {
console.log("yaya");
$.ajax({
url : "{url path='admin/categories/toggle-online'}",
data : {
category_id : $(this).data('id'),
action : 'visibilityToggle'
}
});
});
$('a.category-delete').click(function(ev) {
$('#delete_category_id').val($(this).data('id'));
});
{* Inline editing of object position using bootstrap-editable *}
$('a.product-delete').click(function(ev) {
$('#delete_product_id').val($(this).data('id'));
});
$('.categoryPositionChange').editable({
type : 'text',
title : '{intl l="Enter new category position"}',
mode : 'popup',
inputclass : 'input-mini',
placement : 'left',
success : function(response, newValue) {
// The URL template
var url = "{url path='/admin/categories/update-position' category_id='__ID__' position='__POS__'}";
// Perform subtitutions
url = url.replace('__ID__', $(this).data('id'))
.replace('__POS__', newValue);
{* Toggle object visibility *}
// Reload the page
location.href = url;
}
});
{* Change default status *}
$('.change-default').change(function(ev) {
var url = "{url path='/admin/categories/set-default' category_id='__ID__'}";
// Perform ID subtitutions
url = url.replace('__ID__', $(this).val());
// Reload the page
location.href = url;
});
$(".categoryVisibleToggle").click(function() {
$.ajax({
url : "{url path='admin/categories/toggle-online'}",
data : {
category_id : $(this).data('id'),
action : 'visibilityToggle'
}
});
});
$(".productVisibleToggle").click(function() {
$.ajax({
url : "{url path='admin/products/toggle-online'}",
data : {
category_id : $(this).data('id'),
action : 'visibilityToggle'
}
});
});
{* Inline editing of object position using bootstrap-editable *}
$('.categoryPositionChange').editable({
type : 'text',
title : '{intl l="Enter new category position"}',
mode : 'popup',
inputclass : 'input-mini',
placement : 'left',
success : function(response, newValue) {
// The URL template
var url = "{url path='/admin/categories/update-position' category_id='__ID__' position='__POS__'}";
// Perform subtitutions
url = url.replace('__ID__', $(this).data('id'))
.replace('__POS__', newValue);
// Reload the page
location.href = url;
}
});
$('.productPositionChange').editable({
type : 'text',
title : '{intl l="Enter new product position"}',
mode : 'popup',
inputclass : 'input-mini',
placement : 'left',
success : function(response, newValue) {
// The URL template
var url = "{url path='/admin/products/update-position' product_id='__ID__' position='__POS__'}";
// Perform subtitutions
url = url.replace('__ID__', $(this).data('id'))
.replace('__POS__', newValue);
// Reload the page
location.href = url;
}
});
})
</script>
</script>
{/block}

View File

@@ -8,206 +8,154 @@
<div class="catalog edit-category">
<div id="wrapper" class="container">
{include file="includes/catalog-breadcrumb.html"}
{include file="includes/catalog-breadcrumb.html" editing_category="true"}
<div class="row">
{loop name="category_edit" type="category" visible="*" id="{$current_category_id}" backend_context="1" lang="$edit_language_id"}
{loop name="category_edit" type="category" visible="*" id="{$category_id}" backend_context="1" lang="$edit_language_id"}
<div class="col-md-12 general-block-decorator">
<div class="row">
<div class="col-md-7 title">
{intl l='Edit category'}
{intl l='Edit category %title' title=$TITLE}
</div>
<div class="col-md-5 actions">
<button class="btn btn-default" title="{intl l='Edit previous category'}"><i class="glyphicon glyphicon-arrow-left"></i></button>
<button class="btn btn-default" title="{intl l='Preview category page'}"><i class="glyphicon glyphicon-eye-open"></i></button>
<button class="btn btn-default" title="{intl l='Edit next category'}"><i class="glyphicon glyphicon-arrow-right"></i></button>
{if $HAS_PREVIOUS != 0}
<a href="{url path='/admin/categories/update' category_id=$PREVIOUS}" class="btn btn-default" title="{intl l='Edit previous category'}"><span class="glyphicon glyphicon-arrow-left"></span></a>
{else}
<a href="#" disabled="disabled" class="btn btn-default"><span class="glyphicon glyphicon-arrow-left"></span></a>
{/if}
<a href="{$URL}" target="_blank" class="btn btn-default" title="{intl l='Preview category page'}"><span class="glyphicon glyphicon-eye-open"></span></a>
{if $HAS_NEXT != 0}
<a href="{url path='/admin/categories/update' category_id=$NEXT}" class="btn btn-default" title="{intl l='Edit next category'}"><span class="glyphicon glyphicon-arrow-right"></span></a>
{else}
<a href="#" disabled="disabled" class="btn btn-default"><span class="glyphicon glyphicon-arrow-right"></span></a>
{/if}
</div>
</div>
<form method="post">
<div class="row">
<div class="col-md-12">
<div class="tabbable">
<ul class="nav nav-tabs admin-tabs" id="tabbed_menu">
<li class="active">
<a href="#general_description">{intl l="General description"}</a>
</li>
<ul class="nav nav-tabs">
<li class="active"><a href="#general_description" data-toggle="tab">{intl l="General description"}</a></li>
<li>
<a href="#details">{intl l="Details"}</a>
</li>
<li>
<a href="#images">{intl l="Images"}</a>
</li>
<li>
<a href="#documents">{intl l="Documents"}</a>
</li>
<li>
<a href="#modules">{intl l="Modules"}</a>
</li>
</ul>
<li><a href="#details" data-toggle="tab">{intl l="Details"}</a></li>
<li><a href="#images" data-toggle="tab">{intl l="Images"}</a></li>
<li><a href="#documents" data-toggle="tab">{intl l="Documents"}</a></li>
<li><a href="#modules" data-toggle="tab">{intl l="Modules"}</a></li>
</ul>
<div class="tab-content">
<div class="tab-content">
<div class="tab-pane active form-container" id="general_description">
<div class="form-horizontal col-md-12">
<fieldset>
<div class="tab-pane fade active in" id="general_description">
{include file="includes/inner-form-toolbar.html" close_url="{url path='admin/catalog/category/edit' category_id=$current_category_id}"}
<div class="form-container">
<div class="row">
<div class="col-md-12">
<div class="control-group">
<label class="control-label">
{intl l='Title *'}
</label>
{form name="thelia.admin.category.modification"}
<form method="POST" action="{url path='/admin/categories/save'}" {form_enctype form=$form} class="clearfix">
<div class="controls">
<input type="text" required="required" title="{intl l='Category title'}" placeholder="{intl l='Category title'}" class="input-block-level" value="{$TITLE|htmlspecialchars}">
</div>
</div>
{include file="includes/inner-form-toolbar.html" close_url="{url path='/admin/categories' category_id=$category_id}"}
<div class="control-group">
<label class="control-label">
{intl l='Summary'}
<span class="label-help-block">{intl l="A short category description, used when a summary or an introduction is required"}</span>
</label>
{* Be sure to get the category ID, even if the form could not be validated *}
<input type="hidden" name="category_id" value="{$category_id}" />
<div class="controls">
<textarea name="summary" rows="3" title="{intl l='Short category description'}" placeholder="{intl l='Short category description'}" class="input-block-level">{$CHAPO|htmlspecialchars}</textarea>
</div>
</div>
{form_hidden_fields form=$form}
<div class="control-group">
<label class="control-label">
{intl l='Detailed description'}
<span class="label-help-block">{intl l="The détailed category description."}</span>
</label>
{form_field form=$form field='success_url'}
<input type="hidden" name="{$name}" value="{url path='/admin/category' category_id={$category_d}}" />
{/form_field}
<div class="controls">
<textarea name="summary" rows="10" class="input-block-level">{$DESCRIPTION|htmlspecialchars}</textarea>
{form_field form=$form field='locale'}
<input type="hidden" name="{$name}" value="{$edit_language_locale}" />
{/form_field}
</div>
</div>
{if $form_error}<div class="alert alert-danger">{$form_error_message}</div>{/if}
<div class="control-group">
<label class="control-label">
{intl l='Conclusion'}
<span class="label-help-block">{intl l="A short post-description information"}</span>
</label>
{include file="includes/standard-description-form-fields.html"}
<div class="controls">
<textarea name="summary" rows="3" title="{intl l='Short category description'}" placeholder="{intl l='Short category description'}" class="input-block-level">{$POSTSCRIPTUM|htmlspecialchars}</textarea>
</div>
</div>
{form_field form=$form field='url'}
<div class="form-group {if $error}has-error{/if}">
<label for="{$label_attr.for}" class="control-label">
{intl l="{$label}"} :
</label>
<div class="control-group">
<label class="control-label">
{intl l='Rewriten URL'}
</label>
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" value="{$value}" title="{intl l='Rewritten URL'}" placeholder="{intl l='Rewriten URL'}" class="form-control">
</div>
{/form_field}
<div class="controls">
<div class="input-group input-block-level">
<input type="text" required="required" title="{intl l='Rewriten URL'}" placeholder="{intl l='Rewriten URL'}" class="input-block-level" value="{$URL|htmlspecialchars}">
<a class="btn btn-default input-group-addon use_default_rewriten_url" href="#">{intl l='Use default'}</a>
</div>
<div class="help-block">{intl l="The rewritten URL to the category page. Click \"Use Default\" button to use the default URL. Use only digits, letters, - and _ characters."}</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
{form_field form=$form field='parent'}
<div class="form-group {if $error}has-error{/if}">
</div>
</div>
<label for="{$label_attr.for}" class="control-label">
{intl l="{$label}"} :
</label>
<div class="row">
<div class="col-md-12">
<div class="control-group">
<lablel>&nbsp;</lablel>
<div class="controls">
<p>{intl l='Category created on %date_create. Last modification: %date_change' date_create="{format_date date=$CREATE_DATE}" date_change="{format_date date=$UPDATE_DATE}"}}</p>
</div>
</div>
</div>
</div>
<select id="{$label_attr.for}" required="required" name="{$name}" class="form-control">
<option value="0">{intl l="Top level"}</option>
</fieldset>
</div>
</div>
{$myparent=$PARENT}
{loop name="cat-parent" type="category-tree" visible="*" category="0"}
<option value="{$ID}" style="padding-left: {3 + $LEVEL * 20}px" {if $myparent == $ID}selected="selected"{/if} {if $category_id == $ID}disabled="disabled"{/if}>{$TITLE}</option>
{/loop}
<div class="tab-pane form-container" id="details">
<div class="form-horizontal col-md-12">
<fieldset>
{include file="includes/inner-form-toolbar.html" close_url="{url path='admin/catalog/category/edit' category_id=$current_category_id}"}
</select>
</div>
{/form_field}
</div>
<div class="col-md-6">
{form_field form=$form field='visible'}
<div class="form-group {if $error}has-error{/if}">
<label for="{$label_attr.for}" class="control-label">{intl l='Visibility'}</label>
<div class="checkbox">
<label>
<input type="checkbox" id="{$label_attr.for}" name="{$name}" value="1" {if $value != 0}checked="checked"{/if}>
{$label}
</label>
</div>
</div>
{/form_field}
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="control-group">
<label class="control-label">
{intl l='Category ID'}
</label>
<div class="controls">
<input type="text" name="id" disabled="disabled" value="{$ID}" />
</div>
</div>
</div>
<div class="col-md-6">
<div class="control-group">
<label class="control-label">
{intl l='Parent category *'}
</label>
<div class="controls">
<select required="required" name="parent">
<option value="0">{intl l="Top level"}</option>
{loop name="cat-parent" type="category-tree" visible="*" category="0" exclude="{$current_category_id}"}
<option value="{$ID}" style="padding-left: {3 + $LEVEL * 20}px" {if $parent_category_id == $ID}selected="selected"{/if}>{$TITLE}</option>
{/loop}
</select>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="control-group">
<label class="control-label">
{intl l='Visibility'}
</label>
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="visible" {if $VISIBLE}checked="checked"{/if}> {intl l="Display this category on front-office"}
</label>
<lablel>&nbsp;</lablel>
<div class="controls">
<p>{intl l='Category created on %date_create. Last modification: %date_change' date_create="{format_date date=$CREATE_DATE}" date_change="{format_date date=$UPDATE_DATE}"}</p>
</div>
</div>
</div>
</div>
</div>
</fieldset>
</div>
</div>
</form>
{/form}
</div>
</div>
<div class="tab-pane" id="images">
<p>Images</p>
</div>
<div class="tab-pane fade" id="details">
klljkmk
</div>
<div class="tab-pane" id="documents">
<p>Documents</p>
</div>
<div class="tab-pane fade" id="images">
</div>
<div class="tab-pane" id="modules">
<p>Modules</p>
</div>
<div class="tab-pane fade" id="documents">
</div>
</div>
</div>
</form>
{/loop}
<div class="tab-pane fade" id="modules">
</div>
</div>
</div>
</div>
</div>
{/loop}
</div>
</div>
</div>
@@ -217,10 +165,6 @@
<script>
$(function() {
$('#tabbed_menu a').click(function (e) {
e.preventDefault();
$(this).tab('show');
});
$('.use_default_rewriten_url').click(function(ev) {
alert("Not functionnal");

View File

@@ -54,7 +54,7 @@
{form_field form=$form field='name'}
<div class="form-group {if $error}has-error{/if}">
<label for="{$label_attr.for}" class="control-label">{intl l="{$label}"} : </label>
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" value="{$value|htmlspecialchars}" title="{intl l='Currency name'}" placeholder="{intl l='Currency name'}" class="form-control">
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" value="{$value}" title="{intl l='Currency name'}" placeholder="{intl l='Currency name'}" class="form-control">
<span class="help-block">&nbsp;</span>
</div>
{/form_field}
@@ -64,7 +64,7 @@
<label for="{$label_attr.for}" class="control-label">
{intl l="{$label}"} :
</label>
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" value="{$value|htmlspecialchars}" title="{intl l='Currency ISO 4217 Code'}" placeholder="{intl l='Code'}" class="form-control">
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" value="{$value}" title="{intl l='Currency ISO 4217 Code'}" placeholder="{intl l='Code'}" class="form-control">
<span class="help-block">
<a title="{intl l='More information about ISO 4217'}" href="http://fr.wikipedia.org/wiki/ISO_4217" target="_blank">List of ISO 4217 code</a>
</span>
@@ -80,7 +80,7 @@
<label for="{$label_attr.for}" class="control-label">
{intl l="{$label}"} :
</label>
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" value="{$value|htmlspecialchars}" title="{intl l='Currency symbol'}" placeholder="{intl l='Symbol'}" class="form-control">
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" value="{$value}" title="{intl l='Currency symbol'}" placeholder="{intl l='Symbol'}" class="form-control">
<span class="help-block">{intl l='The symbol, such as $, £, &euro;...'}</span>
</div>
{/form_field}
@@ -90,7 +90,7 @@
<label for="{$label_attr.for}" class="control-label">
{intl l="{$label}"} :
</label>
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" value="{$value|htmlspecialchars}" title="{intl l='Rate from Euro'}" placeholder="{intl l='Rate'}" class="form-control">
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" value="{$value}" title="{intl l='Rate from Euro'}" placeholder="{intl l='Rate'}" class="form-control">
<span class="help-block">The rate from Euro: Price in Euro x rate = Price in this currency</span>
</div>
{/form_field}

View File

@@ -13,7 +13,7 @@
<div class="general-block-decorator text-center">
<h1>{intl l="Oops! An Error Occurred"}</h1>
{block name="error-message"}<div class="alert alert-danger">{$error_message}</div>{/block}
{block name="error-message"}<p>{$error_message}</p>{/block}
<a href="{url path='/admin/home'}" class="btn btn-default btn-info btn-lg"><span class="glyphicon glyphicon-home"></span> {intl l="Go to administration home"}</a>
</div>

View File

@@ -5,17 +5,17 @@
<li><a href="{url path='admin/catalog'}">Catalog</a>
{ifloop rel="category_path"}</li>
{loop name="category_path" type="category-path" visible="*" category=$current_category_id}
{if $ID == $current_category_id}
{loop name="category_path" type="category-path" visible="*" category=$category_id}
{if $ID == $category_id}
<li class="active">
{if $action == 'edit'}
{intl l='Editing %cat' cat="{$TITLE}"}
{if $editing_category == true}
{intl l='Editing %cat' cat="{$TITLE}"}
{else}
{$TITLE} <a href="{url path='admin/catalog/category/edit' category_id=$ID}" title="{intl l='Edit this category'}">{intl l="(edit)"}</a>
{$TITLE} <a href="{url path='/admin/categories/update' category_id=$ID}" title="{intl l='Edit this category'}">{intl l="(edit)"}</a>
{/if}
</li>
{else}
<li><a href="{url path='admin/catalog/category' id=" $ID" action='browse'}">{$TITLE}</a></li>
<li><a href="{url path='/admin/categories' category_id=" $ID" action='browse'}">{$TITLE}</a></li>
{/if}
{/loop}
{/ifloop}

View File

@@ -43,7 +43,7 @@
{/form_field}
{form_field form=$form field='id'}
<input type="hidden" name="{$name}" value="{$value|htmlspecialchars}" />
<input type="hidden" name="{$name}" value="{$value}" />
{/form_field}
{form_field form=$form field='locale'}
@@ -55,7 +55,7 @@
{form_field form=$form field='name'}
<div class="form-group {if $error}has-error{/if}">
<label for="{$label_attr.for}" class="control-label">{intl l="{$label}"} : </label>
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" value="{$value|htmlspecialchars}" title="{intl l='Variable name'}" placeholder="{intl l='Variable name'}" class="form-control">
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" value="{$value}" title="{intl l='Variable name'}" placeholder="{intl l='Variable name'}" class="form-control">
</div>
{/form_field}
@@ -71,14 +71,14 @@
{form_field form=$form field='title'}
<div class="form-group {if $error}has-error{/if}">
<label for="{$label_attr.for}" class="control-label">{intl l="{$label}"} : </label>
<input type="text" id="{$label_attr.for}" name="{$name}" required="required" title="{intl l='Title'}" placeholder="{intl l='Title'}" class="form-control" value="{$value|htmlspecialchars}">
<input type="text" id="{$label_attr.for}" name="{$name}" required="required" title="{intl l='Title'}" placeholder="{intl l='Title'}" class="form-control" value="{$value}">
</div>
{/form_field}
{form_field form=$form field='subject'}
<div class="form-group {if $error}has-error{/if}">
<label for="{$label_attr.for}" class="control-label">{intl l="{$label}"} : </label>
<input type="text" id="{$label_attr.for}" name="{$name}" required="required" title="{intl l='Subject'}" placeholder="{intl l='Subject'}" class="form-control" value="{$value|htmlspecialchars}">
<input type="text" id="{$label_attr.for}" name="{$name}" required="required" title="{intl l='Subject'}" placeholder="{intl l='Subject'}" class="form-control" value="{$value}">
</div>
{/form_field}
@@ -88,7 +88,7 @@
{intl l="{$label}"} :
<span class="label-help-block">{intl l="The mailing template in HTML format."}</span>
</label>
<textarea name="{$name}" id="{$label_attr.for}" rows="10" class="form-control">{$value|htmlspecialchars}</textarea>
<textarea name="{$name}" id="{$label_attr.for}" rows="10" class="form-control">{$value}</textarea>
</div>
{/form_field}
@@ -98,7 +98,7 @@
{intl l="{$label}"} :
<span class="label-help-block">{intl l="The mailing template in text-only format."}</span>
</label>
<textarea name="{$name}" id="{$label_attr.for}" rows="10" class="form-control">{$value|htmlspecialchars}</textarea>
<textarea name="{$name}" id="{$label_attr.for}" rows="10" class="form-control">{$value}</textarea>
</div>
{/form_field}

View File

@@ -59,7 +59,7 @@
<div class="btn-group">
{loop type="auth" name="can_change" roles="ADMIN" permissions="admin.orders.edit"}
<a class="btn btn-default btn-xs" title="{intl l='Edit this order'}" href="{url path='/admin/order/update/$ID'}"><span class="glyphicon glyphicon-edit"></span></a>
<a class="btn btn-default btn-xs" title="{intl l='Edit this order'}" href="{url path="/admin/order/update/$ID"}"><span class="glyphicon glyphicon-edit"></span></a>
{/loop}
{loop type="auth" name="can_delete" roles="ADMIN" permissions="admin.orders.delete"}
@@ -83,7 +83,7 @@
<div class="btn-group">
{loop type="auth" name="can_change" roles="ADMIN" permissions="admin.orders.edit"}
<a class="btn btn-default btn-xs" title="{intl l='Edit this order'}" href="{url path='/admin/order/update/$ID'}"><span class="glyphicon glyphicon-edit"></span></a>
<a class="btn btn-default btn-xs" title="{intl l='Edit this order'}" href="{url path="/admin/order/update/$ID"}"><span class="glyphicon glyphicon-edit"></span></a>
{/loop}
{loop type="auth" name="can_delete" roles="ADMIN" permissions="admin.orders.delete"}
@@ -107,7 +107,7 @@
<div class="btn-group">
{loop type="auth" name="can_change" roles="ADMIN" permissions="admin.orders.edit"}
<a class="btn btn-default btn-xs" title="{intl l='Edit this order'}" href="{url path='/admin/order/update/$ID'}"><span class="glyphicon glyphicon-edit"></span></a>
<a class="btn btn-default btn-xs" title="{intl l='Edit this order'}" href="{url path="/admin/order/update/$ID"}"><span class="glyphicon glyphicon-edit"></span></a>
{/loop}
{loop type="auth" name="can_delete" roles="ADMIN" permissions="admin.orders.delete"}

View File

@@ -45,7 +45,7 @@
{* We do not allow creation of hidden variables *}
{form_field form=$form field='id'}
<input type="hidden" name="{$name}" value="{$value|htmlspecialchars}" />
<input type="hidden" name="{$name}" value="{$value}" />
{/form_field}
{form_field form=$form field='hidden'}
@@ -61,14 +61,14 @@
{form_field form=$form field='name'}
<div class="form-group {if $error}has-error{/if}">
<label for="{$label_attr.for}" class="control-label">{intl l="{$label}"} : </label>
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" value="{$value|htmlspecialchars}" title="{intl l='Variable name'}" placeholder="{intl l='Variable name'}" class="form-control">
<input type="text" id="{$label_attr.for}" required="required" name="{$name}" value="{$value}" title="{intl l='Variable name'}" placeholder="{intl l='Variable name'}" class="form-control">
</div>
{/form_field}
{form_field form=$form field='value'}
<div class="form-group {if $error}has-error{/if}">
<label for="{$label_attr.for}" class="control-label">{intl l="{$label}"} : </label>
<input type="text" id="{$label_attr.for}" name="{$name}" value="{$value|htmlspecialchars}" title="{intl l='Variable value'}" placeholder="{intl l='Variable value'}" class="form-control">
<input type="text" id="{$label_attr.for}" name="{$name}" value="{$value}" title="{intl l='Variable value'}" placeholder="{intl l='Variable value'}" class="form-control">
</div>
{/form_field}

View File

@@ -95,7 +95,7 @@
{if $SECURED}
{$VALUE}
{else}
<input id="cancelable_edit_{$ID}" class="js-edit form-control" data-id="{$ID}" type="text" name="variable[{$ID}]" value="{$VALUE|htmlspecialchars}" />
<input id="cancelable_edit_{$ID}" class="js-edit form-control" data-id="{$ID}" type="text" name="variable[{$ID}]" value="{$VALUE}" />
{/if}
</td>