diff --git a/composer.json b/composer.json index 4a3798384..afef9e30b 100755 --- a/composer.json +++ b/composer.json @@ -37,7 +37,8 @@ "imagine/imagine": "dev-master", "symfony/icu": "1.0", - "swiftmailer/swiftmailer": "5.0.*" + "swiftmailer/swiftmailer": "5.0.*", + "symfony/serializer": "2.3.*" }, "require-dev" : { "phpunit/phpunit": "3.7.*", diff --git a/composer.lock b/composer.lock index b0310c075..f90373a84 100755 --- a/composer.lock +++ b/composer.lock @@ -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": "a40be01c82e68ba0c446dc204d2667da", + "hash": "097481390dc87b3482d895b3b6a65479", "packages": [ { "name": "imagine/imagine", @@ -1456,6 +1456,53 @@ "homepage": "http://symfony.com", "time": "2013-08-23 14:06:02" }, + { + "name": "symfony/serializer", + "version": "v2.3.4", + "target-dir": "Symfony/Component/Serializer", + "source": { + "type": "git", + "url": "https://github.com/symfony/Serializer.git", + "reference": "457ba76395955926a67ea692957b0872dead5278" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Serializer/zipball/457ba76395955926a67ea692957b0872dead5278", + "reference": "457ba76395955926a67ea692957b0872dead5278", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Serializer\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Serializer Component", + "homepage": "http://symfony.com", + "time": "2013-07-21 12:12:18" + }, { "name": "symfony/translation", "version": "v2.2.6", @@ -1722,12 +1769,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "1.2.12" + "reference": "0e9958c459d675fb497d8dc5001c91d335734e48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1.2.12", - "reference": "1.2.12", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e9958c459d675fb497d8dc5001c91d335734e48", + "reference": "0e9958c459d675fb497d8dc5001c91d335734e48", "shasum": "" }, "require": { @@ -1916,12 +1963,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "1.2.0" + "reference": "31babf400e5b5868573bf49a000a3519d3978233" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1.2.0", - "reference": "1.2.0", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/31babf400e5b5868573bf49a000a3519d3978233", + "reference": "31babf400e5b5868573bf49a000a3519d3978233", "shasum": "" }, "require": { @@ -1966,12 +2013,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3.7.24" + "reference": "af7b77ccb5c64458bdfca95665d29558d1df7d08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3.7.24", - "reference": "3.7.24", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/af7b77ccb5c64458bdfca95665d29558d1df7d08", + "reference": "af7b77ccb5c64458bdfca95665d29558d1df7d08", "shasum": "" }, "require": { diff --git a/core/lib/Thelia/Action/Attribute.php b/core/lib/Thelia/Action/Attribute.php index 44c5968a4..12478e8a1 100644 --- a/core/lib/Thelia/Action/Attribute.php +++ b/core/lib/Thelia/Action/Attribute.php @@ -123,19 +123,7 @@ class Attribute extends BaseAction implements EventSubscriberInterface */ public function updatePosition(UpdatePositionEvent $event) { - if (null !== $attribute = AttributeQuery::create()->findPk($event->getObjectId())) { - - $attribute->setDispatcher($this->getDispatcher()); - - $mode = $event->getMode(); - - if ($mode == UpdatePositionEvent::POSITION_ABSOLUTE) - return $attribute->changeAbsolutePosition($event->getPosition()); - else if ($mode == UpdatePositionEvent::POSITION_UP) - return $attribute->movePositionUp(); - else if ($mode == UpdatePositionEvent::POSITION_DOWN) - return $attribute->movePositionDown(); - } + return $this->genericUpdatePosition(AttributeQuery::create(), $event); } protected function doAddToAllTemplates(AttributeModel $attribute) diff --git a/core/lib/Thelia/Action/AttributeAv.php b/core/lib/Thelia/Action/AttributeAv.php index a6b442fa2..0a72739d1 100644 --- a/core/lib/Thelia/Action/AttributeAv.php +++ b/core/lib/Thelia/Action/AttributeAv.php @@ -112,19 +112,7 @@ class AttributeAv extends BaseAction implements EventSubscriberInterface */ public function updatePosition(UpdatePositionEvent $event) { - if (null !== $attribute = AttributeAvQuery::create()->findPk($event->getObjectId())) { - - $attribute->setDispatcher($this->getDispatcher()); - - $mode = $event->getMode(); - - if ($mode == UpdatePositionEvent::POSITION_ABSOLUTE) - return $attribute->changeAbsolutePosition($event->getPosition()); - else if ($mode == UpdatePositionEvent::POSITION_UP) - return $attribute->movePositionUp(); - else if ($mode == UpdatePositionEvent::POSITION_DOWN) - return $attribute->movePositionDown(); - } + return $this->genericUpdatePosition(AttributeAvQuery::create(), $event); } diff --git a/core/lib/Thelia/Action/BaseAction.php b/core/lib/Thelia/Action/BaseAction.php index 56565ddc6..d371919eb 100755 --- a/core/lib/Thelia/Action/BaseAction.php +++ b/core/lib/Thelia/Action/BaseAction.php @@ -23,6 +23,10 @@ namespace Thelia\Action; use Symfony\Component\DependencyInjection\ContainerInterface; +use Thelia\Model\AdminLog; +use Propel\Runtime\ActiveQuery\PropelQuery; +use Propel\Runtime\ActiveQuery\ModelCriteria; +use Thelia\Core\Event\UpdatePositionEvent; class BaseAction { @@ -45,4 +49,42 @@ class BaseAction { return $this->container->get('event_dispatcher'); } + + + /** + * Changes object position, selecting absolute ou relative change. + * + * @param $query the query to retrieve the object to move + * @param UpdatePositionEvent $event + */ + protected function genericUpdatePosition(ModelCriteria $query, UpdatePositionEvent $event) + { + if (null !== $object = $query->findPk($event->getObjectId())) { + + $object->setDispatcher($this->getDispatcher()); + + $mode = $event->getMode(); + + if ($mode == UpdatePositionEvent::POSITION_ABSOLUTE) + return $object->changeAbsolutePosition($event->getPosition()); + else if ($mode == UpdatePositionEvent::POSITION_UP) + return $object->movePositionUp(); + else if ($mode == UpdatePositionEvent::POSITION_DOWN) + return $object->movePositionDown(); + } + } + + /** + * Helper to append a message to the admin log. + * + * @param string $message + */ + public function adminLogAppend($message) + { + AdminLog::append( + $message, + $this->container->get('request'), + $this->container->get('thelia.securityContext')->getAdminUser() + ); + } } diff --git a/core/lib/Thelia/Action/Category.php b/core/lib/Thelia/Action/Category.php index 25a94711f..8429a7c4e 100755 --- a/core/lib/Thelia/Action/Category.php +++ b/core/lib/Thelia/Action/Category.php @@ -136,19 +136,7 @@ class Category extends BaseAction implements EventSubscriberInterface */ 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(); - } + return $this->genericUpdatePosition(CategoryQuery::create(), $event); } public function addContent(CategoryAddContentEvent $event) { diff --git a/core/lib/Thelia/Action/Currency.php b/core/lib/Thelia/Action/Currency.php index 946fee375..3c428683b 100644 --- a/core/lib/Thelia/Action/Currency.php +++ b/core/lib/Thelia/Action/Currency.php @@ -166,20 +166,7 @@ class Currency extends BaseAction implements EventSubscriberInterface */ public function updatePosition(UpdatePositionEvent $event) { - if (null !== $currency = CurrencyQuery::create()->findPk($event->getObjectId())) { - - $currency->setDispatcher($this->getDispatcher()); - - $mode = $event->getMode(); - echo "loaded $mode !"; - - if ($mode == UpdatePositionEvent::POSITION_ABSOLUTE) - return $currency->changeAbsolutePosition($event->getPosition()); - else if ($mode == UpdatePositionEvent::POSITION_UP) - return $currency->movePositionUp(); - else if ($mode == UpdatePositionEvent::POSITION_DOWN) - return $currency->movePositionDown(); - } + return $this->genericUpdatePosition(CurrencyQuery::create(), $event); } /** diff --git a/core/lib/Thelia/Action/Document.php b/core/lib/Thelia/Action/Document.php index 86fc51ac5..6b5188ba8 100644 --- a/core/lib/Thelia/Action/Document.php +++ b/core/lib/Thelia/Action/Document.php @@ -25,8 +25,12 @@ namespace Thelia\Action; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Thelia\Core\Event\DocumentCreateOrUpdateEvent; +use Thelia\Core\Event\DocumentDeleteEvent; use Thelia\Core\Event\DocumentEvent; +use Thelia\Exception\ImageException; use Thelia\Model\ConfigQuery; +use Thelia\Tools\FileManager; use Thelia\Tools\URL; use Imagine\Document\ImagineInterface; @@ -88,45 +92,175 @@ class Document extends BaseCachedFile implements EventSubscriberInterface * * This method updates the cache_file_path and file_url attributes of the event * - * @param DocumentEvent $event - * @throws \InvalidArgumentException, DocumentException + * @param DocumentEvent $event Event + * + * @throws \Thelia\Exception\DocumentException + * @throws \InvalidArgumentException , DocumentException */ public function processDocument(DocumentEvent $event) { $subdir = $event->getCacheSubdirectory(); - $source_file = $event->getSourceFilepath(); + $sourceFile = $event->getSourceFilepath(); - if (null == $subdir || null == $source_file) { + if (null == $subdir || null == $sourceFile) { throw new \InvalidArgumentException("Cache sub-directory and source file path cannot be null"); } - $originalDocumentPathInCache = $this->getCacheFilePath($subdir, $source_file, true); + $originalDocumentPathInCache = $this->getCacheFilePath($subdir, $sourceFile, true); if (! file_exists($originalDocumentPathInCache)) { - if (! file_exists($source_file)) { - throw new DocumentException(sprintf("Source document file %s does not exists.", $source_file)); + if (! file_exists($sourceFile)) { + throw new DocumentException(sprintf("Source document file %s does not exists.", $sourceFile)); } $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)); + if (false == symlink($sourceFile, $originalDocumentPathInCache)) { + throw new DocumentException(sprintf("Failed to create symbolic link for %s in %s document cache directory", basename($sourceFile), $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)); + } else { + // mode = 'copy' + if (false == @copy($sourceFile, $originalDocumentPathInCache)) { + throw new DocumentException(sprintf("Failed to copy %s in %s document cache directory", basename($sourceFile), $subdir)); } } } // Compute the document URL - $document_url = $this->getCacheFileURL($subdir, basename($originalDocumentPathInCache)); + $documentUrl = $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)); + $event->setDocumentPath($documentUrl); + $event->setDocumentUrl(URL::getInstance()->absoluteUrl($documentUrl, null, URL::PATH_TO_FILE)); + } + + /** + * Take care of saving document in the database and file storage + * + * @param DocumentCreateOrUpdateEvent $event Document event + * + * @throws \Thelia\Exception\ImageException + * @todo refactor make all documents using propel inheritance and factorise image behaviour into one single clean action + */ + public function saveDocument(DocumentCreateOrUpdateEvent $event) + { + $this->adminLogAppend( + $this->container->get('thelia.translator')->trans( + 'Saving documents for %parentName% parent id %parentId% (%parentType%)', + array( + '%parentName%' => $event->getParentName(), + '%parentId%' => $event->getParentId(), + '%parentType%' => $event->getDocumentType() + ), + 'document' + ) + ); + + $fileManager = new FileManager($this->container); + $model = $event->getModelDocument(); + + $nbModifiedLines = $model->save(); + + $event->setModelDocument($model); + + if (!$nbModifiedLines) { + throw new ImageException( + sprintf( + 'Document "%s" with parent id %s (%s) failed to be saved', + $event->getParentName(), + $event->getParentId(), + $event->getDocumentType() + ) + ); + } + + $newUploadedFile = $fileManager->copyUploadedFile($event->getParentId(), $event->getDocumentType(), $event->getModelDocument(), $event->getUploadedFile(), FileManager::FILE_TYPE_DOCUMENTS); + $event->setUploadedFile($newUploadedFile); + } + + /** + * Take care of updating document in the database and file storage + * + * @param DocumentCreateOrUpdateEvent $event Document event + * + * @throws \Thelia\Exception\ImageException + * @todo refactor make all documents using propel inheritance and factorise image behaviour into one single clean action + */ + public function updateDocument(DocumentCreateOrUpdateEvent $event) + { + $this->adminLogAppend( + $this->container->get('thelia.translator')->trans( + 'Updating documents for %parentName% parent id %parentId% (%parentType%)', + array( + '%parentName%' => $event->getParentName(), + '%parentId%' => $event->getParentId(), + '%parentType%' => $event->getDocumentType() + ), + 'image' + ) + ); + + if (null !== $event->getUploadedFile()) { + $event->getModelDocument()->setTitle($event->getUploadedFile()->getClientOriginalName()); + } + + $fileManager = new FileManager($this->container); + // Copy and save file + if ($event->getUploadedFile()) { + // Remove old picture file from file storage + $url = $fileManager->getUploadDir($event->getDocumentType(), FileManager::FILE_TYPE_DOCUMENTS) . '/' . $event->getOldModelDocument()->getFile(); + unlink(str_replace('..', '', $url)); + + $newUploadedFile = $fileManager->copyUploadedFile($event->getParentId(), $event->getDocumentType(), $event->getModelDocument(), $event->getUploadedFile(), FileManager::FILE_TYPE_DOCUMENTS); + $event->setUploadedFile($newUploadedFile); + } + + // Update document modifications + $event->getModelDocument()->save(); + $event->setModelDocument($event->getModelDocument()); + } + + /** + * Take care of deleting document in the database and file storage + * + * @param DocumentDeleteEvent $event Image event + * + * @throws \Exception + * @todo refactor make all documents using propel inheritance and factorise image behaviour into one single clean action + */ + public function deleteDocument(DocumentDeleteEvent $event) + { + $fileManager = new FileManager($this->container); + + try { + $fileManager->deleteFile($event->getDocumentToDelete(), $event->getDocumentType(), FileManager::FILE_TYPE_DOCUMENTS); + + $this->adminLogAppend( + $this->container->get('thelia.translator')->trans( + 'Deleting document for %id% with parent id %parentId%', + array( + '%id%' => $event->getDocumentToDelete()->getId(), + '%parentId%' => $event->getDocumentToDelete()->getParentId(), + ), + 'document' + ) + ); + } catch(\Exception $e) { + $this->adminLogAppend( + $this->container->get('thelia.translator')->trans( + 'Fail to delete document for %id% with parent id %parentId% (Exception : %e%)', + array( + '%id%' => $event->getDocumentToDelete()->getId(), + '%parentId%' => $event->getDocumentToDelete()->getParentId(), + '%e%' => $e->getMessage() + ), + 'document' + ) + ); + throw $e; + } } public static function getSubscribedEvents() @@ -134,6 +268,9 @@ class Document extends BaseCachedFile implements EventSubscriberInterface return array( TheliaEvents::DOCUMENT_PROCESS => array("processDocument", 128), TheliaEvents::DOCUMENT_CLEAR_CACHE => array("clearCache", 128), + TheliaEvents::DOCUMENT_DELETE => array("deleteDocument", 128), + TheliaEvents::DOCUMENT_SAVE => array("saveDocument", 128), + TheliaEvents::DOCUMENT_UPDATE => array("updateDocument", 128), ); } } diff --git a/core/lib/Thelia/Action/Feature.php b/core/lib/Thelia/Action/Feature.php index a746ce4e2..01799510c 100644 --- a/core/lib/Thelia/Action/Feature.php +++ b/core/lib/Thelia/Action/Feature.php @@ -123,19 +123,7 @@ class Feature extends BaseAction implements EventSubscriberInterface */ public function updatePosition(UpdatePositionEvent $event) { - if (null !== $feature = FeatureQuery::create()->findPk($event->getObjectId())) { - - $feature->setDispatcher($this->getDispatcher()); - - $mode = $event->getMode(); - - if ($mode == UpdatePositionEvent::POSITION_ABSOLUTE) - return $feature->changeAbsolutePosition($event->getPosition()); - else if ($mode == UpdatePositionEvent::POSITION_UP) - return $feature->movePositionUp(); - else if ($mode == UpdatePositionEvent::POSITION_DOWN) - return $feature->movePositionDown(); - } + return $this->genericUpdatePosition(FeatureQuery::create(), $event); } protected function doAddToAllTemplates(FeatureModel $feature) diff --git a/core/lib/Thelia/Action/FeatureAv.php b/core/lib/Thelia/Action/FeatureAv.php index 2bd117b4b..25f9ae5f2 100644 --- a/core/lib/Thelia/Action/FeatureAv.php +++ b/core/lib/Thelia/Action/FeatureAv.php @@ -112,19 +112,7 @@ class FeatureAv extends BaseAction implements EventSubscriberInterface */ public function updatePosition(UpdatePositionEvent $event) { - if (null !== $feature = FeatureAvQuery::create()->findPk($event->getObjectId())) { - - $feature->setDispatcher($this->getDispatcher()); - - $mode = $event->getMode(); - - if ($mode == UpdatePositionEvent::POSITION_ABSOLUTE) - return $feature->changeAbsolutePosition($event->getPosition()); - else if ($mode == UpdatePositionEvent::POSITION_UP) - return $feature->movePositionUp(); - else if ($mode == UpdatePositionEvent::POSITION_DOWN) - return $feature->movePositionDown(); - } + return $this->genericUpdatePosition(FeatureAvQuery::create(), $event); } diff --git a/core/lib/Thelia/Action/Image.php b/core/lib/Thelia/Action/Image.php index ba5ee153f..b15b00f0b 100755 --- a/core/lib/Thelia/Action/Image.php +++ b/core/lib/Thelia/Action/Image.php @@ -23,10 +23,20 @@ namespace Thelia\Action; +use Propel\Runtime\ActiveRecord\ActiveRecordInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Thelia\Core\Event\ImageCreateOrUpdateEvent; +use Thelia\Core\Event\ImagesCreateOrUpdateEvent; +use Thelia\Core\Event\ImageDeleteEvent; use Thelia\Core\Event\ImageEvent; +use Thelia\Model\CategoryImage; use Thelia\Model\ConfigQuery; +use Thelia\Model\ContentImage; +use Thelia\Model\FolderImage; +use Thelia\Model\ProductImage; +use Thelia\Tools\FileManager; use Thelia\Tools\URL; use Imagine\Image\ImagineInterface; @@ -39,10 +49,10 @@ use Thelia\Core\Event\TheliaEvents; /** * - * Image management actions. This class handles image processing an caching. + * Image management actions. This class handles image processing and caching. * - * Basically, images are stored outside the web space (by default in local/media/images), - * and cached in the web space (by default in web/local/images). + * Basically, images are stored outside of the web space (by default in local/media/images), + * and cached inside the web space (by default in web/local/images). * * In the images caches directory, a subdirectory for images categories (eg. product, category, folder, etc.) is * automatically created, and the cached image is created here. Plugin may use their own subdirectory as required. @@ -78,6 +88,8 @@ class Image extends BaseCachedFile implements EventSubscriberInterface const EXACT_RATIO_WITH_CROP = 2; const KEEP_IMAGE_RATIO = 3; + + /** * @return string root of the image cache directory in web space */ @@ -240,6 +252,128 @@ class Image extends BaseCachedFile implements EventSubscriberInterface $event->setOriginalFileUrl(URL::getInstance()->absoluteUrl($original_image_url, null, URL::PATH_TO_FILE)); } + /** + * Take care of saving image in the database and file storage + * + * @param ImageCreateOrUpdateEvent $event Image event + * + * @throws \Thelia\Exception\ImageException + * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action + */ + public function saveImage(ImageCreateOrUpdateEvent $event) + { + $this->adminLogAppend( + $this->container->get('thelia.translator')->trans( + 'Saving images for %parentName% parent id %parentId% (%parentType%)', + array( + '%parentName%' => $event->getParentName(), + '%parentId%' => $event->getParentId(), + '%parentType%' => $event->getImageType() + ), + 'image' + ) + ); + + $fileManager = new FileManager($this->container); + $model = $event->getModelImage(); + + $nbModifiedLines = $model->save(); + $event->setModelImage($model); + + if (!$nbModifiedLines) { + throw new ImageException( + sprintf( + 'Image "%s" with parent id %s (%s) failed to be saved', + $event->getParentName(), + $event->getParentId(), + $event->getImageType() + ) + ); + } + + $newUploadedFile = $fileManager->copyUploadedFile($event->getParentId(), $event->getImageType(), $event->getModelImage(), $event->getUploadedFile(), FileManager::FILE_TYPE_IMAGES); + $event->setUploadedFile($newUploadedFile); + } + + /** + * Take care of updating image in the database and file storage + * + * @param ImageCreateOrUpdateEvent $event Image event + * + * @throws \Thelia\Exception\ImageException + * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action + */ + public function updateImage(ImageCreateOrUpdateEvent $event) + { + $this->adminLogAppend( + $this->container->get('thelia.translator')->trans( + 'Updating images for %parentName% parent id %parentId% (%parentType%)', + array( + '%parentName%' => $event->getParentName(), + '%parentId%' => $event->getParentId(), + '%parentType%' => $event->getImageType() + ), + 'image' + ) + ); + + $fileManager = new FileManager($this->container); + // Copy and save file + if ($event->getUploadedFile()) { + // Remove old picture file from file storage + $url = $fileManager->getUploadDir($event->getImageType(), FileManager::FILE_TYPE_IMAGES) . '/' . $event->getOldModelImage()->getFile(); + unlink(str_replace('..', '', $url)); + + $newUploadedFile = $fileManager->copyUploadedFile($event->getParentId(), $event->getImageType(), $event->getModelImage(), $event->getUploadedFile(), FileManager::FILE_TYPE_IMAGES); + $event->setUploadedFile($newUploadedFile); + } + + // Update image modifications + $event->getModelImage()->save(); + $event->setModelImage($event->getModelImage()); + } + + /** + * Take care of deleting image in the database and file storage + * + * @param ImageDeleteEvent $event Image event + * + * @throws \Exception + * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action + */ + public function deleteImage(ImageDeleteEvent $event) + { + $fileManager = new FileManager($this->container); + + try { + $fileManager->deleteFile($event->getImageToDelete(), $event->getImageType(), FileManager::FILE_TYPE_IMAGES); + + $this->adminLogAppend( + $this->container->get('thelia.translator')->trans( + 'Deleting image for %id% with parent id %parentId%', + array( + '%id%' => $event->getImageToDelete()->getId(), + '%parentId%' => $event->getImageToDelete()->getParentId(), + ), + 'image' + ) + ); + } catch(\Exception $e){ + $this->adminLogAppend( + $this->container->get('thelia.translator')->trans( + 'Fail to delete image for %id% with parent id %parentId% (Exception : %e%)', + array( + '%id%' => $event->getImageToDelete()->getId(), + '%parentId%' => $event->getImageToDelete()->getParentId(), + '%e%' => $e->getMessage() + ), + 'image' + ) + ); + throw $e; + } + } + /** * Process image resizing, with borders or cropping. If $dest_width and $dest_height * are both null, no resize is performed. @@ -362,6 +496,9 @@ class Image extends BaseCachedFile implements EventSubscriberInterface return array( TheliaEvents::IMAGE_PROCESS => array("processImage", 128), TheliaEvents::IMAGE_CLEAR_CACHE => array("clearCache", 128), + TheliaEvents::IMAGE_DELETE => array("deleteImage", 128), + TheliaEvents::IMAGE_SAVE => array("saveImage", 128), + TheliaEvents::IMAGE_UPDATE => array("updateImage", 128), ); } } diff --git a/core/lib/Thelia/Action/Product.php b/core/lib/Thelia/Action/Product.php index 69a07c157..cecc1df39 100644 --- a/core/lib/Thelia/Action/Product.php +++ b/core/lib/Thelia/Action/Product.php @@ -48,6 +48,26 @@ use Thelia\Model\AccessoryQuery; use Thelia\Model\Accessory; use Thelia\Core\Event\ProductAddAccessoryEvent; use Thelia\Core\Event\ProductDeleteAccessoryEvent; +use Thelia\Core\Event\FeatureProductUpdateEvent; +use Thelia\Model\FeatureProduct; +use Thelia\Model\FeatureQuery; +use Thelia\Core\Event\FeatureProductDeleteEvent; +use Thelia\Model\FeatureProductQuery; +use Thelia\Model\ProductCategoryQuery; +use Thelia\Core\Event\ProductSetTemplateEvent; +use Thelia\Model\AttributeCombinationQuery; +use Thelia\Model\ProductSaleElementsQuery; +use Propel\Runtime\ActiveQuery\PropelQuery; +use Thelia\Core\Event\ProductDeleteCategoryEvent; +use Thelia\Core\Event\ProductAddCategoryEvent; +use Thelia\Model\AttributeAvQuery; +use Thelia\Model\AttributeCombination; +use Thelia\Core\Event\ProductCreateCombinationEvent; +use Propel\Runtime\Propel; +use Thelia\Model\Map\ProductTableMap; +use Thelia\Core\Event\ProductDeleteCombinationEvent; +use Thelia\Model\ProductPrice; +use Thelia\Model\ProductSaleElements; class Product extends BaseAction implements EventSubscriberInterface { @@ -71,7 +91,15 @@ class Product extends BaseAction implements EventSubscriberInterface // Set the default tax rule to this product ->setTaxRule(TaxRuleQuery::create()->findOneByIsDefault(true)) - ->create($event->getDefaultCategory()) + //public function create($defaultCategoryId, $basePrice, $priceCurrencyId, $taxRuleId, $baseWeight) { + + ->create( + $event->getDefaultCategory(), + $event->getBasePrice(), + $event->getCurrencyId(), + $event->getTaxRuleId(), + $event->getBaseWeight() + ); ; $event->setProduct($product); @@ -84,8 +112,6 @@ class Product extends BaseAction implements EventSubscriberInterface */ public function update(ProductUpdateEvent $event) { - $search = ProductQuery::create(); - if (null !== $product = ProductQuery::create()->findPk($event->getProductId())) { $product @@ -96,11 +122,16 @@ class Product extends BaseAction implements EventSubscriberInterface ->setDescription($event->getDescription()) ->setChapo($event->getChapo()) ->setPostscriptum($event->getPostscriptum()) - - ->setParent($event->getParent()) ->setVisible($event->getVisible()) - ->save(); + ->save() + ; + + // Update the rewriten URL, if required + $product->setRewrittenUrl($event->getLocale(), $event->getUrl()); + + // Update default category (ifd required) + $product->updateDefaultCategory($event->getDefaultCategory()); $event->setProduct($product); } @@ -147,19 +178,7 @@ class Product extends BaseAction implements EventSubscriberInterface */ public function updatePosition(UpdatePositionEvent $event) { - if (null !== $product = ProductQuery::create()->findPk($event->getObjectId())) { - - $product->setDispatcher($this->getDispatcher()); - - $mode = $event->getMode(); - - if ($mode == UpdatePositionEvent::POSITION_ABSOLUTE) - return $product->changeAbsolutePosition($event->getPosition()); - else if ($mode == UpdatePositionEvent::POSITION_UP) - return $product->movePositionUp(); - else if ($mode == UpdatePositionEvent::POSITION_DOWN) - return $product->movePositionDown(); - } + return $this->genericUpdatePosition(ProductQuery::create(), $event); } public function addContent(ProductAddContentEvent $event) { @@ -193,6 +212,34 @@ class Product extends BaseAction implements EventSubscriberInterface ; } + public function addCategory(ProductAddCategoryEvent $event) { + + if (ProductCategoryQuery::create() + ->filterByProduct($event->getProduct()) + ->filterByCategoryId($event->getCategoryId()) + ->count() <= 0) { + + $productCategory = new ProductCategory(); + + $productCategory + ->setProduct($event->getProduct()) + ->setCategoryId($event->getCategoryId()) + ->setDefaultCategory(false) + ->save() + ; + } + } + + public function removeCategory(ProductDeleteCategoryEvent $event) { + + $productCategory = ProductCategoryQuery::create() + ->filterByProduct($event->getProduct()) + ->filterByCategoryId($event->getCategoryId()) + ->findOne(); + + if ($productCategory != null) $productCategory->delete(); + } + public function addAccessory(ProductAddAccessoryEvent $event) { if (AccessoryQuery::create() @@ -224,26 +271,165 @@ class Product extends BaseAction implements EventSubscriberInterface ; } + public function setProductTemplate(ProductSetTemplateEvent $event) { + + $product = $event->getProduct(); + + // Delete all product feature relations + FeatureProductQuery::create()->filterByProduct($product)->delete(); + + // Delete all product attributes sale elements + ProductSaleElementsQuery::create()->filterByProduct($product)->delete(); + + // Update the product template + $template_id = $event->getTemplateId(); + + // Set it to null if it's zero. + if ($template_id <= 0) $template_id = NULL; + + $product->setTemplateId($template_id)->save(); + } + + /** + * Changes accessry position, selecting absolute ou relative change. + * + * @param ProductChangePositionEvent $event + */ + public function updateAccessoryPosition(UpdatePositionEvent $event) + { + return $this->genericUpdatePosition(AccessoryQuery::create(), $event); + } /** * Changes position, selecting absolute ou relative change. * * @param ProductChangePositionEvent $event */ - public function updateAccessoryPosition(UpdatePositionEvent $event) + public function updateContentPosition(UpdatePositionEvent $event) { - if (null !== $accessory = AccessoryQuery::create()->findPk($event->getObjectId())) { + return $this->genericUpdatePosition(ProductAssociatedContentQuery::create(), $event); + } - $accessory->setDispatcher($this->getDispatcher()); + public function updateFeatureProductValue(FeatureProductUpdateEvent $event) { - $mode = $event->getMode(); + // If the feature is not free text, it may have one ore more values. + // If the value exists, we do not change it + // If the value does not exists, we create it. + // + // If the feature is free text, it has only a single value. + // Etiher create or update it. - if ($mode == UpdatePositionEvent::POSITION_ABSOLUTE) - return $accessory->changeAbsolutePosition($event->getPosition()); - else if ($mode == UpdatePositionEvent::POSITION_UP) - return $accessory->movePositionUp(); - else if ($mode == UpdatePositionEvent::POSITION_DOWN) - return $accessory->movePositionDown(); + $featureProductQuery = FeatureProductQuery::create() + ->filterByFeatureId($event->getFeatureId()) + ->filterByProductId($event->getProductId()) + ; + + if ($event->getIsTextValue() !== true) { + $featureProductQuery->filterByFeatureAvId($event->getFeatureValue()); + } + + $featureProduct = $featureProductQuery->findOne(); + + if ($featureProduct == null) { + $featureProduct = new FeatureProduct(); + + $featureProduct + ->setDispatcher($this->getDispatcher()) + + ->setProductId($event->getProductId()) + ->setFeatureId($event->getFeatureId()) + ; + } + + if ($event->getIsTextValue() == true) { + $featureProduct->setFreeTextValue($event->getFeatureValue()); + } + else { + $featureProduct->setFeatureAvId($event->getFeatureValue()); + } + + $featureProduct->save(); + + $event->setFeatureProduct($featureProduct); + } + + public function deleteFeatureProductValue(FeatureProductDeleteEvent $event) { + + $featureProduct = FeatureProductQuery::create() + ->filterByProductId($event->getProductId()) + ->filterByFeatureId($event->getFeatureId()) + ->delete() + ; + } + + public function createProductCombination(ProductCreateCombinationEvent $event) { + + $con = Propel::getWriteConnection(ProductTableMap::DATABASE_NAME); + + $con->beginTransaction(); + + try { + $product = $event->getProduct(); + + // Create an empty product sale element + $salesElement = new ProductSaleElements(); + + $salesElement + ->setProduct($product) + ->setRef($product->getRef()) + ->setPromo(0) + ->setNewness(0) + ->setWeight(0) + ->setIsDefault(false) + ->save($con) + ; + + // Create an empty product price in the default currency + $product_price = new ProductPrice(); + + $product_price + ->setProductSaleElements($salesElement) + ->setPromoPrice(0) + ->setPrice(0) + ->setCurrencyId($event->getCurrencyId()) + ->save($con) + ; + + $combinationAttributes = $event->getAttributeAvList(); + + if (count($combinationAttributes) > 0) { + + foreach($combinationAttributes as $attributeAvId) { + + $attributeAv = AttributeAvQuery::create()->findPk($attributeAvId); + + if ($attributeAv !== null) { + $attributeCombination = new AttributeCombination(); + + $attributeCombination + ->setAttributeAvId($attributeAvId) + ->setAttribute($attributeAv->getAttribute()) + ->setProductSaleElements($salesElement) + ->save(); + } + } + } + + // Store all the stuff ! + $con->commit(); + } + catch(\Exception $ex) { + + $con->rollback(); + + throw $ex; + } + } + + public function deleteProductCombination(ProductDeleteCombinationEvent $event) { + + if (null !== $pse = ProductSaleElementsQuery::create()->findPk($event->getProductSaleElementId())) { + $pse->delete(); } } @@ -263,9 +449,22 @@ class Product extends BaseAction implements EventSubscriberInterface TheliaEvents::PRODUCT_ADD_CONTENT => array("addContent", 128), TheliaEvents::PRODUCT_REMOVE_CONTENT => array("removeContent", 128), TheliaEvents::PRODUCT_UPDATE_ACCESSORY_POSITION => array("updateAccessoryPosition", 128), + TheliaEvents::PRODUCT_UPDATE_CONTENT_POSITION => array("updateContentPosition", 128), + + TheliaEvents::PRODUCT_ADD_COMBINATION => array("createProductCombination", 128), + TheliaEvents::PRODUCT_DELETE_COMBINATION => array("deleteProductCombination", 128), TheliaEvents::PRODUCT_ADD_ACCESSORY => array("addAccessory", 128), TheliaEvents::PRODUCT_REMOVE_ACCESSORY => array("removeAccessory", 128), + + TheliaEvents::PRODUCT_ADD_CATEGORY => array("addCategory", 128), + TheliaEvents::PRODUCT_REMOVE_CATEGORY => array("removeCategory", 128), + + TheliaEvents::PRODUCT_SET_TEMPLATE => array("setProductTemplate", 128), + + TheliaEvents::PRODUCT_FEATURE_UPDATE_VALUE => array("updateFeatureProductValue", 128), + TheliaEvents::PRODUCT_FEATURE_DELETE_VALUE => array("deleteFeatureProductValue", 128), + ); } } diff --git a/core/lib/Thelia/Action/Template.php b/core/lib/Thelia/Action/Template.php index 18174dd26..47d5d7a4d 100644 --- a/core/lib/Thelia/Action/Template.php +++ b/core/lib/Thelia/Action/Template.php @@ -135,6 +135,26 @@ class Template extends BaseAction implements EventSubscriberInterface } } + /** + * Changes position, selecting absolute ou relative change. + * + * @param CategoryChangePositionEvent $event + */ + public function updateAttributePosition(UpdatePositionEvent $event) + { + return $this->genericUpdatePosition(AttributeTemplateQuery::create(), $event); + } + + /** + * Changes position, selecting absolute ou relative change. + * + * @param CategoryChangePositionEvent $event + */ + public function updateFeaturePosition(UpdatePositionEvent $event) + { + return $this->genericUpdatePosition(FeatureTemplateQuery::create(), $event); + } + public function deleteAttribute(TemplateDeleteAttributeEvent $event) { $attribute_template = AttributeTemplateQuery::create() @@ -185,6 +205,9 @@ class Template extends BaseAction implements EventSubscriberInterface TheliaEvents::TEMPLATE_ADD_FEATURE => array("addFeature", 128), TheliaEvents::TEMPLATE_DELETE_FEATURE => array("deleteFeature", 128), + TheliaEvents::TEMPLATE_CHANGE_ATTRIBUTE_POSITION => array('updateAttributePosition', 128), + TheliaEvents::TEMPLATE_CHANGE_FEATURE_POSITION => array('updateFeaturePosition', 128), + ); } } \ No newline at end of file diff --git a/core/lib/Thelia/Config/Resources/action.xml b/core/lib/Thelia/Config/Resources/action.xml index 15839df95..f153a8de0 100755 --- a/core/lib/Thelia/Config/Resources/action.xml +++ b/core/lib/Thelia/Config/Resources/action.xml @@ -36,6 +36,10 @@ + + + + diff --git a/core/lib/Thelia/Config/Resources/config.xml b/core/lib/Thelia/Config/Resources/config.xml index e26526a06..66ab0f69f 100755 --- a/core/lib/Thelia/Config/Resources/config.xml +++ b/core/lib/Thelia/Config/Resources/config.xml @@ -35,6 +35,7 @@ + @@ -58,18 +59,26 @@
+ + + + + - + + + + @@ -239,6 +248,11 @@ + + + + + diff --git a/core/lib/Thelia/Config/Resources/routing/admin.xml b/core/lib/Thelia/Config/Resources/routing/admin.xml index 1d6101f95..fa71f7240 100755 --- a/core/lib/Thelia/Config/Resources/routing/admin.xml +++ b/core/lib/Thelia/Config/Resources/routing/admin.xml @@ -37,6 +37,70 @@ Thelia\Controller\Admin\CategoryController::defaultAction + + + + Thelia\Controller\Admin\FileController::saveImageAjaxAction + .* + \d+ + + + Thelia\Controller\Admin\FileController::getImageFormAjaxAction + .* + \d+ + + + Thelia\Controller\Admin\FileController::getImageListAjaxAction + .* + \d+ + + + Thelia\Controller\Admin\FileController::viewImageAction + .* + \d+ + + + Thelia\Controller\Admin\FileController::updateImageAction + .* + \d+ + + + Thelia\Controller\Admin\FileController::deleteImageAction + .* + \d+ + + + + Thelia\Controller\Admin\FileController::saveDocumentAjaxAction + .* + \d+ + + + Thelia\Controller\Admin\FileController::getDocumentFormAjaxAction + .* + \d+ + + + Thelia\Controller\Admin\FileController::getDocumentListAjaxAction + .* + \d+ + + + Thelia\Controller\Admin\FileController::viewDocumentAction + .* + \d+ + + + Thelia\Controller\Admin\FileController::updateDocumentAction + .* + \d+ + + + Thelia\Controller\Admin\FileController::deleteDocumentAction + .* + \d+ + + @@ -106,6 +170,10 @@ Thelia\Controller\Admin\CategoryController::addRelatedContentAction + + Thelia\Controller\Admin\CategoryController::addRelatedPictureAction + + Thelia\Controller\Admin\CategoryController::deleteRelatedContentAction @@ -150,22 +218,47 @@ Thelia\Controller\Admin\ProductController::updatePositionAction - - + + Thelia\Controller\Admin\ProductController::loadGeneralAjaxTabAction + + + + + + Thelia\Controller\Admin\ProductController::loadRelatedAjaxTabAction + + + + + + Thelia\Controller\Admin\ProductController::addAdditionalCategoryAction + + + + Thelia\Controller\Admin\ProductController::deleteAdditionalCategoryAction + + + + + Thelia\Controller\Admin\ProductController::addRelatedContentAction - + Thelia\Controller\Admin\ProductController::deleteRelatedContentAction - + Thelia\Controller\Admin\ProductController::getAvailableRelatedContentAction xml|json - + + Thelia\Controller\Admin\ProductController::updateContentPositionAction + + + Thelia\Controller\Admin\ProductController::addAccessoryAction @@ -180,18 +273,57 @@ xml|json - + Thelia\Controller\Admin\ProductController::updateAccessoryPositionAction - + + + + Thelia\Controller\Admin\ProductController::loadAttributesAjaxTabAction + + + + Thelia\Controller\Admin\ProductController::setProductTemplateAction + Thelia\Controller\Admin\ProductController::updateAttributesAndFeaturesAction + + + + Thelia\Controller\Admin\ProductController::getAttributeValuesAction + xml|json + + + + Thelia\Controller\Admin\ProductController::addAttributeValueToCombinationAction + xml|json + + + + Thelia\Controller\Admin\ProductController::addCombinationAction + + + + Thelia\Controller\Admin\ProductController::deleteCombinationAction + + + + Thelia\Controller\Admin\ProductController::updateCombinationAction + + + + Thelia\Controller\Admin\ProductController::updateDefaultPriceAction + + + + + Thelia\Controller\Admin\FolderController::defaultAction @@ -398,6 +530,10 @@ Thelia\Controller\Admin\TemplateController::deleteFeatureAction + + Thelia\Controller\Admin\TemplateController::updateFeaturePositionAction + + Thelia\Controller\Admin\TemplateController::getAjaxAttributesAction @@ -410,6 +546,10 @@ Thelia\Controller\Admin\TemplateController::deleteAttributeAction + + Thelia\Controller\Admin\TemplateController::updateAttributePositionAction + + diff --git a/core/lib/Thelia/Controller/Admin/AbstractCrudController.php b/core/lib/Thelia/Controller/Admin/AbstractCrudController.php index a86f55b13..d2e7e5508 100644 --- a/core/lib/Thelia/Controller/Admin/AbstractCrudController.php +++ b/core/lib/Thelia/Controller/Admin/AbstractCrudController.php @@ -58,6 +58,7 @@ abstract class AbstractCrudController extends BaseAdminController * @param string $objectName the lower case object name. Example. "message" * * @param string $defaultListOrder the default object list order, or null if list is not sortable. Example: manual + * @param string $orderRequestParameterName Name of the request parameter that set the list order (null if list is not sortable) * * @param string $viewPermissionIdentifier the 'view' permission identifier. Example: "admin.configuration.message.view" * @param string $createPermissionIdentifier the 'create' permission identifier. Example: "admin.configuration.message.create" @@ -445,6 +446,8 @@ abstract class AbstractCrudController extends BaseAdminController /** * Update object position (only for objects whichsupport that) + * + * FIXME: integrate with genericUpdatePositionAction */ public function updatePositionAction() { @@ -482,6 +485,38 @@ abstract class AbstractCrudController extends BaseAdminController } } + protected function genericUpdatePositionAction($object, $eventName, $doFinalRedirect = true) { + + // Check current user authorization + if (null !== $response = $this->checkAuth($this->updatePermissionIdentifier)) return $response; + + if ($object != null) { + + try { + $mode = $this->getRequest()->get('mode', null); + + if ($mode == 'up') + $mode = UpdatePositionEvent::POSITION_UP; + else if ($mode == 'down') + $mode = UpdatePositionEvent::POSITION_DOWN; + else + $mode = UpdatePositionEvent::POSITION_ABSOLUTE; + + $position = $this->getRequest()->get('position', null); + + $event = new UpdatePositionEvent($object->getId(), $mode, $position); + + $this->dispatch($eventName, $event); + } + catch (\Exception $ex) { + // Any error + return $this->errorPage($ex); + } + } + + if ($doFinalRedirect) $this->redirectToEditionTemplate(); + } + /** * Online status toggle (only for object which support it) */ diff --git a/core/lib/Thelia/Controller/Admin/BaseAdminController.php b/core/lib/Thelia/Controller/Admin/BaseAdminController.php index 1e0f65055..fa505b5c3 100755 --- a/core/lib/Thelia/Controller/Admin/BaseAdminController.php +++ b/core/lib/Thelia/Controller/Admin/BaseAdminController.php @@ -42,6 +42,7 @@ use Thelia\Log\Tlog; use Symfony\Component\Routing\Router; use Thelia\Model\Admin; use Thelia\Core\Security\Token\CookieTokenProvider; +use Thelia\Model\CurrencyQuery; class BaseAdminController extends BaseController { @@ -250,6 +251,23 @@ class BaseAdminController extends BaseController $this->redirect(URL::getInstance()->absoluteUrl($this->getRoute($routeId), $urlParameters)); } + /** + * Get the current edition currency ID, checking if a change was requested in the current request. + */ + protected function getCurrentEditionCurrency() + { + // Return the new language if a change is required. + if (null !== $edit_currency_id = $this->getRequest()->get('edit_currency_id', null)) { + + if (null !== $edit_currency = CurrencyQuery::create()->findOneById($edit_currency_id)) { + return $edit_currency; + } + } + + // Otherwise return the lang stored in session. + return $this->getSession()->getAdminEditionCurrency(); + } + /** * Get the current edition lang ID, checking if a change was requested in the current request. */ @@ -376,6 +394,9 @@ class BaseAdminController extends BaseController // Find the current edit language ID $edition_language = $this->getCurrentEditionLang(); + // Find the current edit currency ID + $edition_currency = $this->getCurrentEditionCurrency(); + // Prepare common template variables $args = array_merge($args, array( 'locale' => $session->getLang()->getLocale(), @@ -385,11 +406,16 @@ class BaseAdminController extends BaseController 'edit_language_id' => $edition_language->getId(), 'edit_language_locale' => $edition_language->getLocale(), + 'edit_currency_id' => $edition_currency->getId(), + 'current_url' => $this->getRequest()->getUri() )); - // Update the current edition language in session - $this->getSession()->setAdminEditionLang($edition_language); + // Update the current edition language & currency in session + $this->getSession() + ->setAdminEditionLang($edition_language) + ->setAdminEditionCurrency($edition_currency) + ; // Render the template. try { diff --git a/core/lib/Thelia/Controller/Admin/CategoryController.php b/core/lib/Thelia/Controller/Admin/CategoryController.php index 4d0d15ef1..0b2b310ef 100755 --- a/core/lib/Thelia/Controller/Admin/CategoryController.php +++ b/core/lib/Thelia/Controller/Admin/CategoryController.php @@ -23,10 +23,13 @@ namespace Thelia\Controller\Admin; +use Symfony\Component\HttpFoundation\Response; use Thelia\Core\Event\CategoryDeleteEvent; +use Thelia\Core\Event\ImageCreateOrUpdateEvent; use Thelia\Core\Event\TheliaEvents; use Thelia\Core\Event\CategoryUpdateEvent; use Thelia\Core\Event\CategoryCreateEvent; +use Thelia\Log\Tlog; use Thelia\Model\CategoryQuery; use Thelia\Form\CategoryModificationForm; use Thelia\Form\CategoryCreationForm; @@ -34,7 +37,6 @@ use Thelia\Core\Event\UpdatePositionEvent; use Thelia\Core\Event\CategoryToggleVisibilityEvent; use Thelia\Core\Event\CategoryDeleteContentEvent; use Thelia\Core\Event\CategoryAddContentEvent; -use Thelia\Model\CategoryAssociatedContent; use Thelia\Model\FolderQuery; use Thelia\Model\ContentQuery; use Propel\Runtime\ActiveQuery\Criteria; @@ -306,6 +308,39 @@ class CategoryController extends AbstractCrudController $this->redirectToEditionTemplate(); } + /** + * Add category pictures + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function addRelatedPictureAction() + { + // Check current user authorization + if (null !== $response = $this->checkAuth("admin.categories.update")) { + return $response; + } + +// $content_id = intval($this->getRequest()->get('content_id')); +// +// if ($content_id > 0) { +// +// $event = new CategoryAddContentEvent( +// $this->getExistingObject(), +// $content_id +// ); +// +// try { +// $this->dispatch(TheliaEvents::CATEGORY_ADD_CONTENT, $event); +// } +// catch (\Exception $ex) { +// // Any error +// return $this->errorPage($ex); +// } +// } + + $this->redirectToEditionTemplate(); + } + public function deleteRelatedContentAction() { // Check current user authorization @@ -331,4 +366,5 @@ class CategoryController extends AbstractCrudController $this->redirectToEditionTemplate(); } + } diff --git a/core/lib/Thelia/Controller/Admin/FileController.php b/core/lib/Thelia/Controller/Admin/FileController.php new file mode 100755 index 000000000..f43f30fe6 --- /dev/null +++ b/core/lib/Thelia/Controller/Admin/FileController.php @@ -0,0 +1,680 @@ +. */ +/* */ +/**********************************************************************************/ + +namespace Thelia\Controller\Admin; + +use Propel\Runtime\Exception\PropelException; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Router; +use Thelia\Core\Event\DocumentCreateOrUpdateEvent; +use Thelia\Core\Event\DocumentDeleteEvent; +use Thelia\Core\Event\ImageCreateOrUpdateEvent; +use Thelia\Core\Event\ImageDeleteEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\Translation\Translator; +use Thelia\Form\Exception\FormValidationException; +use Thelia\Log\Tlog; +use Thelia\Model\CategoryDocument; +use Thelia\Model\CategoryImage; +use Thelia\Model\ContentDocument; +use Thelia\Model\ContentImage; +use Thelia\Model\FolderDocument; +use Thelia\Model\FolderImage; +use Thelia\Model\ProductDocument; +use Thelia\Model\ProductImage; +use Thelia\Tools\FileManager; +use Thelia\Tools\Rest\ResponseRest; + +/** + * Created by JetBrains PhpStorm. + * Date: 8/19/13 + * Time: 3:24 PM + * + * Control View and Action (Model) via Events + * Control Files and Images + * + * @package File + * @author Guillaume MOREL + * + */ +class FileController extends BaseAdminController +{ + + + /** + * Manage how a image collection has to be saved + * + * @param int $parentId Parent id owning images being saved + * @param string $parentType Parent Type owning images being saved + * + * @return Response + */ + public function saveImageAjaxAction($parentId, $parentType) + { + $this->checkAuth('ADMIN', 'admin.image.save'); + $this->checkXmlHttpRequest(); + + if ($this->isParentTypeValid($parentType)) { + if ($this->getRequest()->isMethod('POST')) { + + /** @var UploadedFile $fileBeingUploaded */ + $fileBeingUploaded = $this->getRequest()->files->get('file'); + + $fileManager = new FileManager($this->container); + + // Validate if file is too big + if ($fileBeingUploaded->getError() == 1) { + $message = $this->getTranslator() + ->trans( + 'File is too heavy, please retry with a file having a size less than %size%.', + array('%size%' => ini_get('post_max_size')), + 'image' + ); + + return new ResponseRest($message, 'text', 403); + } + // Validate if it is a image or file + if (!$fileManager->isImage($fileBeingUploaded->getMimeType())) { + $message = $this->getTranslator() + ->trans( + 'You can only upload images (.png, .jpg, .jpeg, .gif)', + array(), + 'image' + ); + + return new ResponseRest($message, 'text', 415); + } + + $parentModel = $fileManager->getParentFileModel($parentType, $parentId); + $imageModel = $fileManager->getImageModel($parentType); + + if ($parentModel === null || $imageModel === null || $fileBeingUploaded === null) { + return new Response('', 404); + } + + $defaultTitle = $parentModel->getTitle(); + $imageModel->setParentId($parentId); + $imageModel->setTitle($defaultTitle); + + $imageCreateOrUpdateEvent = new ImageCreateOrUpdateEvent( + $parentType, + $parentId + ); + $imageCreateOrUpdateEvent->setModelImage($imageModel); + $imageCreateOrUpdateEvent->setUploadedFile($fileBeingUploaded); + $imageCreateOrUpdateEvent->setParentName($parentModel->getTitle()); + + + // Dispatch Event to the Action + $this->dispatch( + TheliaEvents::IMAGE_SAVE, + $imageCreateOrUpdateEvent + ); + + + return new ResponseRest(array('status' => true, 'message' => '')); + } + } + + return new Response('', 404); + } + + /** + * Manage how a document collection has to be saved + * + * @param int $parentId Parent id owning documents being saved + * @param string $parentType Parent Type owning documents being saved + * + * @return Response + */ + public function saveDocumentAjaxAction($parentId, $parentType) + { + $this->checkAuth('ADMIN', 'admin.document.save'); + $this->checkXmlHttpRequest(); + + if ($this->isParentTypeValid($parentType)) { + if ($this->getRequest()->isMethod('POST')) { + + /** @var UploadedFile $fileBeingUploaded */ + $fileBeingUploaded = $this->getRequest()->files->get('file'); + + $fileManager = new FileManager($this->container); + + // Validate if file is too big + if ($fileBeingUploaded->getError() == 1) { + $message = $this->getTranslator() + ->trans( + 'File is too heavy, please retry with a file having a size less than %size%.', + array('%size%' => ini_get('post_max_size')), + 'document' + ); + + return new ResponseRest($message, 'text', 403); + } + + $parentModel = $fileManager->getParentFileModel($parentType, $parentId); + $documentModel = $fileManager->getDocumentModel($parentType); + + if ($parentModel === null || $documentModel === null || $fileBeingUploaded === null) { + return new Response('', 404); + } + + $documentModel->setParentId($parentId); + $documentModel->setTitle($fileBeingUploaded->getClientOriginalName()); + + $documentCreateOrUpdateEvent = new DocumentCreateOrUpdateEvent( + $parentType, + $parentId + ); + $documentCreateOrUpdateEvent->setModelDocument($documentModel); + $documentCreateOrUpdateEvent->setUploadedFile($fileBeingUploaded); + $documentCreateOrUpdateEvent->setParentName($parentModel->getTitle()); + + + // Dispatch Event to the Action + $this->dispatch( + TheliaEvents::DOCUMENT_SAVE, + $documentCreateOrUpdateEvent + ); + + + return new ResponseRest(array('status' => true, 'message' => '')); + } + } + + return new Response('', 404); + } + + /** + * Manage how a image list will be displayed in AJAX + * + * @param int $parentId Parent id owning images being saved + * @param string $parentType Parent Type owning images being saved + * + * @return Response + */ + public function getImageListAjaxAction($parentId, $parentType) + { + $this->checkAuth('ADMIN', 'admin.image.save'); + $this->checkXmlHttpRequest(); + $args = array('imageType' => $parentType, 'parentId' => $parentId); + + return $this->render('includes/image-upload-list-ajax', $args); + } + + /** + * Manage how a document list will be displayed in AJAX + * + * @param int $parentId Parent id owning documents being saved + * @param string $parentType Parent Type owning documents being saved + * + * @return Response + */ + public function getDocumentListAjaxAction($parentId, $parentType) + { + $this->checkAuth('ADMIN', 'admin.document.save'); + $this->checkXmlHttpRequest(); + $args = array('documentType' => $parentType, 'parentId' => $parentId); + + return $this->render('includes/document-upload-list-ajax', $args); + } + + /** + * Manage how an image list will be uploaded in AJAX + * + * @param int $parentId Parent id owning images being saved + * @param string $parentType Parent Type owning images being saved + * + * @return Response + */ + public function getImageFormAjaxAction($parentId, $parentType) + { + $this->checkAuth('ADMIN', 'admin.image.save'); + $this->checkXmlHttpRequest(); + $args = array('imageType' => $parentType, 'parentId' => $parentId); + + return $this->render('includes/image-upload-form', $args); + } + + /** + * Manage how an document list will be uploaded in AJAX + * + * @param int $parentId Parent id owning documents being saved + * @param string $parentType Parent Type owning documents being saved + * + * @return Response + */ + public function getDocumentFormAjaxAction($parentId, $parentType) + { + $this->checkAuth('ADMIN', 'admin.document.save'); + $this->checkXmlHttpRequest(); + $args = array('documentType' => $parentType, 'parentId' => $parentId); + + return $this->render('includes/document-upload-form', $args); + } + + /** + * Manage how an image is viewed + * + * @param int $imageId Parent id owning images being saved + * @param string $parentType Parent Type owning images being saved + * + * @return Response + */ + public function viewImageAction($imageId, $parentType) + { + if (null !== $response = $this->checkAuth('admin.image.view')) { + return $response; + } + try { + $fileManager = new FileManager($this->container); + $image = $fileManager->getImageModelQuery($parentType)->findPk($imageId); + $redirectUrl = $fileManager->getRedirectionUrl($parentType, $image->getParentId(), FileManager::FILE_TYPE_IMAGES); + + return $this->render('image-edit', array( + 'imageId' => $imageId, + 'imageType' => $parentType, + 'redirectUrl' => $redirectUrl, + 'formId' => $fileManager->getFormId($parentType, FileManager::FILE_TYPE_IMAGES) + )); + } catch (\Exception $e) { + $this->pageNotFound(); + } + } + + /** + * Manage how an document is viewed + * + * @param int $documentId Parent id owning images being saved + * @param string $parentType Parent Type owning images being saved + * + * @return Response + */ + public function viewDocumentAction($documentId, $parentType) + { + if (null !== $response = $this->checkAuth('admin.document.view')) { + return $response; + } + try { + $fileManager = new FileManager($this->container); + $document = $fileManager->getDocumentModelQuery($parentType)->findPk($documentId); + $redirectUrl = $fileManager->getRedirectionUrl($parentType, $document->getParentId(), FileManager::FILE_TYPE_DOCUMENTS); + + return $this->render('document-edit', array( + 'documentId' => $documentId, + 'documentType' => $parentType, + 'redirectUrl' => $redirectUrl, + 'formId' => $fileManager->getFormId($parentType, FileManager::FILE_TYPE_DOCUMENTS) + )); + } catch (\Exception $e) { + $this->pageNotFound(); + } + } + + /** + * Manage how an image is updated + * + * @param int $imageId Parent id owning images being saved + * @param string $parentType Parent Type owning images being saved + * + * @return Response + */ + public function updateImageAction($imageId, $parentType) + { + if (null !== $response = $this->checkAuth('admin.image.update')) { + return $response; + } + + $message = false; + + $fileManager = new FileManager($this->container); + $imageModification = $fileManager->getImageForm($parentType, $this->getRequest()); + + try { + $image = $fileManager->getImageModelQuery($parentType)->findPk($imageId); + $oldImage = clone $image; + if (null === $image) { + throw new \InvalidArgumentException(sprintf('%d image id does not exist', $imageId)); + } + + $form = $this->validateForm($imageModification); + + $event = $this->createImageEventInstance($parentType, $image, $form->getData()); + $event->setOldModelImage($oldImage); + + $files = $this->getRequest()->files; + $fileForm = $files->get($imageModification->getName()); + if (isset($fileForm['file'])) { + $event->setUploadedFile($fileForm['file']); + } + + $this->dispatch(TheliaEvents::IMAGE_UPDATE, $event); + + $imageUpdated = $event->getModelImage(); + + $this->adminLogAppend(sprintf('Image with Ref %s (ID %d) modified', $imageUpdated->getTitle(), $imageUpdated->getId())); + + if ($this->getRequest()->get('save_mode') == 'close') { + $this->redirectToRoute('admin.images'); + } else { + $this->redirectSuccess($imageModification); + } + + } catch (FormValidationException $e) { + $message = sprintf('Please check your input: %s', $e->getMessage()); + } catch (PropelException $e) { + $message = $e->getMessage(); + } catch (\Exception $e) { + $message = sprintf('Sorry, an error occurred: %s', $e->getMessage().' '.$e->getFile()); + } + + if ($message !== false) { + Tlog::getInstance()->error(sprintf('Error during image editing : %s.', $message)); + + $imageModification->setErrorMessage($message); + + $this->getParserContext() + ->addForm($imageModification) + ->setGeneralError($message); + } + + $redirectUrl = $fileManager->getRedirectionUrl($parentType, $image->getParentId(), FileManager::FILE_TYPE_IMAGES); + + return $this->render('image-edit', array( + 'imageId' => $imageId, + 'imageType' => $parentType, + 'redirectUrl' => $redirectUrl, + 'formId' => $fileManager->getFormId($parentType, FileManager::FILE_TYPE_IMAGES) + )); + } + + /** + * Manage how an document is updated + * + * @param int $documentId Parent id owning documents being saved + * @param string $parentType Parent Type owning documents being saved + * + * @return Response + */ + public function updateDocumentAction($documentId, $parentType) + { + if (null !== $response = $this->checkAuth('admin.document.update')) { + return $response; + } + + $message = false; + + $fileManager = new FileManager($this->container); + $documentModification = $fileManager->getDocumentForm($parentType, $this->getRequest()); + + try { + $document = $fileManager->getDocumentModelQuery($parentType)->findPk($documentId); + $oldDocument = clone $document; + if (null === $document) { + throw new \InvalidArgumentException(sprintf('%d document id does not exist', $documentId)); + } + + $form = $this->validateForm($documentModification); + + $event = $this->createDocumentEventInstance($parentType, $document, $form->getData()); + $event->setOldModelDocument($oldDocument); + + $files = $this->getRequest()->files; + $fileForm = $files->get($documentModification->getName()); + if (isset($fileForm['file'])) { + $event->setUploadedFile($fileForm['file']); + } + + $this->dispatch(TheliaEvents::DOCUMENT_UPDATE, $event); + + $documentUpdated = $event->getModelDocument(); + + $this->adminLogAppend(sprintf('Document with Ref %s (ID %d) modified', $documentUpdated->getTitle(), $documentUpdated->getId())); + + if ($this->getRequest()->get('save_mode') == 'close') { + $this->redirectToRoute('admin.documents'); + } else { + $this->redirectSuccess($documentModification); + } + + } catch (FormValidationException $e) { + $message = sprintf('Please check your input: %s', $e->getMessage()); + } catch (PropelException $e) { + $message = $e->getMessage(); + } catch (\Exception $e) { + $message = sprintf('Sorry, an error occurred: %s', $e->getMessage().' '.$e->getFile()); + } + + if ($message !== false) { + Tlog::getInstance()->error(sprintf('Error during document editing : %s.', $message)); + + $documentModification->setErrorMessage($message); + + $this->getParserContext() + ->addForm($documentModification) + ->setGeneralError($message); + } + + $redirectUrl = $fileManager->getRedirectionUrl($parentType, $document->getParentId(), FileManager::FILE_TYPE_DOCUMENTS); + + return $this->render('document-edit', array( + 'documentId' => $documentId, + 'documentType' => $parentType, + 'redirectUrl' => $redirectUrl, + 'formId' => $fileManager->getFormId($parentType, FileManager::FILE_TYPE_DOCUMENTS) + )); + } + + /** + * Manage how a image has to be deleted (AJAX) + * + * @param int $imageId Parent id owning image being deleted + * @param string $parentType Parent Type owning image being deleted + * + * @return Response + */ + public function deleteImageAction($imageId, $parentType) + { + $this->checkAuth('ADMIN', 'admin.image.delete'); + $this->checkXmlHttpRequest(); + + $fileManager = new FileManager($this->container); + $imageModelQuery = $fileManager->getImageModelQuery($parentType); + $model = $imageModelQuery->findPk($imageId); + + if ($model == null) { + return $this->pageNotFound(); + } + + // Feed event + $imageDeleteEvent = new ImageDeleteEvent( + $model, + $parentType + ); + + // Dispatch Event to the Action + $this->dispatch( + TheliaEvents::IMAGE_DELETE, + $imageDeleteEvent + ); + + $message = $this->getTranslator() + ->trans( + 'Images deleted successfully', + array(), + 'image' + ); + + return new Response($message); + } + + /** + * Manage how a document has to be deleted (AJAX) + * + * @param int $documentId Parent id owning document being deleted + * @param string $parentType Parent Type owning document being deleted + * + * @return Response + */ + public function deleteDocumentAction($documentId, $parentType) + { + $this->checkAuth('ADMIN', 'admin.document.delete'); + $this->checkXmlHttpRequest(); + + $fileManager = new FileManager($this->container); + $documentModelQuery = $fileManager->getDocumentModelQuery($parentType); + $model = $documentModelQuery->findPk($documentId); + + if ($model == null) { + return $this->pageNotFound(); + } + + // Feed event + $documentDeleteEvent = new DocumentDeleteEvent( + $model, + $parentType + ); + + // Dispatch Event to the Action + $this->dispatch( + TheliaEvents::DOCUMENT_DELETE, + $documentDeleteEvent + ); + + $message = $this->getTranslator() + ->trans( + 'Document deleted successfully', + array(), + 'document' + ); + + return new Response($message); + } + + /** + * Log error message + * + * @param string $parentType Parent type + * @param string $action Creation|Update|Delete + * @param string $message Message to log + * @param \Exception $e Exception to log + * + * @return $this + */ + protected function logError($parentType, $action, $message, $e) + { + Tlog::getInstance()->error( + sprintf( + 'Error during ' . $parentType . ' ' . $action . ' process : %s. Exception was %s', + $message, + $e->getMessage() + ) + ); + + return $this; + } + + /** + * Check if parent type is valid or not + * + * @param string $parentType Parent type + * + * @return bool + */ + public function isParentTypeValid($parentType) + { + return (in_array($parentType, FileManager::getAvailableTypes())); + } + + /** + * Create Image Event instance + * + * @param string $parentType Parent Type owning images being saved + * @param CategoryImage|ProductImage|ContentImage|FolderImage $model Image model + * @param array $data Post data + * + * @return ImageCreateOrUpdateEvent + */ + protected function createImageEventInstance($parentType, $model, $data) + { + $imageCreateEvent = new ImageCreateOrUpdateEvent($parentType, null); + + if (isset($data['title'])) { + $model->setTitle($data['title']); + } + if (isset($data['chapo'])) { + $model->setChapo($data['chapo']); + } + if (isset($data['description'])) { + $model->setDescription($data['description']); + } + if (isset($data['file'])) { + $model->setFile($data['file']); + } + if (isset($data['postscriptum'])) { + $model->setPostscriptum($data['postscriptum']); + } + + $imageCreateEvent->setModelImage($model); + + return $imageCreateEvent; + } + + /** + * Create Document Event instance + * + * @param string $parentType Parent Type owning documents being saved + * @param CategoryDocument|ProductDocument|ContentDocument|FolderDocument $model Document model + * @param array $data Post data + * + * @return DocumentCreateOrUpdateEvent + */ + protected function createDocumentEventInstance($parentType, $model, $data) + { + $documentCreateEvent = new DocumentCreateOrUpdateEvent($parentType, null); + + if (isset($data['title'])) { + $model->setTitle($data['title']); + } + if (isset($data['chapo'])) { + $model->setChapo($data['chapo']); + } + if (isset($data['description'])) { + $model->setDescription($data['description']); + } + if (isset($data['file'])) { + $model->setFile($data['file']); + } + if (isset($data['postscriptum'])) { + $model->setPostscriptum($data['postscriptum']); + } + + $documentCreateEvent->setModelDocument($model); + + return $documentCreateEvent; + } + + +} diff --git a/core/lib/Thelia/Controller/Admin/ProductController.php b/core/lib/Thelia/Controller/Admin/ProductController.php index 27c559442..342af6b14 100644 --- a/core/lib/Thelia/Controller/Admin/ProductController.php +++ b/core/lib/Thelia/Controller/Admin/ProductController.php @@ -43,6 +43,20 @@ use Thelia\Model\AccessoryQuery; use Thelia\Model\CategoryQuery; use Thelia\Core\Event\ProductAddAccessoryEvent; use Thelia\Core\Event\ProductDeleteAccessoryEvent; +use Thelia\Core\Event\FeatureProductUpdateEvent; +use Thelia\Model\FeatureQuery; +use Thelia\Core\Event\FeatureProductDeleteEvent; +use Thelia\Model\FeatureTemplateQuery; +use Thelia\Core\Event\ProductSetTemplateEvent; +use Thelia\Core\Event\ProductAddCategoryEvent; +use Thelia\Core\Event\ProductDeleteCategoryEvent; +use Thelia\Model\AttributeQuery; +use Thelia\Model\AttributeAvQuery; +use Thelia\Model\ProductSaleElementsQuery; +use Thelia\Model\AttributeCombination; +use Thelia\Model\AttributeAv; +use Thelia\Core\Event\ProductCreateCombinationEvent; +use Thelia\Core\Event\ProductDeleteCombinationEvent; /** * Manages products @@ -72,6 +86,35 @@ class ProductController extends AbstractCrudController ); } + /** + * Attributes ajax tab loading + */ + public function loadAttributesAjaxTabAction() { + + return $this->render( + 'ajax/product-attributes-tab', + array( + 'product_id' => $this->getRequest()->get('product_id', 0), + ) + ); + } + + /** + * Related information ajax tab loading + */ + public function loadRelatedAjaxTabAction() { + + return $this->render( + 'ajax/product-related-tab', + array( + 'product_id' => $this->getRequest()->get('product_id', 0), + 'folder_id' => $this->getRequest()->get('folder_id', 0), + 'accessory_category_id' => $this->getRequest()->get('accessory_category_id', 0) + + ) + ); + } + protected function getCreationForm() { return new ProductCreationForm($this->getRequest()); @@ -92,6 +135,10 @@ class ProductController extends AbstractCrudController ->setLocale($formData['locale']) ->setDefaultCategory($formData['default_category']) ->setVisible($formData['visible']) + ->setBasePrice($formData['price']) + ->setBaseWeight($formData['weight']) + ->setCurrencyId($formData['currency']) + ->setTaxRuleId($formData['tax_rule']) ; return $createEvent; @@ -110,8 +157,8 @@ class ProductController extends AbstractCrudController ->setPostscriptum($formData['postscriptum']) ->setVisible($formData['visible']) ->setUrl($formData['url']) - ->setParent($formData['parent']) - ; + ->setDefaultCategory($formData['default_category']) + ; return $changeEvent; } @@ -137,9 +184,15 @@ class ProductController extends AbstractCrudController protected function hydrateObjectForm($object) { + // Get the default produc sales element + $salesElement = ProductSaleElementsQuery::create()->filterByProduct($object)->filterByIsDefault(true)->findOne(); + +// $prices = $salesElement->getProductPrices(); + // Prepare the data that will hydrate the form $data = array( 'id' => $object->getId(), + 'ref' => $object->getRef(), 'locale' => $object->getLocale(), 'title' => $object->getTitle(), 'chapo' => $object->getChapo(), @@ -148,6 +201,8 @@ class ProductController extends AbstractCrudController 'visible' => $object->getVisible(), 'url' => $object->getRewrittenUrl($this->getCurrentEditionLocale()), 'default_category' => $object->getDefaultCategoryId() + + // A terminer pour les prix ); // Setup the object form @@ -179,10 +234,10 @@ class ProductController extends AbstractCrudController protected function getEditionArguments() { return array( - 'category_id' => $this->getCategoryId(), - 'product_id' => $this->getRequest()->get('product_id', 0), - 'folder_id' => $this->getRequest()->get('folder_id', 0), - 'accessory_category_id'=> $this->getRequest()->get('accessory_category_id', 0), + 'category_id' => $this->getCategoryId(), + 'product_id' => $this->getRequest()->get('product_id', 0), + 'folder_id' => $this->getRequest()->get('folder_id', 0), + 'accessory_category_id' => $this->getRequest()->get('accessory_category_id', 0), 'current_tab' => $this->getRequest()->get('current_tab', 'general') ); } @@ -417,7 +472,6 @@ class ProductController extends AbstractCrudController public function deleteAccessoryAction() { - // Check current user authorization if (null !== $response = $this->checkAuth("admin.products.update")) return $response; @@ -443,28 +497,294 @@ class ProductController extends AbstractCrudController } /** - * Update accessory position (only for objects whichsupport that) + * Update accessory position */ public function updateAccessoryPositionAction() { + $accessory = AccessoryQuery::create()->findPk($this->getRequest()->get('accessory_id', null)); + + return $this->genericUpdatePositionAction( + $accessory, + TheliaEvents::PRODUCT_UPDATE_ACCESSORY_POSITION + ); + } + + /** + * Update related content position + */ + public function updateContentPositionAction() + { + $content = ProductAssociatedContentQuery::create()->findPk($this->getRequest()->get('content_id', null)); + + return $this->genericUpdatePositionAction( + $content, + TheliaEvents::PRODUCT_UPDATE_CONTENT_POSITION + ); + } + + /** + * Change product template for a given product. + * + * @param unknown $productId + */ + public function setProductTemplateAction($productId) { // Check current user authorization if (null !== $response = $this->checkAuth('admin.products.update')) return $response; + $product = ProductQuery::create()->findPk($productId); + + if ($product != null) { + + $template_id = intval($this->getRequest()->get('template_id', 0)); + + $this->dispatch( + TheliaEvents::PRODUCT_SET_TEMPLATE, + new ProductSetTemplateEvent($product, $template_id) + ); + } + + $this->redirectToEditionTemplate(); + } + + /** + * Update product attributes and features + */ + public function updateAttributesAndFeaturesAction($productId) { + + $product = ProductQuery::create()->findPk($productId); + + if ($product != null) { + + $featureTemplate = FeatureTemplateQuery::create()->filterByTemplateId($product->getTemplateId())->find(); + + if ($featureTemplate !== null) { + + // Get all features for the template attached to this product + $allFeatures = FeatureQuery::create() + ->filterByFeatureTemplate($featureTemplate) + ->find(); + + $updatedFeatures = array(); + + // Update all features values, starting with feature av. values + $featureValues = $this->getRequest()->get('feature_value', array()); + + foreach($featureValues as $featureId => $featureValueList) { + + // Delete all features av. for this feature. + $event = new FeatureProductDeleteEvent($productId, $featureId); + + $this->dispatch(TheliaEvents::PRODUCT_FEATURE_DELETE_VALUE, $event); + + // Add then all selected values + foreach($featureValueList as $featureValue) { + $event = new FeatureProductUpdateEvent($productId, $featureId, $featureValue); + + $this->dispatch(TheliaEvents::PRODUCT_FEATURE_UPDATE_VALUE, $event); + } + + $updatedFeatures[] = $featureId; + } + + // Update then features text values + $featureTextValues = $this->getRequest()->get('feature_text_value', array()); + + foreach($featureTextValues as $featureId => $featureValue) { + + // considere empty text as empty feature value (e.g., we will delete it) + if (empty($featureValue)) continue; + + $event = new FeatureProductUpdateEvent($productId, $featureId, $featureValue, true); + + $this->dispatch(TheliaEvents::PRODUCT_FEATURE_UPDATE_VALUE, $event); + + $updatedFeatures[] = $featureId; + } + + // Delete features which don't have any values + foreach($allFeatures as $feature) { + + if (! in_array($feature->getId(), $updatedFeatures)) { + $event = new FeatureProductDeleteEvent($productId, $feature->getId()); + + $this->dispatch(TheliaEvents::PRODUCT_FEATURE_DELETE_VALUE, $event); + } + } + } + } + + // 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 category/product list + $this->redirectToListTemplate(); + } + + public function addAdditionalCategoryAction() { + + // Check current user authorization + if (null !== $response = $this->checkAuth("admin.products.update")) return $response; + + $category_id = intval($this->getRequest()->get('additional_category_id')); + + if ($category_id > 0) { + + $event = new ProductAddCategoryEvent( + $this->getExistingObject(), + $category_id + ); + + try { + $this->dispatch(TheliaEvents::PRODUCT_ADD_CATEGORY, $event); + } + catch (\Exception $ex) { + // Any error + return $this->errorPage($ex); + } + } + + $this->redirectToEditionTemplate(); + } + + public function deleteAdditionalCategoryAction() { + + // Check current user authorization + if (null !== $response = $this->checkAuth("admin.products.update")) return $response; + + $category_id = intval($this->getRequest()->get('additional_category_id')); + + if ($category_id > 0) { + + $event = new ProductDeleteCategoryEvent( + $this->getExistingObject(), + $category_id + ); + + try { + $this->dispatch(TheliaEvents::PRODUCT_REMOVE_CATEGORY, $event); + } + catch (\Exception $ex) { + // Any error + return $this->errorPage($ex); + } + } + + $this->redirectToEditionTemplate(); + } + + // -- Product combination management --------------------------------------- + + public function getAttributeValuesAction($productId, $attributeId) { + + $result = array(); + + // Get attribute for this product + $attribute = AttributeQuery::create()->findPk($attributeId); + + if ($attribute !== null) { + + $values = AttributeAvQuery::create() + ->joinWithI18n($this->getCurrentEditionLocale()) + ->filterByAttribute($attribute) + ->find(); + ; + + if ($values !== null) { + foreach($values as $value) { + $result[] = array('id' => $value->getId(), 'title' => $value->getTitle()); + } + } + } + + return $this->jsonResponse(json_encode($result)); + } + + public function addAttributeValueToCombinationAction($productId, $attributeAvId, $combination) { + + $result = array(); + + // Get attribute for this product + $attributeAv = AttributeAvQuery::create()->joinWithI18n($this->getCurrentEditionLocale())->findPk($attributeAvId); + + if ($attributeAv !== null) { + + $addIt = true; + + $attribute = $attributeAv->getAttribute(); + + // Check if this attribute is not already present + $combinationArray = explode(',', $combination); + + foreach ($combinationArray as $id) { + + $attrAv = AttributeAvQuery::create()->joinWithI18n($this->getCurrentEditionLocale())->findPk($id); + + if ($attrAv !== null) { + + if ($attrAv->getAttributeId() == $attribute->getId()) { + + $result['error'] = $this->getTranslator()->trans( + 'A value for attribute "%name" is already present in the combination', + array('%name' => $attribute->getTitle()) + ); + + $addIt = false; + } + + $result[] = array('id' => $attrAv->getId(), 'title' => $attrAv->getAttribute()->getTitle() . " : " . $attrAv->getTitle()); + } + } + + if ($addIt) $result[] = array('id' => $attributeAv->getId(), 'title' => $attribute->getTitle() . " : " . $attributeAv->getTitle()); + } + + return $this->jsonResponse(json_encode($result)); + } + + /** + * A a new combination to a product + */ + public function addCombinationAction() { + + // Check current user authorization + if (null !== $response = $this->checkAuth("admin.products.update")) return $response; + + $event = new ProductCreateCombinationEvent( + $this->getExistingObject(), + $this->getRequest()->get('combination_attributes', array()), + $this->getCurrentEditionCurrency()->getId() + ); + try { - $mode = $this->getRequest()->get('mode', null); + $this->dispatch(TheliaEvents::PRODUCT_ADD_COMBINATION, $event); + } + catch (\Exception $ex) { + // Any error + return $this->errorPage($ex); + } - if ($mode == 'up') - $mode = UpdatePositionEvent::POSITION_UP; - else if ($mode == 'down') - $mode = UpdatePositionEvent::POSITION_DOWN; - else - $mode = UpdatePositionEvent::POSITION_ABSOLUTE; + $this->redirectToEditionTemplate(); + } - $position = $this->getRequest()->get('position', null); - $event = new UpdatePositionEvent($mode, $position); + /** + * A a new combination to a product + */ + public function deleteCombinationAction() { - $this->dispatch(TheliaEvents::PRODUCT_UPDATE_ACCESSORY_POSITION, $event); + // Check current user authorization + if (null !== $response = $this->checkAuth("admin.products.update")) return $response; + + $event = new ProductDeleteCombinationEvent( + $this->getExistingObject(), + $this->getRequest()->get('product_sale_element_id',0) + ); + + try { + $this->dispatch(TheliaEvents::PRODUCT_DELETE_COMBINATION, $event); } catch (\Exception $ex) { // Any error diff --git a/core/lib/Thelia/Controller/Admin/TemplateController.php b/core/lib/Thelia/Controller/Admin/TemplateController.php index c9e30612c..fb32e4231 100644 --- a/core/lib/Thelia/Controller/Admin/TemplateController.php +++ b/core/lib/Thelia/Controller/Admin/TemplateController.php @@ -39,6 +39,8 @@ use Thelia\Core\Event\TemplateDeleteAttributeEvent; use Thelia\Core\Event\TemplateAddAttributeEvent; use Thelia\Core\Event\TemplateAddFeatureEvent; use Thelia\Core\Event\TemplateDeleteFeatureEvent; +use Thelia\Model\FeatureTemplateQuery; +use Thelia\Model\AttributeTemplateQuery; /** * Manages product templates @@ -255,6 +257,21 @@ class TemplateController extends AbstractCrudController $this->redirectToEditionTemplate(); } + public function updateAttributePositionAction() { + + // Find attribute_template + $attributeTemplate = AttributeTemplateQuery::create() + ->filterByTemplateId($this->getRequest()->get('template_id', null)) + ->filterByAttributeId($this->getRequest()->get('attribute_id', null)) + ->findOne() + ; + + return $this->genericUpdatePositionAction( + $attributeTemplate, + TheliaEvents::TEMPLATE_CHANGE_ATTRIBUTE_POSITION + ); + } + public function addFeatureAction() { // Check current user authorization @@ -299,4 +316,18 @@ class TemplateController extends AbstractCrudController $this->redirectToEditionTemplate(); } + public function updateFeaturePositionAction() { + + // Find feature_template + $featureTemplate = FeatureTemplateQuery::create() + ->filterByTemplateId($this->getRequest()->get('template_id', null)) + ->filterByFeatureId($this->getRequest()->get('feature_id', null)) + ->findOne() + ; + + return $this->genericUpdatePositionAction( + $featureTemplate, + TheliaEvents::TEMPLATE_CHANGE_FEATURE_POSITION + ); + } } \ No newline at end of file diff --git a/core/lib/Thelia/Core/Event/DocumentCreateOrUpdateEvent.php b/core/lib/Thelia/Core/Event/DocumentCreateOrUpdateEvent.php new file mode 100755 index 000000000..81e52257e --- /dev/null +++ b/core/lib/Thelia/Core/Event/DocumentCreateOrUpdateEvent.php @@ -0,0 +1,217 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Thelia\Model\CategoryDocument; +use Thelia\Model\ContentDocument; +use Thelia\Model\FolderDocument; +use Thelia\Model\ProductDocument; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Occurring when a Document is saved + * + * @package Document + * @author Guillaume MOREL + * + */ +class DocumentCreateOrUpdateEvent extends ActionEvent +{ + + /** @var CategoryDocument|ProductDocument|ContentDocument|FolderDocument model to save */ + protected $modelDocument = array(); + + /** @var CategoryDocument|ProductDocument|ContentDocument|FolderDocument model to save */ + protected $oldModelDocument = array(); + + /** @var UploadedFile Document file to save */ + protected $uploadedFile = null; + + /** @var int Document parent id */ + protected $parentId = null; + + /** @var string Document type */ + protected $documentType = null; + + /** @var string Parent name */ + protected $parentName = null; + + /** + * Constructor + * + * @param string $documentType Document type + * ex : FileManager::TYPE_CATEGORY + * @param int $parentId Document parent id + */ + public function __construct($documentType, $parentId) + { + $this->documentType = $documentType; + $this->parentId = $parentId; + } + + /** + * Set Document to save + * + * @param CategoryDocument|ProductDocument|ContentDocument|FolderDocument $document Document to save + * + * @return $this + */ + public function setModelDocument($document) + { + $this->modelDocument = $document; + + return $this; + } + + /** + * Get Document being saved + * + * @return CategoryDocument|ProductDocument|ContentDocument|FolderDocument + */ + public function getModelDocument() + { + return $this->modelDocument; + } + + /** + * Set document type + * + * @param string $documentType Document type + * + * @return $this + */ + public function setDocumentType($documentType) + { + $this->documentType = $documentType; + + return $this; + } + + /** + * Get document type + * + * @return string + */ + public function getDocumentType() + { + return $this->documentType; + } + + /** + * Set Document parent id + * + * @param int $parentId Document parent id + * + * @return $this + */ + public function setParentId($parentId) + { + $this->parentId = $parentId; + + return $this; + } + + /** + * Get Document parent id + * + * @return int + */ + public function getParentId() + { + return $this->parentId; + } + + /** + * Set uploaded file + * + * @param UploadedFile $uploadedFile File being uploaded + * + * @return $this + */ + public function setUploadedFile($uploadedFile) + { + $this->uploadedFile = $uploadedFile; + + return $this; + } + + /** + * Get uploaded file + * + * @return UploadedFile + */ + public function getUploadedFile() + { + return $this->uploadedFile; + } + + /** + * Set parent name + * + * @param string $parentName Parent name + * + * @return $this + */ + public function setParentName($parentName) + { + $this->parentName = $parentName; + + return $this; + } + + /** + * Get parent name + * + * @return string + */ + public function getParentName() + { + return $this->parentName; + } + + /** + * Set old model value + * + * @param CategoryDocument|ContentDocument|FolderDocument|ProductDocument $oldModelDocument + */ + public function setOldModelDocument($oldModelDocument) + { + $this->oldModelDocument = $oldModelDocument; + } + + /** + * Get old model value + * + * @return CategoryDocument|ContentDocument|FolderDocument|ProductDocument + */ + public function getOldModelDocument() + { + return $this->oldModelDocument; + } + + +} diff --git a/core/lib/Thelia/Core/Event/DocumentDeleteEvent.php b/core/lib/Thelia/Core/Event/DocumentDeleteEvent.php new file mode 100755 index 000000000..d9e2b7161 --- /dev/null +++ b/core/lib/Thelia/Core/Event/DocumentDeleteEvent.php @@ -0,0 +1,112 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; + +use Thelia\Model\CategoryDocument; +use Thelia\Model\ContentDocument; +use Thelia\Model\FolderDocument; +use Thelia\Model\ProductDocument; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Occurring when a Document is about to be deleted + * + * @package Document + * @author Guillaume MOREL + * + */ +class DocumentDeleteEvent extends ActionEvent +{ + /** @var string Document type */ + protected $documentType = null; + + /** @var CategoryDocument|ProductDocument|ContentDocument|FolderDocument Document about to be deleted */ + protected $documentToDelete = null; + + /** + * Constructor + * + * @param CategoryDocument|ProductDocument|ContentDocument|FolderDocument $documentToDelete Document about to be deleted + * @param string $documentType Document type + * ex : FileManager::TYPE_CATEGORY + */ + public function __construct($documentToDelete, $documentType) + { + $this->documentToDelete = $documentToDelete; + $this->documentType = $documentType; + } + + /** + * Set picture type + * + * @param string $documentType Document type + * + * @return $this + */ + public function setDocumentType($documentType) + { + $this->documentType = $documentType; + + return $this; + } + + /** + * Get picture type + * + * @return string + */ + public function getDocumentType() + { + return $this->documentType; + } + + /** + * Set Document about to be deleted + * + * @param CategoryDocument|ProductDocument|ContentDocument|FolderDocument $documentToDelete Document about to be deleted + * + * @return $this + */ + public function setDocumentToDelete($documentToDelete) + { + $this->documentToDelete = $documentToDelete; + + return $this; + } + + /** + * Get Document about to be deleted + * + * @return CategoryDocument|ProductDocument|ContentDocument|FolderDocument + */ + public function getDocumentToDelete() + { + return $this->documentToDelete; + } + + +} diff --git a/core/lib/Thelia/Core/Event/DocumentEvent.php b/core/lib/Thelia/Core/Event/DocumentEvent.php index f58d51083..6248b8d9a 100644 --- a/core/lib/Thelia/Core/Event/DocumentEvent.php +++ b/core/lib/Thelia/Core/Event/DocumentEvent.php @@ -23,43 +23,60 @@ namespace Thelia\Core\Event; +/** + * Class DocumentEvent + * + * @package Thelia\Core\Event + */ class DocumentEvent extends CachedFileEvent { - protected $document_path; - protected $document_url; + protected $documentPath; + protected $documentUrl; /** - * @return the document file path + * Get Document path + * + * @return string The document file path */ public function getDocumentPath() { - return $this->document_path; + return $this->documentPath; } /** - * @param string $document_path the document file path + * Set Document path + * + * @param string $documentPath the document file path + * + * @return $this */ - public function setDocumentPath($document_path) + public function setDocumentPath($documentPath) { - $this->document_path = $document_path; + $this->documentPath = $documentPath; return $this; } /** - * @return the document URL + * Get Document URL + * + * @return string The document URL */ public function getDocumentUrl() { - return $this->document_url; + return $this->documentUrl; } /** - * @param string $document_url the document URL + * Set Document URL + * + * @param string $documentUrl the document URL + * + * @return $this */ - public function setDocumentUrl($document_url) + public function setDocumentUrl($documentUrl) { - $this->document_url = $document_url; + $this->documentUrl = $documentUrl; return $this; } diff --git a/core/lib/Thelia/Core/Event/FeatureProductDeleteEvent.php b/core/lib/Thelia/Core/Event/FeatureProductDeleteEvent.php new file mode 100644 index 000000000..5c74c6287 --- /dev/null +++ b/core/lib/Thelia/Core/Event/FeatureProductDeleteEvent.php @@ -0,0 +1,61 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; +use Thelia\Model\FeatureProduct; + +class FeatureProductDeleteEvent extends FeatureProductEvent +{ + protected $product_id; + protected $feature_id; + + public function __construct($product_id, $feature_id) + { + $this->product_id = $product_id; + $this->feature_id = $feature_id; + } + + public function getProductId() + { + return $this->product_id; + } + + public function setProductId($product_id) + { + $this->product_id = $product_id; + + return $this; + } + + public function getFeatureId() + { + return $this->feature_id; + } + + public function setFeatureId($feature_id) + { + $this->feature_id = $feature_id; + + return $this; + } +} diff --git a/core/lib/Thelia/Core/Event/FeatureProductEvent.php b/core/lib/Thelia/Core/Event/FeatureProductEvent.php new file mode 100644 index 000000000..0acf48569 --- /dev/null +++ b/core/lib/Thelia/Core/Event/FeatureProductEvent.php @@ -0,0 +1,52 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; +use Thelia\Model\FeatureProduct; + +class FeatureProductEvent extends ActionEvent +{ + protected $featureProduct = null; + + public function __construct(FeatureProduct $featureProduct = null) + { + $this->featureProduct = $featureProduct; + } + + public function hasFeatureProduct() + { + return ! is_null($this->featureProduct); + } + + public function getFeatureProduct() + { + return $this->featureProduct; + } + + public function setFeatureProduct($featureProduct) + { + $this->featureProduct = $featureProduct; + + return $this; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Event/FeatureProductUpdateEvent.php b/core/lib/Thelia/Core/Event/FeatureProductUpdateEvent.php new file mode 100644 index 000000000..5493c55a7 --- /dev/null +++ b/core/lib/Thelia/Core/Event/FeatureProductUpdateEvent.php @@ -0,0 +1,89 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; +use Thelia\Model\FeatureProduct; + +class FeatureProductUpdateEvent extends FeatureProductEvent +{ + protected $product_id; + protected $feature_id; + protected $feature_value; + protected $is_text_value; + + public function __construct($product_id, $feature_id, $feature_value, $is_text_value = false) + { + $this->product_id = $product_id; + $this->feature_id = $feature_id; + $this->feature_value = $feature_value; + $this->is_text_value = $is_text_value; + } + + public function getProductId() + { + return $this->product_id; + } + + public function setProductId($product_id) + { + $this->product_id = $product_id; + + return $this; + } + + public function getFeatureId() + { + return $this->feature_id; + } + + public function setFeatureId($feature_id) + { + $this->feature_id = $feature_id; + + return $this; + } + + public function getFeatureValue() + { + return $this->feature_value; + } + + public function setFeatureValue($feature_value) + { + $this->feature_value = $feature_value; + + return $this; + } + + public function getIsTextValue() + { + return $this->is_text_value; + } + + public function setIsTextValue($is_text_value) + { + $this->is_text_value = $is_text_value; + + return $this; + } +} diff --git a/core/lib/Thelia/Core/Event/ImageCreateOrUpdateEvent.php b/core/lib/Thelia/Core/Event/ImageCreateOrUpdateEvent.php new file mode 100755 index 000000000..e7d5f3449 --- /dev/null +++ b/core/lib/Thelia/Core/Event/ImageCreateOrUpdateEvent.php @@ -0,0 +1,213 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; +use Symfony\Component\HttpFoundation\File\UploadedFile; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Occurring when an Image is saved + * + * @package Image + * @author Guillaume MOREL + * + */ +class ImageCreateOrUpdateEvent extends ActionEvent +{ + + /** @var \Thelia\Model\CategoryImage|\Thelia\Model\ProductImage|\Thelia\Model\ContentImage|\Thelia\Model\FolderImage model to save */ + protected $modelImage = array(); + + /** @var \Thelia\Model\CategoryImage|\Thelia\Model\ProductImage|\Thelia\Model\ContentImage|\Thelia\Model\FolderImage model to save */ + protected $oldModelImage = array(); + + /** @var UploadedFile Image file to save */ + protected $uploadedFile = null; + + /** @var int Image parent id */ + protected $parentId = null; + + /** @var string Image type */ + protected $imageType = null; + + /** @var string Parent name */ + protected $parentName = null; + + /** + * Constructor + * + * @param string $imageType Image type + * ex : FileManager::TYPE_CATEGORY + * @param int $parentId Image parent id + */ + public function __construct($imageType, $parentId) + { + $this->imageType = $imageType; + $this->parentId = $parentId; + } + + /** + * Set Image to save + * + * @param $image \Thelia\Model\CategoryImage|\Thelia\Model\ProductImage|\Thelia\Model\ContentImage|\Thelia\Model\FolderImage + * + * @return $this + */ + public function setModelImage($image) + { + $this->modelImage = $image; + + return $this; + } + + /** + * Get Image being saved + * + * @return \Thelia\Model\CategoryImage|\Thelia\Model\ProductImage|\Thelia\Model\ContentImage|\Thelia\Model\FolderImage + */ + public function getModelImage() + { + return $this->modelImage; + } + + /** + * Set picture type + * + * @param string $imageType Image type + * + * @return $this + */ + public function setImageType($imageType) + { + $this->imageType = $imageType; + + return $this; + } + + /** + * Get picture type + * + * @return string + */ + public function getImageType() + { + return $this->imageType; + } + + /** + * Set Image parent id + * + * @param int $parentId Image parent id + * + * @return $this + */ + public function setParentId($parentId) + { + $this->parentId = $parentId; + + return $this; + } + + /** + * Get Image parent id + * + * @return int + */ + public function getParentId() + { + return $this->parentId; + } + + /** + * Set uploaded file + * + * @param UploadedFile $uploadedFile File being uploaded + * + * @return $this + */ + public function setUploadedFile($uploadedFile) + { + $this->uploadedFile = $uploadedFile; + + return $this; + } + + /** + * Get uploaded file + * + * @return UploadedFile + */ + public function getUploadedFile() + { + return $this->uploadedFile; + } + + /** + * Set parent name + * + * @param string $parentName Parent name + * + * @return $this + */ + public function setParentName($parentName) + { + $this->parentName = $parentName; + + return $this; + } + + /** + * Get parent name + * + * @return string + */ + public function getParentName() + { + return $this->parentName; + } + + /** + * Set old model value + * + * @param \Thelia\Model\CategoryImage|\Thelia\Model\ContentImage|\Thelia\Model\FolderImage|\Thelia\Model\ProductImage $oldModelImage + */ + public function setOldModelImage($oldModelImage) + { + $this->oldModelImage = $oldModelImage; + } + + /** + * Get old model value + * + * @return \Thelia\Model\CategoryImage|\Thelia\Model\ContentImage|\Thelia\Model\FolderImage|\Thelia\Model\ProductImage + */ + public function getOldModelImage() + { + return $this->oldModelImage; + } + + +} diff --git a/core/lib/Thelia/Core/Event/ImageDeleteEvent.php b/core/lib/Thelia/Core/Event/ImageDeleteEvent.php new file mode 100755 index 000000000..9e890e999 --- /dev/null +++ b/core/lib/Thelia/Core/Event/ImageDeleteEvent.php @@ -0,0 +1,112 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; + +use Thelia\Model\CategoryImage; +use Thelia\Model\ContentImage; +use Thelia\Model\FolderImage; +use Thelia\Model\ProductImage; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Occurring when a Image is about to be deleted + * + * @package Image + * @author Guillaume MOREL + * + */ +class ImageDeleteEvent extends ActionEvent +{ + /** @var string Image type */ + protected $imageType = null; + + /** @var CategoryImage|ProductImage|ContentImage|FolderImage Image about to be deleted */ + protected $imageToDelete = null; + + /** + * Constructor + * + * @param CategoryImage|ProductImage|ContentImage|FolderImage $imageToDelete Image about to be deleted + * @param string $imageType Image type + * ex : FileManager::TYPE_CATEGORY + */ + public function __construct($imageToDelete, $imageType) + { + $this->imageToDelete = $imageToDelete; + $this->imageType = $imageType; + } + + /** + * Set picture type + * + * @param string $imageType Image type + * + * @return $this + */ + public function setImageType($imageType) + { + $this->imageType = $imageType; + + return $this; + } + + /** + * Get picture type + * + * @return string + */ + public function getImageType() + { + return $this->imageType; + } + + /** + * Set Image about to be deleted + * + * @param CategoryImage|ProductImage|ContentImage|FolderImage $imageToDelete Image about to be deleted + * + * @return $this + */ + public function setImageToDelete($imageToDelete) + { + $this->imageToDelete = $imageToDelete; + + return $this; + } + + /** + * Get Image about to be deleted + * + * @return CategoryImage|ProductImage|ContentImage|FolderImage + */ + public function getImageToDelete() + { + return $this->imageToDelete; + } + + +} diff --git a/core/lib/Thelia/Core/Event/ProductAddCategoryEvent.php b/core/lib/Thelia/Core/Event/ProductAddCategoryEvent.php new file mode 100644 index 000000000..215b61f99 --- /dev/null +++ b/core/lib/Thelia/Core/Event/ProductAddCategoryEvent.php @@ -0,0 +1,48 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; + +use Thelia\Model\Product; + +class ProductAddCategoryEvent extends ProductEvent +{ + protected $category_id; + + public function __construct(Product $product, $category_id) + { + parent::__construct($product); + + $this->category_id = $category_id; + } + + public function getCategoryId() + { + return $this->category_id; + } + + public function setCategoryId($category_id) + { + $this->category_id = $category_id; + } +} diff --git a/core/lib/Thelia/Core/Event/ProductCreateCombinationEvent.php b/core/lib/Thelia/Core/Event/ProductCreateCombinationEvent.php new file mode 100644 index 000000000..322451199 --- /dev/null +++ b/core/lib/Thelia/Core/Event/ProductCreateCombinationEvent.php @@ -0,0 +1,64 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; +use Thelia\Model\Product; + +class ProductCreateCombinationEvent extends ProductEvent +{ + protected $attribute_av_list; + protected $currency_id; + + public function __construct(Product $product, $attribute_av_list, $currency_id) + { + parent::__construct($product); + + $this->attribute_av_list = $attribute_av_list; + $this->currency_id = $currency_id; + } + + public function getAttributeAvList() + { + return $this->attribute_av_list; + } + + public function setAttributeAvList($attribute_av_list) + { + $this->attribute_av_list = $attribute_av_list; + + return $this; + } + + public function getCurrencyId() + { + return $this->currency_id; + } + + public function setCurrencyId($currency_id) + { + $this->currency_id = $currency_id; + + return $this; + } + +} diff --git a/core/lib/Thelia/Core/Event/ProductCreateEvent.php b/core/lib/Thelia/Core/Event/ProductCreateEvent.php index d2d30a11a..012d29793 100644 --- a/core/lib/Thelia/Core/Event/ProductCreateEvent.php +++ b/core/lib/Thelia/Core/Event/ProductCreateEvent.php @@ -31,6 +31,11 @@ class ProductCreateEvent extends ProductEvent protected $default_category; protected $visible; + protected $basePrice; + protected $baseWeight; + protected $taxRuleId; + protected $currencyId; + public function getRef() { return $this->ref; @@ -85,4 +90,48 @@ class ProductCreateEvent extends ProductEvent $this->visible = $visible; return $this; } + + public function getBasePrice() + { + return $this->basePrice; + } + + public function setBasePrice($basePrice) + { + $this->basePrice = $basePrice; + return $this; + } + + public function getBaseWeight() + { + return $this->baseWeight; + } + + public function setBaseWeight($baseWeight) + { + $this->baseWeight = $baseWeight; + return $this; + } + + public function getTaxRuleId() + { + return $this->taxRuleId; + } + + public function setTaxRuleId($taxRuleId) + { + $this->taxRuleId = $taxRuleId; + return $this; + } + + public function getCurrencyId() + { + return $this->currencyId; + } + + public function setCurrencyId($currencyId) + { + $this->currencyId = $currencyId; + return $this; + } } diff --git a/core/lib/Thelia/Core/Event/ProductDeleteCategoryEvent.php b/core/lib/Thelia/Core/Event/ProductDeleteCategoryEvent.php new file mode 100644 index 000000000..4fcfeee92 --- /dev/null +++ b/core/lib/Thelia/Core/Event/ProductDeleteCategoryEvent.php @@ -0,0 +1,50 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; +use Thelia\Model\Product; + +class ProductDeleteCategoryEvent extends ProductEvent +{ + protected $category_id; + + public function __construct(Product $product, $category_id) + { + parent::__construct($product); + + $this->category_id = $category_id; + } + + public function getCategoryId() + { + return $this->category_id; + } + + public function setCategoryId($category_id) + { + $this->category_id = $category_id; + + return $this; + } + +} diff --git a/core/lib/Thelia/Core/Event/ProductDeleteCombinationEvent.php b/core/lib/Thelia/Core/Event/ProductDeleteCombinationEvent.php new file mode 100644 index 000000000..3cc9a25ce --- /dev/null +++ b/core/lib/Thelia/Core/Event/ProductDeleteCombinationEvent.php @@ -0,0 +1,47 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; +use Thelia\Model\Product; + +class ProductDeleteCombinationEvent extends ProductEvent +{ + protected $product_sale_element_id; + + public function __construct(Product $product, $product_sale_element_id) + { + parent::__construct($product); + + $this->product_sale_element_id = $product_sale_element_id; + } + + public function getProductSaleElementId() + { + return $this->product_sale_element_id; + } + + public function setProductSaleElementId($product_sale_element_id) + { + $this->product_sale_element_id = $product_sale_element_id; + } +} diff --git a/core/lib/Thelia/Core/Event/ProductSetTemplateEvent.php b/core/lib/Thelia/Core/Event/ProductSetTemplateEvent.php new file mode 100644 index 000000000..c7c6dc760 --- /dev/null +++ b/core/lib/Thelia/Core/Event/ProductSetTemplateEvent.php @@ -0,0 +1,51 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; +use Thelia\Model\Product; +use Thelia\Core\Event\ActionEvent; + +class ProductSetTemplateEvent extends ProductEvent +{ + public $template_id = null; + + public function __construct(Product $product = null, $template_id) + { + parent::__construct($product); + + $this->template_id = $template_id; + } + + public function getTemplateId() + { + return $this->template_id; + } + + public function setTemplateId($template_id) + { + $this->template_id = $template_id; + + return $this; + } + +} diff --git a/core/lib/Thelia/Core/Event/TheliaEvents.php b/core/lib/Thelia/Core/Event/TheliaEvents.php index 478e0d945..3af6f195c 100755 --- a/core/lib/Thelia/Core/Event/TheliaEvents.php +++ b/core/lib/Thelia/Core/Event/TheliaEvents.php @@ -210,8 +210,8 @@ final class TheliaEvents const BEFORE_CREATECATEGORY_ASSOCIATED_CONTENT = "action.before_createCategoryAssociatedContent"; const AFTER_CREATECATEGORY_ASSOCIATED_CONTENT = "action.after_createCategoryAssociatedContent"; - const BEFORE_DELETECATEGORY_ASSOCIATED_CONTENT = "action.before_deleteCategoryAssociatedContenty"; - const AFTER_DELETECATEGORY_ASSOCIATED_CONTENT = "action.after_deleteproduct_accessory"; + const BEFORE_DELETECATEGORY_ASSOCIATED_CONTENT = "action.before_deleteCategoryAssociatedContent"; + const AFTER_DELETECATEGORY_ASSOCIATED_CONTENT = "action.after_deleteCategoryAssociatedContent"; const BEFORE_UPDATECATEGORY_ASSOCIATED_CONTENT = "action.before_updateCategoryAssociatedContent"; const AFTER_UPDATECATEGORY_ASSOCIATED_CONTENT = "action.after_updateCategoryAssociatedContent"; @@ -224,12 +224,24 @@ final class TheliaEvents const PRODUCT_TOGGLE_VISIBILITY = "action.toggleProductVisibility"; const PRODUCT_UPDATE_POSITION = "action.updateProductPosition"; - const PRODUCT_ADD_CONTENT = "action.productAddContent"; - const PRODUCT_REMOVE_CONTENT = "action.productRemoveContent"; + const PRODUCT_ADD_CONTENT = "action.productAddContent"; + const PRODUCT_REMOVE_CONTENT = "action.productRemoveContent"; + const PRODUCT_UPDATE_CONTENT_POSITION = "action.updateProductContentPosition"; - const PRODUCT_ADD_ACCESSORY = "action.productAddAccessory"; - const PRODUCT_REMOVE_ACCESSORY = "action.productRemoveAccessory"; - const PRODUCT_UPDATE_ACCESSORY_POSITION = "action.updateProductPosition"; + const PRODUCT_ADD_COMBINATION = "action.productAddCombination"; + const PRODUCT_DELETE_COMBINATION = "action.productDeleteCombination"; + + const PRODUCT_SET_TEMPLATE = "action.productSetTemplate"; + + const PRODUCT_ADD_ACCESSORY = "action.productAddProductAccessory"; + const PRODUCT_REMOVE_ACCESSORY = "action.productRemoveProductAccessory"; + const PRODUCT_UPDATE_ACCESSORY_POSITION = "action.updateProductAccessoryPosition"; + + const PRODUCT_FEATURE_UPDATE_VALUE = "action.updateProductFeatureValue"; + const PRODUCT_FEATURE_DELETE_VALUE = "action.deleteProductFeatureValue"; + + const PRODUCT_ADD_CATEGORY = "action.addProductCategory"; + const PRODUCT_REMOVE_CATEGORY = "action.deleteProductCategory"; const BEFORE_CREATEPRODUCT = "action.before_createproduct"; const AFTER_CREATEPRODUCT = "action.after_createproduct"; @@ -251,17 +263,28 @@ final class TheliaEvents const BEFORE_UPDATEACCESSORY = "action.before_updateAccessory"; const AFTER_UPDATEACCESSORY = "action.after_updateAccessory"; - // -- Product Associated Content -------------------------------------------------- + // -- Product Associated Content ------------------------------------------- const BEFORE_CREATEPRODUCT_ASSOCIATED_CONTENT = "action.before_createProductAssociatedContent"; const AFTER_CREATEPRODUCT_ASSOCIATED_CONTENT = "action.after_createProductAssociatedContent"; - const BEFORE_DELETEPRODUCT_ASSOCIATED_CONTENT = "action.before_deleteProductAssociatedContenty"; - const AFTER_DELETEPRODUCT_ASSOCIATED_CONTENT = "action.after_deleteproduct_accessory"; + const BEFORE_DELETEPRODUCT_ASSOCIATED_CONTENT = "action.before_deleteProductAssociatedContent"; + const AFTER_DELETEPRODUCT_ASSOCIATED_CONTENT = "action.after_deleteProductAssociatedContent"; const BEFORE_UPDATEPRODUCT_ASSOCIATED_CONTENT = "action.before_updateProductAssociatedContent"; const AFTER_UPDATEPRODUCT_ASSOCIATED_CONTENT = "action.after_updateProductAssociatedContent"; + // -- Feature product ------------------------------------------------------ + + const BEFORE_CREATEFEATURE_PRODUCT = "action.before_createFeatureProduct"; + const AFTER_CREATEFEATURE_PRODUCT = "action.after_createFeatureProduct"; + + const BEFORE_DELETEFEATURE_PRODUCT = "action.before_deleteFeatureProduct"; + const AFTER_DELETEFEATURE_PRODUCT = "action.after_deleteFeatureProduct"; + + const BEFORE_UPDATEFEATURE_PRODUCT = "action.before_updateFeatureProduct"; + const AFTER_UPDATEFEATURE_PRODUCT = "action.after_updateFeatureProduct"; + /** * sent when a new existing cat id duplicated. This append when current customer is different from current cart */ @@ -314,11 +337,46 @@ final class TheliaEvents */ const DOCUMENT_PROCESS = "action.processDocument"; + /** + * Sent on image cache clear request + */ + const DOCUMENT_CLEAR_CACHE = "action.clearDocumentCache"; + + /** + * Save given documents + */ + const DOCUMENT_SAVE = "action.saveDocument"; + + /** + * Save given documents + */ + const DOCUMENT_UPDATE = "action.updateDocument"; + + /** + * Delete given document + */ + const DOCUMENT_DELETE = "action.deleteDocument"; + /** * Sent on image cache clear request */ const IMAGE_CLEAR_CACHE = "action.clearImageCache"; + /** + * Save given images + */ + const IMAGE_SAVE = "action.saveImages"; + + /** + * Save given images + */ + const IMAGE_UPDATE = "action.updateImages"; + + /** + * Delete given image + */ + const IMAGE_DELETE = "action.deleteImage"; + /** * Sent when creating a Coupon */ @@ -429,6 +487,7 @@ final class TheliaEvents const AFTER_DELETECURRENCY = "action.after_deleteCurrency"; const CHANGE_DEFAULT_CURRENCY = 'action.changeDefaultCurrency'; + // -- Product templates management ----------------------------------------- const TEMPLATE_CREATE = "action.createTemplate"; @@ -441,6 +500,9 @@ final class TheliaEvents const TEMPLATE_ADD_FEATURE = "action.templateAddFeature"; const TEMPLATE_DELETE_FEATURE = "action.templateDeleteFeature"; + const TEMPLATE_CHANGE_FEATURE_POSITION = "action.templateChangeAttributePosition"; + const TEMPLATE_CHANGE_ATTRIBUTE_POSITION = "action.templateChangeFeaturePosition"; + const BEFORE_CREATETEMPLATE = "action.before_createTemplate"; const AFTER_CREATETEMPLATE = "action.after_createTemplate"; diff --git a/core/lib/Thelia/Core/HttpFoundation/Session/Session.php b/core/lib/Thelia/Core/HttpFoundation/Session/Session.php index 8a0952ff4..798a9c7fd 100755 --- a/core/lib/Thelia/Core/HttpFoundation/Session/Session.php +++ b/core/lib/Thelia/Core/HttpFoundation/Session/Session.php @@ -65,23 +65,6 @@ class Session extends BaseSession return $this; } - public function getAdminEditionLang() - { - $lang = $this->get('thelia.admin.edition.lang'); - - if (null === $lang) { - $lang = Lang::getDefaultLanguage(); - } - return $lang; - } - - public function setAdminEditionLang($langId) - { - $this->set('thelia.admin.edition.lang', $langId); - - return $this; - } - public function setCurrency(Currency $currency) { $this->set("thelia.current.currency", $currency); @@ -98,6 +81,43 @@ class Session extends BaseSession return $currency; } + // -- Admin lang and currency ---------------------------------------------- + + public function getAdminEditionCurrency() + { + $currency = $this->get('thelia.admin.edition.currency', null); + + if (null === $currency) { + $currency = Currency::getDefaultCurrency(); + } + + return $currency; + } + + public function setAdminEditionCurrency($currencyId) + { + $this->set('thelia.admin.edition.currency', $currencyId); + + return $this; + } + + public function getAdminEditionLang() + { + $lang = $this->get('thelia.admin.edition.lang'); + + if (null === $lang) { + $lang = Lang::getDefaultLanguage(); + } + return $lang; + } + + public function setAdminEditionLang($lang) + { + $this->set('thelia.admin.edition.lang', $lang); + + return $this; + } + // -- Customer user -------------------------------------------------------- public function setCustomerUser(UserInterface $user) diff --git a/core/lib/Thelia/Core/Template/Loop/Accessory.php b/core/lib/Thelia/Core/Template/Loop/Accessory.php index 6dc269b62..34d674c10 100755 --- a/core/lib/Thelia/Core/Template/Loop/Accessory.php +++ b/core/lib/Thelia/Core/Template/Loop/Accessory.php @@ -74,6 +74,7 @@ class Accessory extends Product $search = AccessoryQuery::create(); $product = $this->getProduct(); + $search->filterByProductId($product, Criteria::IN); $order = $this->getOrder(); @@ -93,10 +94,16 @@ class Accessory extends Product $accessories = $this->search($search); $accessoryIdList = array(0); - $accessoryPosition = array(); + $accessoryPosition = $accessoryId = array(); + foreach ($accessories as $accessory) { - array_push($accessoryIdList, $accessory->getAccessory()); - $accessoryPosition[$accessory->getAccessory()] = $accessory->getPosition(); + + $accessoryProductId = $accessory->getAccessory(); + + array_push($accessoryIdList, $accessoryProductId); + + $accessoryPosition[$accessoryProductId] = $accessory->getPosition(); + $accessoryId[$accessoryProductId] = $accessory->getId(); } $receivedIdList = $this->getId(); @@ -111,12 +118,15 @@ class Accessory extends Product $loopResult = parent::exec($pagination); foreach($loopResult as $loopResultRow) { + + $accessoryProductId = $loopResultRow->get('ID'); + $loopResultRow - ->set("POSITION" , $accessoryPosition[$loopResultRow->get('ID')]) - ; + ->set("ID" , $accessoryId[$accessoryProductId]) + ->set("POSITION", $accessoryPosition[$accessoryProductId]) + ; } return $loopResult; } - } diff --git a/core/lib/Thelia/Core/Template/Loop/AssociatedContent.php b/core/lib/Thelia/Core/Template/Loop/AssociatedContent.php index 20fe5cb1e..78794b715 100755 --- a/core/lib/Thelia/Core/Template/Loop/AssociatedContent.php +++ b/core/lib/Thelia/Core/Template/Loop/AssociatedContent.php @@ -135,8 +135,17 @@ class AssociatedContent extends Content $associatedContents = $this->search($search); $associatedContentIdList = array(0); + + $contentIdList = array(0); + $contentPosition = $contentId = array(); + foreach ($associatedContents as $associatedContent) { - array_push($associatedContentIdList, $associatedContent->getContentId()); + + $associatedContentId = $associatedContent->getContentId(); + + array_push($associatedContentIdList, $associatedContentId); + $contentPosition[$associatedContentId] = $associatedContent->getPosition(); + $contentId[$associatedContentId] = $associatedContent->getId(); } $receivedIdList = $this->getId(); @@ -148,7 +157,18 @@ class AssociatedContent extends Content $this->args->get('id')->setValue( implode(',', array_intersect($receivedIdList, $associatedContentIdList)) ); } - return parent::exec($pagination); - } + $loopResult = parent::exec($pagination); + foreach($loopResult as $loopResultRow) { + + $relatedContentId = $loopResultRow->get('ID'); + + $loopResultRow + ->set("ID" , $contentId[$relatedContentId]) + ->set("POSITION", $contentPosition[$relatedContentId]) + ; + } + + return $loopResult; + } } diff --git a/core/lib/Thelia/Core/Template/Loop/Attribute.php b/core/lib/Thelia/Core/Template/Loop/Attribute.php index e2ea7cf0f..9a9aa6ff3 100755 --- a/core/lib/Thelia/Core/Template/Loop/Attribute.php +++ b/core/lib/Thelia/Core/Template/Loop/Attribute.php @@ -41,7 +41,8 @@ use Thelia\Type\BooleanOrBothType; use Thelia\Model\ProductQuery; use Thelia\Model\TemplateQuery; use Thelia\Model\AttributeTemplateQuery; - +use Thelia\Core\Translation\Translator; +use Thelia\Model\Map\AttributeTemplateTableMap; /** * * Attribute loop @@ -106,33 +107,50 @@ class Attribute extends BaseI18nLoop $product = $this->getProduct(); $template = $this->getTemplate(); - - if (null !== $product) { - // Find the template assigned to the product. - $productObj = ProductQuery::create()->findPk($product); - - // Ignore if the product cannot be found. - if ($productObj !== null) - $template = $productObj->getTemplate(); - } - - // If we have to filter by template, find all attributes assigned to this template, and filter by found IDs - if (null !== $template) { - $search->filterById( - AttributeTemplateQuery::create()->filterByTemplateId($template)->select('attribute_id')->find(), - Criteria::IN - ); - } - $exclude_template = $this->getExcludeTemplate(); - // If we have to filter by template, find all attributes assigned to this template, and filter by found IDs - if (null !== $exclude_template) { - // Exclure tous les attribut qui sont attachés aux templates indiqués - $search->filterById( - AttributeTemplateQuery::create()->filterByTemplateId($exclude_template)->select('attribute_id')->find(), - Criteria::NOT_IN - ); + $use_attribute_pos = true; + + if (null !== $product) { + // Find all template assigned to the products. + $products = ProductQuery::create()->findById($product); + + // Ignore if the product cannot be found. + if ($products !== null) { + + // Create template array + if ($template == null) $template = array(); + + foreach($products as $product) { + $tpl_id = $product->getTemplateId(); + + if (! is_null($tpl_id)) $template[] = $tpl_id; + } + } + } + + if (! empty($template)) { + + // Join with feature_template table to get position + $search + ->withColumn(AttributeTemplateTableMap::POSITION, 'position') + ->filterByTemplate(TemplateQuery::create()->findById($template), Criteria::IN) + ; + + $use_attribute_pos = false; + } + else if (null !== $exclude_template) { + + // Join with attribute_template table to get position + $exclude_attributes = AttributeTemplateQuery::create()->filterByTemplateId($exclude_template)->select('attribute_id')->find(); + + $search + ->joinAttributeTemplate(null, Criteria::LEFT_JOIN) + ->withColumn(AttributeTemplateTableMap::POSITION, 'position') + ->filterById($exclude_attributes, Criteria::NOT_IN) + ; + + $use_attribute_pos = false; } $orders = $this->getOrder(); @@ -152,10 +170,16 @@ class Attribute extends BaseI18nLoop $search->addDescendingOrderByColumn('i18n_TITLE'); break; case "manual": - $search->orderByPosition(Criteria::ASC); + if ($use_attribute_pos) + $search->orderByPosition(Criteria::ASC); + else + $search->addAscendingOrderByColumn(AttributeTemplateTableMap::POSITION); break; case "manual_reverse": - $search->orderByPosition(Criteria::DESC); + if ($use_attribute_pos) + $search->orderByPosition(Criteria::DESC); + else + $search->addDescendingOrderByColumn(AttributeTemplateTableMap::POSITION); break; } } @@ -174,7 +198,8 @@ class Attribute extends BaseI18nLoop ->set("CHAPO", $attribute->getVirtualColumn('i18n_CHAPO')) ->set("DESCRIPTION", $attribute->getVirtualColumn('i18n_DESCRIPTION')) ->set("POSTSCRIPTUM", $attribute->getVirtualColumn('i18n_POSTSCRIPTUM')) - ->set("POSITION", $attribute->getPosition()); + ->set("POSITION", $use_attribute_pos ? $attribute->getPosition() : $attribute->getVirtualColumn('position')) + ; $loopResult->addRow($loopResultRow); } diff --git a/core/lib/Thelia/Core/Template/Loop/Category.php b/core/lib/Thelia/Core/Template/Loop/Category.php index 7a0bac76d..9c9ddab7d 100755 --- a/core/lib/Thelia/Core/Template/Loop/Category.php +++ b/core/lib/Thelia/Core/Template/Loop/Category.php @@ -35,6 +35,7 @@ use Thelia\Model\CategoryQuery; use Thelia\Type\TypeCollection; use Thelia\Type; use Thelia\Type\BooleanOrBothType; +use Thelia\Model\ProductQuery; /** * @@ -73,6 +74,8 @@ class Category extends BaseI18nLoop return new ArgumentCollection( Argument::createIntListTypeArgument('id'), Argument::createIntTypeArgument('parent'), + Argument::createIntTypeArgument('product'), + Argument::createIntTypeArgument('exclude_product'), Argument::createBooleanTypeArgument('current'), Argument::createBooleanTypeArgument('not_empty', 0), Argument::createBooleanOrBothTypeArgument('visible', 1), @@ -128,6 +131,22 @@ class Category extends BaseI18nLoop if ($this->getVisible() != BooleanOrBothType::ANY) $search->filterByVisible($this->getVisible() ? 1 : 0); + $product = $this->getProduct(); + + if ($product != null) { + $obj = ProductQuery::create()->findPk($product); + + if ($obj != null) $search->filterByProduct($obj, Criteria::IN); + } + + $exclude_product = $this->getExclude_product(); + + if ($exclude_product != null) { + $obj = ProductQuery::create()->findPk($exclude_product); + + if ($obj != null) $search->filterByProduct($obj, Criteria::NOT_IN); + } + $orders = $this->getOrder(); foreach ($orders as $order) { diff --git a/core/lib/Thelia/Core/Template/Loop/Document.php b/core/lib/Thelia/Core/Template/Loop/Document.php index 0e7e979c9..d60aaf7b2 100644 --- a/core/lib/Thelia/Core/Template/Loop/Document.php +++ b/core/lib/Thelia/Core/Template/Loop/Document.php @@ -251,9 +251,9 @@ class Document extends BaseI18nLoop $loopResultRow ->set("ID" , $result->getId()) - ->set("LOCALE" ,$locale) + ->set("LOCALE" , $locale) ->set("DOCUMENT_URL" , $event->getFileUrl()) - ->set("DOCUMENT_PATH" , $event->getCacheFilepath()) + ->set("DOCUMENT_PATH" , $event->getDocumentPath()) ->set("ORIGINAL_DOCUMENT_PATH", $source_filepath) ->set("TITLE" , $result->getVirtualColumn('i18n_TITLE')) ->set("CHAPO" , $result->getVirtualColumn('i18n_CHAPO')) diff --git a/core/lib/Thelia/Core/Template/Loop/Feature.php b/core/lib/Thelia/Core/Template/Loop/Feature.php index 380333e38..f542b2e8d 100755 --- a/core/lib/Thelia/Core/Template/Loop/Feature.php +++ b/core/lib/Thelia/Core/Template/Loop/Feature.php @@ -41,6 +41,8 @@ use Thelia\Type\TypeCollection; use Thelia\Type; use Thelia\Type\BooleanOrBothType; use Thelia\Model\FeatureTemplateQuery; +use Thelia\Model\TemplateQuery; +use Thelia\Model\Map\FeatureTemplateTableMap; /** * @@ -70,7 +72,7 @@ class Feature extends BaseI18nLoop new Argument( 'order', new TypeCollection( - new Type\EnumListType(array('alpha', 'alpha-reverse', 'manual', 'manual_reverse')) + new Type\EnumListType(array('id', 'id_reverse', 'alpha', 'alpha-reverse', 'manual', 'manual_reverse')) ), 'manual' ), @@ -108,39 +110,55 @@ class Feature extends BaseI18nLoop $product = $this->getProduct(); $template = $this->getTemplate(); - - if (null !== $product) { - // Find the template assigned to the product. - $productObj = ProductQuery::create()->findPk($product); - - // Ignore if the product cannot be found. - if ($productObj !== null) - $template = $productObj->getTemplate(); - } - - // If we have to filter by template, find all features assigned to this template, and filter by found IDs - if (null !== $template) { - $search->filterById( - FeatureTemplateQuery::create()->filterByTemplateId($template)->select('feature_id')->find(), - Criteria::IN - ); - } - $exclude_template = $this->getExcludeTemplate(); - // If we have to filter by template, find all features assigned to this template, and filter by found IDs + $use_feature_pos = true; + + if (null !== $product) { + // Find all template assigned to the products. + $products = ProductQuery::create()->findById($product); + + // Ignore if the product cannot be found. + if ($products !== null) { + + // Create template array + if ($template == null) $template = array(); + + foreach($products as $product) { + $tpl_id = $product->getTemplateId(); + + if (! is_null($tpl_id)) $template[] = $tpl_id; + } + } + } + + if (! empty($template)) { + + // Join with feature_template table to get position + $search + ->withColumn(FeatureTemplateTableMap::POSITION, 'position') + ->filterByTemplate(TemplateQuery::create()->findById($template), Criteria::IN) + ; + + $use_feature_pos = false; + } + if (null !== $exclude_template) { - // Exclure tous les attribut qui sont attachés aux templates indiqués - $search->filterById( - FeatureTemplateQuery::create()->filterByTemplateId($exclude_template)->select('feature_id')->find(), - Criteria::NOT_IN - ); + $exclude_features = FeatureTemplateQuery::create()->filterByTemplateId($exclude_template)->select('feature_id')->find(); + + $search + ->joinFeatureTemplate(null, Criteria::LEFT_JOIN) + ->withColumn(FeatureTemplateTableMap::POSITION, 'position') + ->filterById($exclude_features, Criteria::NOT_IN) + ; + + $use_feature_pos = false; } $title = $this->getTitle(); if (null !== $title) { - //find all feture that match exactly this title and find with all locales. + //find all feature that match exactly this title and find with all locales. $features = FeatureI18nQuery::create() ->filterByTitle($title, Criteria::LIKE) ->select('id') @@ -158,6 +176,12 @@ class Feature extends BaseI18nLoop foreach ($orders as $order) { switch ($order) { + case "id": + $search->orderById(Criteria::ASC); + break; + case "id_reverse": + $search->orderById(Criteria::DESC); + break; case "alpha": $search->addAscendingOrderByColumn('i18n_TITLE'); break; @@ -165,14 +189,22 @@ class Feature extends BaseI18nLoop $search->addDescendingOrderByColumn('i18n_TITLE'); break; case "manual": - $search->orderByPosition(Criteria::ASC); + if ($use_feature_pos) + $search->orderByPosition(Criteria::ASC); + else + $search->addAscendingOrderByColumn(FeatureTemplateTableMap::POSITION); break; case "manual_reverse": - $search->orderByPosition(Criteria::DESC); + if ($use_feature_pos) + $search->orderByPosition(Criteria::DESC); + else + $search->addDescendingOrderByColumn(FeatureTemplateTableMap::POSITION); break; } + } + /* perform search */ $features = $this->search($search, $pagination); @@ -187,7 +219,8 @@ class Feature extends BaseI18nLoop ->set("CHAPO", $feature->getVirtualColumn('i18n_CHAPO')) ->set("DESCRIPTION", $feature->getVirtualColumn('i18n_DESCRIPTION')) ->set("POSTSCRIPTUM", $feature->getVirtualColumn('i18n_POSTSCRIPTUM')) - ->set("POSITION", $feature->getPosition()); + ->set("POSITION", $use_feature_pos ? $feature->getPosition() : $feature->getVirtualColumn('position')) + ; $loopResult->addRow($loopResultRow); } diff --git a/core/lib/Thelia/Core/Template/Loop/FeatureValue.php b/core/lib/Thelia/Core/Template/Loop/FeatureValue.php index 30a8dcf27..8ae8b0f4f 100755 --- a/core/lib/Thelia/Core/Template/Loop/FeatureValue.php +++ b/core/lib/Thelia/Core/Template/Loop/FeatureValue.php @@ -59,7 +59,7 @@ class FeatureValue extends BaseI18nLoop Argument::createIntTypeArgument('product', null, true), Argument::createIntListTypeArgument('feature_availability'), Argument::createBooleanTypeArgument('exclude_feature_availability', 0), - Argument::createBooleanTypeArgument('exclude_personal_values', 0), + Argument::createBooleanTypeArgument('exclude_free_text', 0), new Argument( 'order', new TypeCollection( @@ -79,7 +79,7 @@ class FeatureValue extends BaseI18nLoop { $search = FeatureProductQuery::create(); - /* manage featureAv translations */ + // manage featureAv translations $locale = $this->configureI18nProcessing( $search, array('TITLE', 'CHAPO', 'DESCRIPTION', 'POSTSCRIPTUM'), @@ -103,13 +103,9 @@ class FeatureValue extends BaseI18nLoop } $excludeFeatureAvailability = $this->getExclude_feature_availability(); - if ($excludeFeatureAvailability == true) { - $search->filterByFeatureAvId(null, Criteria::NULL); - } - $excludeDefaultValues = $this->getExclude_personal_values(); - if ($excludeDefaultValues == true) { - $search->filterByByDefault(null, Criteria::NULL); + if ($excludeFeatureAvailability == true) { + $search->filterByFeatureAvId(null, Criteria::ISNULL); } $orders = $this->getOrder(); @@ -136,17 +132,26 @@ class FeatureValue extends BaseI18nLoop $loopResult = new LoopResult($featureValues); foreach ($featureValues as $featureValue) { + $loopResultRow = new LoopResultRow($loopResult, $featureValue, $this->versionable, $this->timestampable, $this->countable); - $loopResultRow->set("ID", $featureValue->getId()); $loopResultRow - ->set("LOCALE",$locale) - ->set("PERSONAL_VALUE", $featureValue->getByDefault()) - ->set("TITLE",$featureValue->getVirtualColumn(FeatureAvTableMap::TABLE_NAME . '_i18n_TITLE')) - ->set("CHAPO", $featureValue->getVirtualColumn(FeatureAvTableMap::TABLE_NAME . '_i18n_CHAPO')) - ->set("DESCRIPTION", $featureValue->getVirtualColumn(FeatureAvTableMap::TABLE_NAME . '_i18n_DESCRIPTION')) - ->set("POSTSCRIPTUM", $featureValue->getVirtualColumn(FeatureAvTableMap::TABLE_NAME . '_i18n_POSTSCRIPTUM')) - ->set("POSITION", $featureValue->getPosition()); + ->set("ID" , $featureValue->getId()) + ->set("PRODUCT" , $featureValue->getProductId()) + ->set("FEATURE_AV_ID" , $featureValue->getFeatureAvId()) + ->set("FREE_TEXT_VALUE" , $featureValue->getFreeTextValue()) + + ->set("IS_FREE_TEXT" , is_null($featureValue->getFeatureAvId()) ? 1 : 0) + ->set("IS_FEATURE_AV" , is_null($featureValue->getFeatureAvId()) ? 0 : 1) + + ->set("LOCALE" , $locale) + ->set("TITLE" , $featureValue->getVirtualColumn(FeatureAvTableMap::TABLE_NAME . '_i18n_TITLE')) + ->set("CHAPO" , $featureValue->getVirtualColumn(FeatureAvTableMap::TABLE_NAME . '_i18n_CHAPO')) + ->set("DESCRIPTION" , $featureValue->getVirtualColumn(FeatureAvTableMap::TABLE_NAME . '_i18n_DESCRIPTION')) + ->set("POSTSCRIPTUM" , $featureValue->getVirtualColumn(FeatureAvTableMap::TABLE_NAME . '_i18n_POSTSCRIPTUM')) + + ->set("POSITION" , $featureValue->getPosition()) + ; $loopResult->addRow($loopResultRow); } diff --git a/core/lib/Thelia/Core/Template/Loop/Product.php b/core/lib/Thelia/Core/Template/Loop/Product.php index 9deade5cc..7c812d81f 100755 --- a/core/lib/Thelia/Core/Template/Loop/Product.php +++ b/core/lib/Thelia/Core/Template/Loop/Product.php @@ -650,6 +650,7 @@ class Product extends BaseI18nLoop ->set("IS_NEW" , $product->getVirtualColumn('main_product_is_new')) ->set("POSITION" , $product->getPosition()) ->set("VISIBLE" , $product->getVisible() ? "1" : "0") + ->set("TEMPLATE" , $product->getTemplateId()) ->set("HAS_PREVIOUS" , $previous != null ? 1 : 0) ->set("HAS_NEXT" , $next != null ? 1 : 0) ->set("PREVIOUS" , $previous != null ? $previous->getId() : -1) diff --git a/core/lib/Thelia/Core/Template/Loop/ProductSaleElements.php b/core/lib/Thelia/Core/Template/Loop/ProductSaleElements.php index 8fb044556..e25164456 100755 --- a/core/lib/Thelia/Core/Template/Loop/ProductSaleElements.php +++ b/core/lib/Thelia/Core/Template/Loop/ProductSaleElements.php @@ -170,17 +170,19 @@ class ProductSaleElements extends BaseLoop $taxedPromoPrice = null; } - $loopResultRow->set("ID", $PSEValue->getId()) - ->set("QUANTITY", $PSEValue->getQuantity()) - ->set("IS_PROMO", $PSEValue->getPromo() === 1 ? 1 : 0) - ->set("IS_NEW", $PSEValue->getNewness() === 1 ? 1 : 0) - ->set("WEIGHT", $PSEValue->getWeight()) - ->set("PRICE", $price) - ->set("PRICE_TAX", $taxedPrice - $price) - ->set("TAXED_PRICE", $taxedPrice) - ->set("PROMO_PRICE", $promoPrice) - ->set("PROMO_PRICE_TAX", $taxedPromoPrice - $promoPrice) - ->set("TAXED_PROMO_PRICE", $taxedPromoPrice); + $loopResultRow + ->set("ID" , $PSEValue->getId()) + ->set("QUANTITY" , $PSEValue->getQuantity()) + ->set("IS_PROMO" , $PSEValue->getPromo() === 1 ? 1 : 0) + ->set("IS_NEW" , $PSEValue->getNewness() === 1 ? 1 : 0) + ->set("IS_DEFAULT" , $PSEValue->getIsDefault() === 1 ? 1 : 0) + ->set("WEIGHT" , $PSEValue->getWeight()) + ->set("PRICE" , $price) + ->set("PRICE_TAX" , $taxedPrice - $price) + ->set("TAXED_PRICE" , $taxedPrice) + ->set("PROMO_PRICE" , $promoPrice) + ->set("PROMO_PRICE_TAX" , $taxedPromoPrice - $promoPrice) + ->set("TAXED_PROMO_PRICE" , $taxedPromoPrice); $loopResult->addRow($loopResultRow); } diff --git a/core/lib/Thelia/Core/Template/Smarty/Plugins/FlashMessage.php b/core/lib/Thelia/Core/Template/Smarty/Plugins/FlashMessage.php new file mode 100755 index 000000000..569aedb84 --- /dev/null +++ b/core/lib/Thelia/Core/Template/Smarty/Plugins/FlashMessage.php @@ -0,0 +1,103 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Core\Template\Smarty\Plugins; + +use Symfony\Component\Form\FormView; +use Thelia\Form\BaseForm; +use Thelia\Core\Template\Element\Exception\ElementNotFoundException; +use Symfony\Component\HttpFoundation\Request; +use Thelia\Core\Template\Smarty\SmartyPluginDescriptor; +use Thelia\Core\Template\Smarty\AbstractSmartyPlugin; +use Thelia\Core\Template\ParserContext; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Plugin for smarty defining blocks allowing to get flash message + * A flash message is a variable, array, object stored in session under the flashMessage key + * ex $SESSION['flashMessage']['myKey'] + * + * blocks : + * - {flashMessage key="myKey"} ... {/flashMessage} + * + * Class Form + * + * @package Thelia\Core\Template\Smarty\Plugins + * @author Guillaume MOREL + */ +class FlashMessage extends AbstractSmartyPlugin +{ + + /** @var Request Request service */ + protected $request; + + /** + * Constructor + * + * @param Request $request Request service + */ + public function __construct(Request $request) + { + $this->request = $request; + } + + /** + * Get FlashMessage + * And clean session from this key + * + * @param array $params Block parameters + * @param mixed $content Block content + * @param \Smarty_Internal_Template $template Template + * @param bool $repeat Control how many times + * the block is displayed + * + * @return mixed + */ + public function getFlashMessage($params, $content, \Smarty_Internal_Template $template, &$repeat) + { + if ($repeat) { + $key = $params['key']; + $flashBag = $this->request->getSession()->get('flashMessage'); + $template->assign('value', $flashBag[$key]); + + // Reset flash message (can be read once) + unset($flashBag[$key]); + $this->request->getSession()->set('flashMessage', $flashBag); + } else { + return $content; + } + } + + /** + * @return array an array of SmartyPluginDescriptor + */ + public function getPluginDescriptors() + { + return array( + new SmartyPluginDescriptor("block", "flashMessage", $this, "getFlashMessage") + ); + } + +} diff --git a/core/lib/Thelia/Core/Template/Smarty/Plugins/UrlGenerator.php b/core/lib/Thelia/Core/Template/Smarty/Plugins/UrlGenerator.php index f1249697a..c74d1d2b3 100755 --- a/core/lib/Thelia/Core/Template/Smarty/Plugins/UrlGenerator.php +++ b/core/lib/Thelia/Core/Template/Smarty/Plugins/UrlGenerator.php @@ -48,8 +48,9 @@ class UrlGenerator extends AbstractSmartyPlugin public function generateUrlFunction($params, &$smarty) { // the path to process - $path = $this->getParam($params, 'path', null); - $file = $this->getParam($params, 'file', null); + $path = $this->getParam($params, 'path', null); + $file = $this->getParam($params, 'file', null); // Do not invoke index.php in URL (get a static file in web space + $noamp = $this->getParam($params, 'noamp', null); // Do not change & in & if ($file !== null) { $path = $file; @@ -66,10 +67,12 @@ class UrlGenerator extends AbstractSmartyPlugin $url = URL::getInstance()->absoluteUrl( $path, - $this->getArgsFromParam($params, array('path', 'file', 'target')), + $this->getArgsFromParam($params, array('noamp', 'path', 'file', 'target')), $mode ); + if ($noamp == null) $url = str_replace('&', '&', $url); + if ($target != null) $url .= '#'.$target; return $url; diff --git a/core/lib/Thelia/Form/CategoryDocumentModification.php b/core/lib/Thelia/Form/CategoryDocumentModification.php new file mode 100644 index 000000000..47c637df0 --- /dev/null +++ b/core/lib/Thelia/Form/CategoryDocumentModification.php @@ -0,0 +1,54 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form; + + +use Thelia\Core\Translation\Translator; +use Thelia\Form\Image\DocumentModification; +use Thelia\Form\Image\ImageModification; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Form allowing to process an document collection + * + * @package Image + * @author Guillaume MOREL + * + */ +class CategoryDocumentModification extends DocumentModification +{ + + /** + * Get form name + * This name must be unique + * + * @return string + */ + public function getName() + { + return 'thelia_category_document_modification'; + } +} diff --git a/core/lib/Thelia/Form/CategoryImageModification.php b/core/lib/Thelia/Form/CategoryImageModification.php new file mode 100644 index 000000000..06674d9ef --- /dev/null +++ b/core/lib/Thelia/Form/CategoryImageModification.php @@ -0,0 +1,53 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form; + + +use Thelia\Core\Translation\Translator; +use Thelia\Form\Image\ImageModification; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Form allowing to process an image collection + * + * @package Image + * @author Guillaume MOREL + * + */ +class CategoryImageModification extends ImageModification +{ + + /** + * Get form name + * This name must be unique + * + * @return string + */ + public function getName() + { + return 'thelia_category_image_modification'; + } +} diff --git a/core/lib/Thelia/Form/ContentDocumentModification.php b/core/lib/Thelia/Form/ContentDocumentModification.php new file mode 100644 index 000000000..748eec0ec --- /dev/null +++ b/core/lib/Thelia/Form/ContentDocumentModification.php @@ -0,0 +1,54 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form; + + +use Thelia\Core\Translation\Translator; +use Thelia\Form\Image\DocumentModification; +use Thelia\Form\Image\ImageModification; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Form allowing to process a file + * + * @package File + * @author Guillaume MOREL + * + */ +class ContentDocumentModification extends DocumentModification +{ + + /** + * Get form name + * This name must be unique + * + * @return string + */ + public function getName() + { + return 'thelia_content_document_modification'; + } +} diff --git a/core/lib/Thelia/Form/ContentImageModification.php b/core/lib/Thelia/Form/ContentImageModification.php new file mode 100644 index 000000000..f40fc5497 --- /dev/null +++ b/core/lib/Thelia/Form/ContentImageModification.php @@ -0,0 +1,53 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form; + + +use Thelia\Core\Translation\Translator; +use Thelia\Form\Image\ImageModification; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Form allowing to process an image collection + * + * @package Image + * @author Guillaume MOREL + * + */ +class ContentImageModification extends ImageModification +{ + + /** + * Get form name + * This name must be unique + * + * @return string + */ + public function getName() + { + return 'thelia_content_image_modification'; + } +} diff --git a/core/lib/Thelia/Form/FolderDocumentModification.php b/core/lib/Thelia/Form/FolderDocumentModification.php new file mode 100644 index 000000000..bbf57ab9d --- /dev/null +++ b/core/lib/Thelia/Form/FolderDocumentModification.php @@ -0,0 +1,54 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form; + + +use Thelia\Core\Translation\Translator; +use Thelia\Form\Image\DocumentModification; +use Thelia\Form\Image\ImageModification; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Form allowing to process a file + * + * @package Image + * @author Guillaume MOREL + * + */ +class FolderDocumentModification extends DocumentModification +{ + + /** + * Get form name + * This name must be unique + * + * @return string + */ + public function getName() + { + return 'thelia_folder_document_modification'; + } +} diff --git a/core/lib/Thelia/Form/FolderImageModification.php b/core/lib/Thelia/Form/FolderImageModification.php new file mode 100644 index 000000000..a9305433b --- /dev/null +++ b/core/lib/Thelia/Form/FolderImageModification.php @@ -0,0 +1,53 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form; + + +use Thelia\Core\Translation\Translator; +use Thelia\Form\Image\ImageModification; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Form allowing to process an image collection + * + * @package Image + * @author Guillaume MOREL + * + */ +class FolderImageModification extends ImageModification +{ + + /** + * Get form name + * This name must be unique + * + * @return string + */ + public function getName() + { + return 'thelia_folder_image_modification'; + } +} diff --git a/core/lib/Thelia/Form/Image/DocumentModification.php b/core/lib/Thelia/Form/Image/DocumentModification.php new file mode 100644 index 000000000..39ba559be --- /dev/null +++ b/core/lib/Thelia/Form/Image/DocumentModification.php @@ -0,0 +1,138 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form\Image; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Validator\Constraints\Image; +use Symfony\Component\Validator\Constraints\NotBlank; +use Thelia\Core\Translation\Translator; +use Thelia\Form\BaseForm; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Form allowing to process a file + * @todo refactor make all document using propel inheritance and factorise image behaviour into one single clean action + * + * @package File + * @author Guillaume MOREL + * + */ +abstract class DocumentModification extends BaseForm +{ + /** + * + * in this function you add all the fields you need for your Form. + * Form this you have to call add method on $this->form attribute : + * + * $this->form->add('name', 'text') + * ->add('email', 'email', array( + * 'attr' => array( + * 'class' => 'field' + * ), + * 'label' => 'email', + * 'constraints' => array( + * new NotBlank() + * ) + * ) + * ) + * ->add('age', 'integer'); + * + * @return null + */ + protected function buildForm() + { + $this->formBuilder->add( + 'file', + 'file', + array( + 'constraints' => array(), + 'label' => Translator::getInstance()->trans('Replace current document by this file'), + 'label_attr' => array( + 'for' => 'file' + ) + ) + ); + + $this->formBuilder + ->add( + 'title', + 'text', + array( + 'constraints' => array( + new NotBlank() + ), + 'label' => Translator::getInstance()->trans('Title'), + 'label_attr' => array( + 'for' => 'title' + ) + ) + ) + ->add( + 'description', + 'text', + array( + 'constraints' => array(), + 'label' => Translator::getInstance()->trans('Description'), + 'label_attr' => array( + 'for' => 'description' + ) + ) + ) + ->add( + 'chapo', + 'text', + array( + 'constraints' => array(), + 'label' => Translator::getInstance()->trans('Chapo'), + 'label_attr' => array( + 'for' => 'chapo' + ) + ) + ) + ->add( + 'postscriptum', + 'text', + array( + 'constraints' => array(), + 'label' => Translator::getInstance()->trans('Post Scriptum'), + 'label_attr' => array( + 'for' => 'postscriptum' + ) + ) + ) + ->add( + 'postscriptum', + 'text', + array( + 'constraints' => array(), + 'label' => Translator::getInstance()->trans('Post Scriptum'), + 'label_attr' => array( + 'for' => 'postscriptum' + ) + ) + ); + } +} diff --git a/core/lib/Thelia/Form/Image/ImageModification.php b/core/lib/Thelia/Form/Image/ImageModification.php new file mode 100644 index 000000000..c4d26378b --- /dev/null +++ b/core/lib/Thelia/Form/Image/ImageModification.php @@ -0,0 +1,146 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form\Image; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Validator\Constraints\Image; +use Symfony\Component\Validator\Constraints\NotBlank; +use Thelia\Core\Translation\Translator; +use Thelia\Form\BaseForm; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Form allowing to process an image + * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action + * + * @package Image + * @author Guillaume MOREL + * + */ +abstract class ImageModification extends BaseForm +{ + + /** + * + * in this function you add all the fields you need for your Form. + * Form this you have to call add method on $this->form attribute : + * + * $this->form->add('name', 'text') + * ->add('email', 'email', array( + * 'attr' => array( + * 'class' => 'field' + * ), + * 'label' => 'email', + * 'constraints' => array( + * new NotBlank() + * ) + * ) + * ) + * ->add('age', 'integer'); + * + * @return null + */ + protected function buildForm() + { + $this->formBuilder->add( + 'file', + 'file', + array( + 'constraints' => array( + new Image( + array( +// 'minWidth' => 200, +// 'minHeight' => 200 + ) + ) + ), + 'label' => Translator::getInstance()->trans('Replace current image by this file'), + 'label_attr' => array( + 'for' => 'file' + ) + ) + ); + + $this->formBuilder + ->add( + 'title', + 'text', + array( + 'constraints' => array( + new NotBlank() + ), + 'label' => Translator::getInstance()->trans('Title'), + 'label_attr' => array( + 'for' => 'title' + ) + ) + ) + ->add( + 'description', + 'text', + array( + 'constraints' => array(), + 'label' => Translator::getInstance()->trans('Description'), + 'label_attr' => array( + 'for' => 'description' + ) + ) + ) + ->add( + 'chapo', + 'text', + array( + 'constraints' => array(), + 'label' => Translator::getInstance()->trans('Chapo'), + 'label_attr' => array( + 'for' => 'chapo' + ) + ) + ) + ->add( + 'postscriptum', + 'text', + array( + 'constraints' => array(), + 'label' => Translator::getInstance()->trans('Post Scriptum'), + 'label_attr' => array( + 'for' => 'postscriptum' + ) + ) + ) + ->add( + 'postscriptum', + 'text', + array( + 'constraints' => array(), + 'label' => Translator::getInstance()->trans('Post Scriptum'), + 'label_attr' => array( + 'for' => 'postscriptum' + ) + ) + ); + } +} diff --git a/core/lib/Thelia/Form/ProductCreationForm.php b/core/lib/Thelia/Form/ProductCreationForm.php index 9329ca2ec..c1b8bd6b4 100644 --- a/core/lib/Thelia/Form/ProductCreationForm.php +++ b/core/lib/Thelia/Form/ProductCreationForm.php @@ -47,25 +47,48 @@ class ProductCreationForm extends BaseForm "label_attr" => array("for" => "ref") )) ->add("title", "text", array( - "constraints" => array( - new NotBlank() - ), + "constraints" => array(new NotBlank()), "label" => "Product title *", "label_attr" => array("for" => "title") )) ->add("default_category", "integer", array( "constraints" => array(new NotBlank()), - "label" => Translator::getInstance()->trans("Default product category."), + "label" => Translator::getInstance()->trans("Default product category *"), "label_attr" => array("for" => "default_category_field") )) ->add("locale", "text", array( "constraints" => array(new NotBlank()) )) ->add("visible", "integer", array( - "label" => Translator::getInstance()->trans("This product is online."), + "label" => Translator::getInstance()->trans("This product is online"), "label_attr" => array("for" => "visible_field") )) - ; + ; + + if (! $change_mode) { + $this->formBuilder + ->add("price", "number", array( + "constraints" => array(new NotBlank()), + "label" => Translator::getInstance()->trans("Product base price excluding taxes *"), + "label_attr" => array("for" => "price_field") + )) + ->add("currency", "integer", array( + "constraints" => array(new NotBlank()), + "label" => Translator::getInstance()->trans("Price currency *"), + "label_attr" => array("for" => "currency_field") + )) + ->add("tax_rule", "integer", array( + "constraints" => array(new NotBlank()), + "label" => Translator::getInstance()->trans("Tax rule for this product *"), + "label_attr" => array("for" => "tax_rule_field") + )) + ->add("weight", "number", array( + "constraints" => array(new NotBlank()), + "label" => Translator::getInstance()->trans("Weight *"), + "label_attr" => array("for" => "weight_field") + )) + ; + } } public function checkDuplicateRef($value, ExecutionContextInterface $context) diff --git a/core/lib/Thelia/Form/ProductDetailsModificationForm.php b/core/lib/Thelia/Form/ProductDetailsModificationForm.php new file mode 100644 index 000000000..7ded6ff69 --- /dev/null +++ b/core/lib/Thelia/Form/ProductDetailsModificationForm.php @@ -0,0 +1,90 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form; + +use Symfony\Component\Validator\Constraints\GreaterThan; +use Thelia\Core\Translation\Translator; +use Symfony\Component\Validator\Constraints\NotBlank; + +class ProductDetailsModificationForm extends BaseForm +{ + use StandardDescriptionFieldsTrait; + + protected function buildForm() + { + $this->formBuilder + ->add("id", "integer", array( + "label" => Translator::getInstance()->trans("Prodcut ID *"), + "label_attr" => array("for" => "product_id_field"), + "constraints" => array(new GreaterThan(array('value' => 0))) + )) + ->add("price", "number", array( + "constraints" => array(new NotBlank()), + "label" => Translator::getInstance()->trans("Product base price excluding taxes *"), + "label_attr" => array("for" => "price_field") + )) + ->add("price_with_tax", "number", array( + "label" => Translator::getInstance()->trans("Product base price including taxes *"), + "label_attr" => array("for" => "price_with_tax_field") + )) + ->add("currency", "integer", array( + "constraints" => array(new NotBlank()), + "label" => Translator::getInstance()->trans("Price currency *"), + "label_attr" => array("for" => "currency_field") + )) + ->add("tax_rule", "integer", array( + "constraints" => array(new NotBlank()), + "label" => Translator::getInstance()->trans("Tax rule for this product *"), + "label_attr" => array("for" => "tax_rule_field") + )) + ->add("weight", "number", array( + "constraints" => array(new NotBlank()), + "label" => Translator::getInstance()->trans("Weight *"), + "label_attr" => array("for" => "weight_field") + )) + ->add("quantity", "number", array( + "constraints" => array(new NotBlank()), + "label" => Translator::getInstance()->trans("Current quantity *"), + "label_attr" => array("for" => "quantity_field") + )) + ->add("sale_price", "number", array( + "label" => Translator::getInstance()->trans("Sale price *"), + "label_attr" => array("for" => "price_with_tax_field") + )) + ->add("onsale", "integer", array( + "label" => Translator::getInstance()->trans("This product is on sale"), + "label_attr" => array("for" => "onsale_field") + )) + ->add("isnew", "integer", array( + "label" => Translator::getInstance()->trans("Advertise this product as new"), + "label_attr" => array("for" => "isnew_field") + )) + + ; + } + + public function getName() + { + return "thelia_product_details_modification"; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Form/ProductDocumentModification.php b/core/lib/Thelia/Form/ProductDocumentModification.php new file mode 100644 index 000000000..1a9bc5467 --- /dev/null +++ b/core/lib/Thelia/Form/ProductDocumentModification.php @@ -0,0 +1,54 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form; + + +use Thelia\Core\Translation\Translator; +use Thelia\Form\Image\DocumentModification; +use Thelia\Form\Image\ImageModification; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Form allowing to process a file + * + * @package File + * @author Guillaume MOREL + * + */ +class ProductDocumentModification extends DocumentModification +{ + + /** + * Get form name + * This name must be unique + * + * @return string + */ + public function getName() + { + return 'thelia_product_document_modification'; + } +} diff --git a/core/lib/Thelia/Form/ProductImageModification.php b/core/lib/Thelia/Form/ProductImageModification.php new file mode 100644 index 000000000..2d35e176f --- /dev/null +++ b/core/lib/Thelia/Form/ProductImageModification.php @@ -0,0 +1,53 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form; + + +use Thelia\Core\Translation\Translator; +use Thelia\Form\Image\ImageModification; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Form allowing to process an image collection + * + * @package Image + * @author Guillaume MOREL + * + */ +class ProductImageModification extends ImageModification +{ + + /** + * Get form name + * This name must be unique + * + * @return string + */ + public function getName() + { + return 'thelia_product_image_modification'; + } +} diff --git a/core/lib/Thelia/Model/AttributeTemplate.php b/core/lib/Thelia/Model/AttributeTemplate.php index 57b4c3745..bb5a0c02c 100644 --- a/core/lib/Thelia/Model/AttributeTemplate.php +++ b/core/lib/Thelia/Model/AttributeTemplate.php @@ -3,8 +3,29 @@ namespace Thelia\Model; use Thelia\Model\Base\AttributeTemplate as BaseAttributeTemplate; +use Propel\Runtime\Connection\ConnectionInterface; - class AttributeTemplate extends BaseAttributeTemplate +class AttributeTemplate extends BaseAttributeTemplate { + use \Thelia\Model\Tools\ModelEventDispatcherTrait; + use \Thelia\Model\Tools\PositionManagementTrait; + + /** + * Calculate next position relative to our template + */ + protected function addCriteriaToPositionQuery($query) + { + $query->filterByTemplateId($this->getTemplateId()); + } + + /** + * {@inheritDoc} + */ + public function preInsert(ConnectionInterface $con = null) + { + $this->setPosition($this->getNextPosition()); + + return true; + } } diff --git a/core/lib/Thelia/Model/Base/AttributeTemplate.php b/core/lib/Thelia/Model/Base/AttributeTemplate.php index 85d432a62..0014e1a8d 100644 --- a/core/lib/Thelia/Model/Base/AttributeTemplate.php +++ b/core/lib/Thelia/Model/Base/AttributeTemplate.php @@ -82,6 +82,12 @@ abstract class AttributeTemplate implements ActiveRecordInterface */ protected $position; + /** + * The value for the attribute_templatecol field. + * @var string + */ + protected $attribute_templatecol; + /** * The value for the created_at field. * @var string @@ -410,6 +416,17 @@ abstract class AttributeTemplate implements ActiveRecordInterface return $this->position; } + /** + * Get the [attribute_templatecol] column value. + * + * @return string + */ + public function getAttributeTemplatecol() + { + + return $this->attribute_templatecol; + } + /** * Get the [optionally formatted] temporal [created_at] column value. * @@ -542,6 +559,27 @@ abstract class AttributeTemplate implements ActiveRecordInterface return $this; } // setPosition() + /** + * Set the value of [attribute_templatecol] column. + * + * @param string $v new value + * @return \Thelia\Model\AttributeTemplate The current object (for fluent API support) + */ + public function setAttributeTemplatecol($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->attribute_templatecol !== $v) { + $this->attribute_templatecol = $v; + $this->modifiedColumns[] = AttributeTemplateTableMap::ATTRIBUTE_TEMPLATECOL; + } + + + return $this; + } // setAttributeTemplatecol() + /** * Sets the value of [created_at] column to a normalized version of the date/time value specified. * @@ -633,13 +671,16 @@ abstract class AttributeTemplate implements ActiveRecordInterface $col = $row[TableMap::TYPE_NUM == $indexType ? 3 + $startcol : AttributeTemplateTableMap::translateFieldName('Position', TableMap::TYPE_PHPNAME, $indexType)]; $this->position = (null !== $col) ? (int) $col : null; - $col = $row[TableMap::TYPE_NUM == $indexType ? 4 + $startcol : AttributeTemplateTableMap::translateFieldName('CreatedAt', TableMap::TYPE_PHPNAME, $indexType)]; + $col = $row[TableMap::TYPE_NUM == $indexType ? 4 + $startcol : AttributeTemplateTableMap::translateFieldName('AttributeTemplatecol', TableMap::TYPE_PHPNAME, $indexType)]; + $this->attribute_templatecol = (null !== $col) ? (string) $col : null; + + $col = $row[TableMap::TYPE_NUM == $indexType ? 5 + $startcol : AttributeTemplateTableMap::translateFieldName('CreatedAt', TableMap::TYPE_PHPNAME, $indexType)]; if ($col === '0000-00-00 00:00:00') { $col = null; } $this->created_at = (null !== $col) ? PropelDateTime::newInstance($col, null, '\DateTime') : null; - $col = $row[TableMap::TYPE_NUM == $indexType ? 5 + $startcol : AttributeTemplateTableMap::translateFieldName('UpdatedAt', TableMap::TYPE_PHPNAME, $indexType)]; + $col = $row[TableMap::TYPE_NUM == $indexType ? 6 + $startcol : AttributeTemplateTableMap::translateFieldName('UpdatedAt', TableMap::TYPE_PHPNAME, $indexType)]; if ($col === '0000-00-00 00:00:00') { $col = null; } @@ -652,7 +693,7 @@ abstract class AttributeTemplate implements ActiveRecordInterface $this->ensureConsistency(); } - return $startcol + 6; // 6 = AttributeTemplateTableMap::NUM_HYDRATE_COLUMNS. + return $startcol + 7; // 7 = AttributeTemplateTableMap::NUM_HYDRATE_COLUMNS. } catch (Exception $e) { throw new PropelException("Error populating \Thelia\Model\AttributeTemplate object", 0, $e); @@ -911,6 +952,9 @@ abstract class AttributeTemplate implements ActiveRecordInterface if ($this->isColumnModified(AttributeTemplateTableMap::POSITION)) { $modifiedColumns[':p' . $index++] = 'POSITION'; } + if ($this->isColumnModified(AttributeTemplateTableMap::ATTRIBUTE_TEMPLATECOL)) { + $modifiedColumns[':p' . $index++] = 'ATTRIBUTE_TEMPLATECOL'; + } if ($this->isColumnModified(AttributeTemplateTableMap::CREATED_AT)) { $modifiedColumns[':p' . $index++] = 'CREATED_AT'; } @@ -940,6 +984,9 @@ abstract class AttributeTemplate implements ActiveRecordInterface case 'POSITION': $stmt->bindValue($identifier, $this->position, PDO::PARAM_INT); break; + case 'ATTRIBUTE_TEMPLATECOL': + $stmt->bindValue($identifier, $this->attribute_templatecol, PDO::PARAM_STR); + break; case 'CREATED_AT': $stmt->bindValue($identifier, $this->created_at ? $this->created_at->format("Y-m-d H:i:s") : null, PDO::PARAM_STR); break; @@ -1021,9 +1068,12 @@ abstract class AttributeTemplate implements ActiveRecordInterface return $this->getPosition(); break; case 4: - return $this->getCreatedAt(); + return $this->getAttributeTemplatecol(); break; case 5: + return $this->getCreatedAt(); + break; + case 6: return $this->getUpdatedAt(); break; default: @@ -1059,8 +1109,9 @@ abstract class AttributeTemplate implements ActiveRecordInterface $keys[1] => $this->getAttributeId(), $keys[2] => $this->getTemplateId(), $keys[3] => $this->getPosition(), - $keys[4] => $this->getCreatedAt(), - $keys[5] => $this->getUpdatedAt(), + $keys[4] => $this->getAttributeTemplatecol(), + $keys[5] => $this->getCreatedAt(), + $keys[6] => $this->getUpdatedAt(), ); $virtualColumns = $this->virtualColumns; foreach($virtualColumns as $key => $virtualColumn) @@ -1122,9 +1173,12 @@ abstract class AttributeTemplate implements ActiveRecordInterface $this->setPosition($value); break; case 4: - $this->setCreatedAt($value); + $this->setAttributeTemplatecol($value); break; case 5: + $this->setCreatedAt($value); + break; + case 6: $this->setUpdatedAt($value); break; } // switch() @@ -1155,8 +1209,9 @@ abstract class AttributeTemplate implements ActiveRecordInterface if (array_key_exists($keys[1], $arr)) $this->setAttributeId($arr[$keys[1]]); if (array_key_exists($keys[2], $arr)) $this->setTemplateId($arr[$keys[2]]); if (array_key_exists($keys[3], $arr)) $this->setPosition($arr[$keys[3]]); - if (array_key_exists($keys[4], $arr)) $this->setCreatedAt($arr[$keys[4]]); - if (array_key_exists($keys[5], $arr)) $this->setUpdatedAt($arr[$keys[5]]); + if (array_key_exists($keys[4], $arr)) $this->setAttributeTemplatecol($arr[$keys[4]]); + if (array_key_exists($keys[5], $arr)) $this->setCreatedAt($arr[$keys[5]]); + if (array_key_exists($keys[6], $arr)) $this->setUpdatedAt($arr[$keys[6]]); } /** @@ -1172,6 +1227,7 @@ abstract class AttributeTemplate implements ActiveRecordInterface if ($this->isColumnModified(AttributeTemplateTableMap::ATTRIBUTE_ID)) $criteria->add(AttributeTemplateTableMap::ATTRIBUTE_ID, $this->attribute_id); if ($this->isColumnModified(AttributeTemplateTableMap::TEMPLATE_ID)) $criteria->add(AttributeTemplateTableMap::TEMPLATE_ID, $this->template_id); if ($this->isColumnModified(AttributeTemplateTableMap::POSITION)) $criteria->add(AttributeTemplateTableMap::POSITION, $this->position); + if ($this->isColumnModified(AttributeTemplateTableMap::ATTRIBUTE_TEMPLATECOL)) $criteria->add(AttributeTemplateTableMap::ATTRIBUTE_TEMPLATECOL, $this->attribute_templatecol); if ($this->isColumnModified(AttributeTemplateTableMap::CREATED_AT)) $criteria->add(AttributeTemplateTableMap::CREATED_AT, $this->created_at); if ($this->isColumnModified(AttributeTemplateTableMap::UPDATED_AT)) $criteria->add(AttributeTemplateTableMap::UPDATED_AT, $this->updated_at); @@ -1240,6 +1296,7 @@ abstract class AttributeTemplate implements ActiveRecordInterface $copyObj->setAttributeId($this->getAttributeId()); $copyObj->setTemplateId($this->getTemplateId()); $copyObj->setPosition($this->getPosition()); + $copyObj->setAttributeTemplatecol($this->getAttributeTemplatecol()); $copyObj->setCreatedAt($this->getCreatedAt()); $copyObj->setUpdatedAt($this->getUpdatedAt()); if ($makeNew) { @@ -1381,6 +1438,7 @@ abstract class AttributeTemplate implements ActiveRecordInterface $this->attribute_id = null; $this->template_id = null; $this->position = null; + $this->attribute_templatecol = null; $this->created_at = null; $this->updated_at = null; $this->alreadyInSave = false; diff --git a/core/lib/Thelia/Model/Base/AttributeTemplateQuery.php b/core/lib/Thelia/Model/Base/AttributeTemplateQuery.php index e1a8d0320..5e53ba738 100644 --- a/core/lib/Thelia/Model/Base/AttributeTemplateQuery.php +++ b/core/lib/Thelia/Model/Base/AttributeTemplateQuery.php @@ -25,6 +25,7 @@ use Thelia\Model\Map\AttributeTemplateTableMap; * @method ChildAttributeTemplateQuery orderByAttributeId($order = Criteria::ASC) Order by the attribute_id column * @method ChildAttributeTemplateQuery orderByTemplateId($order = Criteria::ASC) Order by the template_id column * @method ChildAttributeTemplateQuery orderByPosition($order = Criteria::ASC) Order by the position column + * @method ChildAttributeTemplateQuery orderByAttributeTemplatecol($order = Criteria::ASC) Order by the attribute_templatecol column * @method ChildAttributeTemplateQuery orderByCreatedAt($order = Criteria::ASC) Order by the created_at column * @method ChildAttributeTemplateQuery orderByUpdatedAt($order = Criteria::ASC) Order by the updated_at column * @@ -32,6 +33,7 @@ use Thelia\Model\Map\AttributeTemplateTableMap; * @method ChildAttributeTemplateQuery groupByAttributeId() Group by the attribute_id column * @method ChildAttributeTemplateQuery groupByTemplateId() Group by the template_id column * @method ChildAttributeTemplateQuery groupByPosition() Group by the position column + * @method ChildAttributeTemplateQuery groupByAttributeTemplatecol() Group by the attribute_templatecol column * @method ChildAttributeTemplateQuery groupByCreatedAt() Group by the created_at column * @method ChildAttributeTemplateQuery groupByUpdatedAt() Group by the updated_at column * @@ -54,6 +56,7 @@ use Thelia\Model\Map\AttributeTemplateTableMap; * @method ChildAttributeTemplate findOneByAttributeId(int $attribute_id) Return the first ChildAttributeTemplate filtered by the attribute_id column * @method ChildAttributeTemplate findOneByTemplateId(int $template_id) Return the first ChildAttributeTemplate filtered by the template_id column * @method ChildAttributeTemplate findOneByPosition(int $position) Return the first ChildAttributeTemplate filtered by the position column + * @method ChildAttributeTemplate findOneByAttributeTemplatecol(string $attribute_templatecol) Return the first ChildAttributeTemplate filtered by the attribute_templatecol column * @method ChildAttributeTemplate findOneByCreatedAt(string $created_at) Return the first ChildAttributeTemplate filtered by the created_at column * @method ChildAttributeTemplate findOneByUpdatedAt(string $updated_at) Return the first ChildAttributeTemplate filtered by the updated_at column * @@ -61,6 +64,7 @@ use Thelia\Model\Map\AttributeTemplateTableMap; * @method array findByAttributeId(int $attribute_id) Return ChildAttributeTemplate objects filtered by the attribute_id column * @method array findByTemplateId(int $template_id) Return ChildAttributeTemplate objects filtered by the template_id column * @method array findByPosition(int $position) Return ChildAttributeTemplate objects filtered by the position column + * @method array findByAttributeTemplatecol(string $attribute_templatecol) Return ChildAttributeTemplate objects filtered by the attribute_templatecol column * @method array findByCreatedAt(string $created_at) Return ChildAttributeTemplate objects filtered by the created_at column * @method array findByUpdatedAt(string $updated_at) Return ChildAttributeTemplate objects filtered by the updated_at column * @@ -151,7 +155,7 @@ abstract class AttributeTemplateQuery extends ModelCriteria */ protected function findPkSimple($key, $con) { - $sql = 'SELECT ID, ATTRIBUTE_ID, TEMPLATE_ID, POSITION, CREATED_AT, UPDATED_AT FROM attribute_template WHERE ID = :p0'; + $sql = 'SELECT ID, ATTRIBUTE_ID, TEMPLATE_ID, POSITION, ATTRIBUTE_TEMPLATECOL, CREATED_AT, UPDATED_AT FROM attribute_template WHERE ID = :p0'; try { $stmt = $con->prepare($sql); $stmt->bindValue(':p0', $key, PDO::PARAM_INT); @@ -408,6 +412,35 @@ abstract class AttributeTemplateQuery extends ModelCriteria return $this->addUsingAlias(AttributeTemplateTableMap::POSITION, $position, $comparison); } + /** + * Filter the query on the attribute_templatecol column + * + * Example usage: + * + * $query->filterByAttributeTemplatecol('fooValue'); // WHERE attribute_templatecol = 'fooValue' + * $query->filterByAttributeTemplatecol('%fooValue%'); // WHERE attribute_templatecol LIKE '%fooValue%' + * + * + * @param string $attributeTemplatecol The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return ChildAttributeTemplateQuery The current query, for fluid interface + */ + public function filterByAttributeTemplatecol($attributeTemplatecol = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($attributeTemplatecol)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $attributeTemplatecol)) { + $attributeTemplatecol = str_replace('*', '%', $attributeTemplatecol); + $comparison = Criteria::LIKE; + } + } + + return $this->addUsingAlias(AttributeTemplateTableMap::ATTRIBUTE_TEMPLATECOL, $attributeTemplatecol, $comparison); + } + /** * Filter the query on the created_at column * diff --git a/core/lib/Thelia/Model/CategoryDocument.php b/core/lib/Thelia/Model/CategoryDocument.php index 5724e2df1..0917ab30c 100755 --- a/core/lib/Thelia/Model/CategoryDocument.php +++ b/core/lib/Thelia/Model/CategoryDocument.php @@ -25,4 +25,28 @@ class CategoryDocument extends BaseCategoryDocument return true; } + + /** + * Set Document parent id + * + * @param int $parentId parent id + * + * @return $this + */ + public function setParentId($parentId) + { + $this->setCategoryId($parentId); + + return $this; + } + + /** + * Get Document parent id + * + * @return int parent id + */ + public function getParentId() + { + return $this->getCategoryId(); + } } \ No newline at end of file diff --git a/core/lib/Thelia/Model/CategoryImage.php b/core/lib/Thelia/Model/CategoryImage.php index 5bf964e10..17ee387b5 100755 --- a/core/lib/Thelia/Model/CategoryImage.php +++ b/core/lib/Thelia/Model/CategoryImage.php @@ -2,6 +2,8 @@ namespace Thelia\Model; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpFoundation\File\UploadedFile; use Thelia\Model\Base\CategoryImage as BaseCategoryImage; use Propel\Runtime\Connection\ConnectionInterface; @@ -25,4 +27,29 @@ class CategoryImage extends BaseCategoryImage return true; } + + /** + * Set Image parent id + * + * @param int $parentId parent id + * + * @return $this + */ + public function setParentId($parentId) + { + $this->setCategoryId($parentId); + + return $this; + } + + /** + * Get Image parent id + * + * @return int parent id + */ + public function getParentId() + { + return $this->getCategoryId(); + } + } diff --git a/core/lib/Thelia/Model/ContentDocument.php b/core/lib/Thelia/Model/ContentDocument.php index 8ecf3a3a9..1409b2713 100755 --- a/core/lib/Thelia/Model/ContentDocument.php +++ b/core/lib/Thelia/Model/ContentDocument.php @@ -25,4 +25,28 @@ class ContentDocument extends BaseContentDocument return true; } + + /** + * Set Document parent id + * + * @param int $parentId parent id + * + * @return $this + */ + public function setParentId($parentId) + { + $this->setContentId($parentId); + + return $this; + } + + /** + * Get Document parent id + * + * @return int parent id + */ + public function getParentId() + { + return $this->getContentId(); + } } diff --git a/core/lib/Thelia/Model/ContentImage.php b/core/lib/Thelia/Model/ContentImage.php index ac1dcf755..b6a3085d4 100755 --- a/core/lib/Thelia/Model/ContentImage.php +++ b/core/lib/Thelia/Model/ContentImage.php @@ -25,4 +25,28 @@ class ContentImage extends BaseContentImage return true; } + + /** + * Set Image parent id + * + * @param int $parentId parent id + * + * @return $this + */ + public function setParentId($parentId) + { + $this->setContentId($parentId); + + return $this; + } + + /** + * Get Image parent id + * + * @return int parent id + */ + public function getParentId() + { + return $this->getContentId(); + } } \ No newline at end of file diff --git a/core/lib/Thelia/Model/FeatureProduct.php b/core/lib/Thelia/Model/FeatureProduct.php index 35a9b2ddc..fe6c5d8c1 100755 --- a/core/lib/Thelia/Model/FeatureProduct.php +++ b/core/lib/Thelia/Model/FeatureProduct.php @@ -3,8 +3,66 @@ namespace Thelia\Model; use Thelia\Model\Base\FeatureProduct as BaseFeatureProduct; +use Thelia\Core\Event\TheliaEvents; +use Propel\Runtime\Connection\ConnectionInterface; +use Thelia\Core\Event\FeatureProductEvent; - class FeatureProduct extends BaseFeatureProduct +class FeatureProduct extends BaseFeatureProduct { + use \Thelia\Model\Tools\ModelEventDispatcherTrait; + + /** + * {@inheritDoc} + */ + public function preInsert(ConnectionInterface $con = null) + { + $this->dispatchEvent(TheliaEvents::BEFORE_CREATEFEATURE_PRODUCT, new FeatureProductEvent($this)); + + return true; + } + + /** + * {@inheritDoc} + */ + public function postInsert(ConnectionInterface $con = null) + { + $this->dispatchEvent(TheliaEvents::AFTER_CREATEFEATURE_PRODUCT, new FeatureProductEvent($this)); + } + + /** + * {@inheritDoc} + */ + public function preUpdate(ConnectionInterface $con = null) + { + $this->dispatchEvent(TheliaEvents::BEFORE_UPDATEFEATURE_PRODUCT, new FeatureProductEvent($this)); + + return true; + } + + /** + * {@inheritDoc} + */ + public function postUpdate(ConnectionInterface $con = null) + { + $this->dispatchEvent(TheliaEvents::AFTER_UPDATEFEATURE_PRODUCT, new FeatureProductEvent($this)); + } + + /** + * {@inheritDoc} + */ + public function preDelete(ConnectionInterface $con = null) + { + $this->dispatchEvent(TheliaEvents::BEFORE_DELETEFEATURE_PRODUCT, new FeatureProductEvent($this)); + + return true; + } + + /** + * {@inheritDoc} + */ + public function postDelete(ConnectionInterface $con = null) + { + $this->dispatchEvent(TheliaEvents::AFTER_DELETEFEATURE_PRODUCT, new FeatureProductEvent($this)); + } } diff --git a/core/lib/Thelia/Model/FeatureTemplate.php b/core/lib/Thelia/Model/FeatureTemplate.php index 47a33027a..3f28a3a10 100644 --- a/core/lib/Thelia/Model/FeatureTemplate.php +++ b/core/lib/Thelia/Model/FeatureTemplate.php @@ -3,8 +3,30 @@ namespace Thelia\Model; use Thelia\Model\Base\FeatureTemplate as BaseFeatureTemplate; +use Propel\Runtime\Connection\ConnectionInterface; - class FeatureTemplate extends BaseFeatureTemplate +class FeatureTemplate extends BaseFeatureTemplate { + use \Thelia\Model\Tools\ModelEventDispatcherTrait; + use \Thelia\Model\Tools\PositionManagementTrait; + + /** + * Calculate next position relative to our template + */ + protected function addCriteriaToPositionQuery($query) + { + $query->filterByTemplateId($this->getTemplateId()); + } + + /** + * {@inheritDoc} + */ + public function preInsert(ConnectionInterface $con = null) + { + // Set the current position for the new object + $this->setPosition($this->getNextPosition()); + + return true; + } } diff --git a/core/lib/Thelia/Model/FolderDocument.php b/core/lib/Thelia/Model/FolderDocument.php index 0a86995d2..1d84d9e55 100755 --- a/core/lib/Thelia/Model/FolderDocument.php +++ b/core/lib/Thelia/Model/FolderDocument.php @@ -25,4 +25,28 @@ class FolderDocument extends BaseFolderDocument return true; } + + /** + * Set Document parent id + * + * @param int $parentId parent id + * + * @return $this + */ + public function setParentId($parentId) + { + $this->setFolderId($parentId); + + return $this; + } + + /** + * Get Document parent id + * + * @return int parent id + */ + public function getParentId() + { + return $this->getFolderId(); + } } diff --git a/core/lib/Thelia/Model/FolderImage.php b/core/lib/Thelia/Model/FolderImage.php index 58d8f928e..f9491c9a5 100755 --- a/core/lib/Thelia/Model/FolderImage.php +++ b/core/lib/Thelia/Model/FolderImage.php @@ -25,4 +25,28 @@ class FolderImage extends BaseFolderImage return true; } + + /** + * Set Image parent id + * + * @param int $parentId parent id + * + * @return $this + */ + public function setParentId($parentId) + { + $this->setFolderId($parentId); + + return $this; + } + + /** + * Get Image parent id + * + * @return int parent id + */ + public function getParentId() + { + return $this->getFolderId(); + } } diff --git a/core/lib/Thelia/Model/Map/AttributeTemplateTableMap.php b/core/lib/Thelia/Model/Map/AttributeTemplateTableMap.php index 1df6d56c1..c0df89d8f 100644 --- a/core/lib/Thelia/Model/Map/AttributeTemplateTableMap.php +++ b/core/lib/Thelia/Model/Map/AttributeTemplateTableMap.php @@ -57,7 +57,7 @@ class AttributeTemplateTableMap extends TableMap /** * The total number of columns */ - const NUM_COLUMNS = 6; + const NUM_COLUMNS = 7; /** * The number of lazy-loaded columns @@ -67,7 +67,7 @@ class AttributeTemplateTableMap extends TableMap /** * The number of columns to hydrate (NUM_COLUMNS - NUM_LAZY_LOAD_COLUMNS) */ - const NUM_HYDRATE_COLUMNS = 6; + const NUM_HYDRATE_COLUMNS = 7; /** * the column name for the ID field @@ -89,6 +89,11 @@ class AttributeTemplateTableMap extends TableMap */ const POSITION = 'attribute_template.POSITION'; + /** + * the column name for the ATTRIBUTE_TEMPLATECOL field + */ + const ATTRIBUTE_TEMPLATECOL = 'attribute_template.ATTRIBUTE_TEMPLATECOL'; + /** * the column name for the CREATED_AT field */ @@ -111,12 +116,12 @@ class AttributeTemplateTableMap extends TableMap * e.g. self::$fieldNames[self::TYPE_PHPNAME][0] = 'Id' */ protected static $fieldNames = array ( - self::TYPE_PHPNAME => array('Id', 'AttributeId', 'TemplateId', 'Position', 'CreatedAt', 'UpdatedAt', ), - self::TYPE_STUDLYPHPNAME => array('id', 'attributeId', 'templateId', 'position', 'createdAt', 'updatedAt', ), - self::TYPE_COLNAME => array(AttributeTemplateTableMap::ID, AttributeTemplateTableMap::ATTRIBUTE_ID, AttributeTemplateTableMap::TEMPLATE_ID, AttributeTemplateTableMap::POSITION, AttributeTemplateTableMap::CREATED_AT, AttributeTemplateTableMap::UPDATED_AT, ), - self::TYPE_RAW_COLNAME => array('ID', 'ATTRIBUTE_ID', 'TEMPLATE_ID', 'POSITION', 'CREATED_AT', 'UPDATED_AT', ), - self::TYPE_FIELDNAME => array('id', 'attribute_id', 'template_id', 'position', 'created_at', 'updated_at', ), - self::TYPE_NUM => array(0, 1, 2, 3, 4, 5, ) + self::TYPE_PHPNAME => array('Id', 'AttributeId', 'TemplateId', 'Position', 'AttributeTemplatecol', 'CreatedAt', 'UpdatedAt', ), + self::TYPE_STUDLYPHPNAME => array('id', 'attributeId', 'templateId', 'position', 'attributeTemplatecol', 'createdAt', 'updatedAt', ), + self::TYPE_COLNAME => array(AttributeTemplateTableMap::ID, AttributeTemplateTableMap::ATTRIBUTE_ID, AttributeTemplateTableMap::TEMPLATE_ID, AttributeTemplateTableMap::POSITION, AttributeTemplateTableMap::ATTRIBUTE_TEMPLATECOL, AttributeTemplateTableMap::CREATED_AT, AttributeTemplateTableMap::UPDATED_AT, ), + self::TYPE_RAW_COLNAME => array('ID', 'ATTRIBUTE_ID', 'TEMPLATE_ID', 'POSITION', 'ATTRIBUTE_TEMPLATECOL', 'CREATED_AT', 'UPDATED_AT', ), + self::TYPE_FIELDNAME => array('id', 'attribute_id', 'template_id', 'position', 'attribute_templatecol', 'created_at', 'updated_at', ), + self::TYPE_NUM => array(0, 1, 2, 3, 4, 5, 6, ) ); /** @@ -126,12 +131,12 @@ class AttributeTemplateTableMap extends TableMap * e.g. self::$fieldKeys[self::TYPE_PHPNAME]['Id'] = 0 */ protected static $fieldKeys = array ( - self::TYPE_PHPNAME => array('Id' => 0, 'AttributeId' => 1, 'TemplateId' => 2, 'Position' => 3, 'CreatedAt' => 4, 'UpdatedAt' => 5, ), - self::TYPE_STUDLYPHPNAME => array('id' => 0, 'attributeId' => 1, 'templateId' => 2, 'position' => 3, 'createdAt' => 4, 'updatedAt' => 5, ), - self::TYPE_COLNAME => array(AttributeTemplateTableMap::ID => 0, AttributeTemplateTableMap::ATTRIBUTE_ID => 1, AttributeTemplateTableMap::TEMPLATE_ID => 2, AttributeTemplateTableMap::POSITION => 3, AttributeTemplateTableMap::CREATED_AT => 4, AttributeTemplateTableMap::UPDATED_AT => 5, ), - self::TYPE_RAW_COLNAME => array('ID' => 0, 'ATTRIBUTE_ID' => 1, 'TEMPLATE_ID' => 2, 'POSITION' => 3, 'CREATED_AT' => 4, 'UPDATED_AT' => 5, ), - self::TYPE_FIELDNAME => array('id' => 0, 'attribute_id' => 1, 'template_id' => 2, 'position' => 3, 'created_at' => 4, 'updated_at' => 5, ), - self::TYPE_NUM => array(0, 1, 2, 3, 4, 5, ) + self::TYPE_PHPNAME => array('Id' => 0, 'AttributeId' => 1, 'TemplateId' => 2, 'Position' => 3, 'AttributeTemplatecol' => 4, 'CreatedAt' => 5, 'UpdatedAt' => 6, ), + self::TYPE_STUDLYPHPNAME => array('id' => 0, 'attributeId' => 1, 'templateId' => 2, 'position' => 3, 'attributeTemplatecol' => 4, 'createdAt' => 5, 'updatedAt' => 6, ), + self::TYPE_COLNAME => array(AttributeTemplateTableMap::ID => 0, AttributeTemplateTableMap::ATTRIBUTE_ID => 1, AttributeTemplateTableMap::TEMPLATE_ID => 2, AttributeTemplateTableMap::POSITION => 3, AttributeTemplateTableMap::ATTRIBUTE_TEMPLATECOL => 4, AttributeTemplateTableMap::CREATED_AT => 5, AttributeTemplateTableMap::UPDATED_AT => 6, ), + self::TYPE_RAW_COLNAME => array('ID' => 0, 'ATTRIBUTE_ID' => 1, 'TEMPLATE_ID' => 2, 'POSITION' => 3, 'ATTRIBUTE_TEMPLATECOL' => 4, 'CREATED_AT' => 5, 'UPDATED_AT' => 6, ), + self::TYPE_FIELDNAME => array('id' => 0, 'attribute_id' => 1, 'template_id' => 2, 'position' => 3, 'attribute_templatecol' => 4, 'created_at' => 5, 'updated_at' => 6, ), + self::TYPE_NUM => array(0, 1, 2, 3, 4, 5, 6, ) ); /** @@ -155,6 +160,7 @@ class AttributeTemplateTableMap extends TableMap $this->addForeignKey('ATTRIBUTE_ID', 'AttributeId', 'INTEGER', 'attribute', 'ID', true, null, null); $this->addForeignKey('TEMPLATE_ID', 'TemplateId', 'INTEGER', 'template', 'ID', true, null, null); $this->addColumn('POSITION', 'Position', 'INTEGER', false, null, null); + $this->addColumn('ATTRIBUTE_TEMPLATECOL', 'AttributeTemplatecol', 'VARCHAR', false, 45, null); $this->addColumn('CREATED_AT', 'CreatedAt', 'TIMESTAMP', false, null, null); $this->addColumn('UPDATED_AT', 'UpdatedAt', 'TIMESTAMP', false, null, null); } // initialize() @@ -323,6 +329,7 @@ class AttributeTemplateTableMap extends TableMap $criteria->addSelectColumn(AttributeTemplateTableMap::ATTRIBUTE_ID); $criteria->addSelectColumn(AttributeTemplateTableMap::TEMPLATE_ID); $criteria->addSelectColumn(AttributeTemplateTableMap::POSITION); + $criteria->addSelectColumn(AttributeTemplateTableMap::ATTRIBUTE_TEMPLATECOL); $criteria->addSelectColumn(AttributeTemplateTableMap::CREATED_AT); $criteria->addSelectColumn(AttributeTemplateTableMap::UPDATED_AT); } else { @@ -330,6 +337,7 @@ class AttributeTemplateTableMap extends TableMap $criteria->addSelectColumn($alias . '.ATTRIBUTE_ID'); $criteria->addSelectColumn($alias . '.TEMPLATE_ID'); $criteria->addSelectColumn($alias . '.POSITION'); + $criteria->addSelectColumn($alias . '.ATTRIBUTE_TEMPLATECOL'); $criteria->addSelectColumn($alias . '.CREATED_AT'); $criteria->addSelectColumn($alias . '.UPDATED_AT'); } diff --git a/core/lib/Thelia/Model/Product.php b/core/lib/Thelia/Model/Product.php index cbb6c0051..2348a9c0d 100755 --- a/core/lib/Thelia/Model/Product.php +++ b/core/lib/Thelia/Model/Product.php @@ -87,12 +87,46 @@ class Product extends BaseProduct return $this; } + public function updateDefaultCategory($defaultCategoryId) { + + // Allow uncategorized products (NULL instead of 0, to bypass delete cascade constraint) + if ($defaultCategoryId <= 0) $defaultCategoryId = NULL; + + // Update the default category + $productCategory = ProductCategoryQuery::create() + ->filterByProductId($this->getId()) + ->filterByDefaultCategory(true) + ->findOne() + ; + + if ($productCategory == null || $productCategory->getCategoryId() != $defaultCategoryId) { + exit; + // Delete the old default category + if ($productCategory !== null) $productCategory->delete(); + + // Add the new default category + $productCategory = new ProductCategory(); + + $productCategory + ->setProduct($this) + ->setCategoryId($defaultCategoryId) + ->setDefaultCategory(true) + ->save() + ; + } + } + /** * Create a new product, along with the default category ID * * @param int $defaultCategoryId the default category ID of this product + * @param float $basePrice the product base price + * @param int $priceCurrencyId the price currency Id + * @param int $taxRuleId the product tax rule ID + * @param float $baseWeight base weight in Kg */ - public function create($defaultCategoryId) { + + public function create($defaultCategoryId, $basePrice, $priceCurrencyId, $taxRuleId, $baseWeight) { $con = Propel::getWriteConnection(ProductTableMap::DATABASE_NAME); @@ -105,18 +139,13 @@ class Product extends BaseProduct $this->save($con); // Add the default category - $pc = new ProductCategory(); - - $pc - ->setProduct($this) - ->setCategoryId($defaultCategoryId) - ->setDefaultCategory(true) - ->save($con) - ; + $this->updateDefaultCategory($defaultCategoryId); // Set the position $this->setPosition($this->getNextPosition())->save($con); + $this->setTaxRuleId($taxRuleId); + // Create an empty product sale element $sale_elements = new ProductSaleElements(); @@ -125,7 +154,8 @@ class Product extends BaseProduct ->setRef($this->getRef()) ->setPromo(0) ->setNewness(0) - ->setWeight(0) + ->setWeight($baseWeight) + ->setIsDefault(true) ->save($con) ; @@ -134,9 +164,9 @@ class Product extends BaseProduct $product_price ->setProductSaleElements($sale_elements) - ->setPromoPrice(0) - ->setPrice(0) - ->setCurrency(CurrencyQuery::create()->findOneByByDefault(true)) + ->setPromoPrice($basePrice) + ->setPrice($basePrice) + ->setCurrencyId($priceCurrencyId) ->save($con) ; diff --git a/core/lib/Thelia/Model/ProductAssociatedContent.php b/core/lib/Thelia/Model/ProductAssociatedContent.php index e07ee2cd6..843d76ba1 100644 --- a/core/lib/Thelia/Model/ProductAssociatedContent.php +++ b/core/lib/Thelia/Model/ProductAssociatedContent.php @@ -11,11 +11,22 @@ class ProductAssociatedContent extends BaseProductAssociatedContent { use \Thelia\Model\Tools\ModelEventDispatcherTrait; + use \Thelia\Model\Tools\PositionManagementTrait; + + /** + * Calculate next position relative to our product + */ + protected function addCriteriaToPositionQuery($query) { + $query->filterByProductId($this->getProductId()); + } + /** * {@inheritDoc} */ public function preInsert(ConnectionInterface $con = null) { + $this->setPosition($this->getNextPosition()); + $this->dispatchEvent(TheliaEvents::BEFORE_CREATEPRODUCT_ASSOCIATED_CONTENT, new ProductAssociatedContentEvent($this)); return true; diff --git a/core/lib/Thelia/Model/ProductDocument.php b/core/lib/Thelia/Model/ProductDocument.php index 53515ff3c..b0f8032da 100755 --- a/core/lib/Thelia/Model/ProductDocument.php +++ b/core/lib/Thelia/Model/ProductDocument.php @@ -26,4 +26,28 @@ class ProductDocument extends BaseProductDocument return true; } + /** + * Set Document parent id + * + * @param int $parentId parent id + * + * @return $this + */ + public function setParentId($parentId) + { + $this->setProductId($parentId); + + return $this; + } + + /** + * Get Document parent id + * + * @return int parent id + */ + public function getParentId() + { + return $this->getProductId(); + } + } diff --git a/core/lib/Thelia/Model/ProductImage.php b/core/lib/Thelia/Model/ProductImage.php index 4bf0c40a6..6cc3ddd8c 100755 --- a/core/lib/Thelia/Model/ProductImage.php +++ b/core/lib/Thelia/Model/ProductImage.php @@ -25,4 +25,28 @@ class ProductImage extends BaseProductImage return true; } + + /** + * Set Image parent id + * + * @param int $parentId parent id + * + * @return $this + */ + public function setParentId($parentId) + { + $this->setProductId($parentId); + + return $this; + } + + /** + * Get Image parent id + * + * @return int parent id + */ + public function getParentId() + { + return $this->getProductId(); + } } diff --git a/core/lib/Thelia/Model/Tools/PositionManagementTrait.php b/core/lib/Thelia/Model/Tools/PositionManagementTrait.php index 70da830ac..642d07402 100644 --- a/core/lib/Thelia/Model/Tools/PositionManagementTrait.php +++ b/core/lib/Thelia/Model/Tools/PositionManagementTrait.php @@ -126,7 +126,8 @@ trait PositionManagementTrait { $result->setDispatcher($this->getDispatcher())->setPosition($my_position)->save(); $cnx->commit(); - } catch (Exception $e) { + } + catch (Exception $e) { $cnx->rollback(); } } @@ -179,7 +180,10 @@ trait PositionManagementTrait { try { foreach ($results as $result) { - $result->setDispatcher($this->getDispatcher())->setPosition($result->getPosition() + $delta)->save($cnx); + + $objNewPosition = $result->getPosition() + $delta; + + $result->setDispatcher($this->getDispatcher())->setPosition($objNewPosition)->save($cnx); } $this @@ -188,7 +192,8 @@ trait PositionManagementTrait { ; $cnx->commit(); - } catch (Exception $e) { + } + catch (Exception $e) { $cnx->rollback(); } } diff --git a/core/lib/Thelia/Tests/Tools/FileManagerTest.php b/core/lib/Thelia/Tests/Tools/FileManagerTest.php new file mode 100644 index 000000000..8c1c9fe99 --- /dev/null +++ b/core/lib/Thelia/Tests/Tools/FileManagerTest.php @@ -0,0 +1,906 @@ + + */ + +namespace Thelia\Tests\Type; + + +use Thelia\Core\Event\DocumentCreateOrUpdateEvent; +use Thelia\Core\Event\ImageCreateOrUpdateEvent; +use Thelia\Core\Translation\Translator; +use Thelia\Exception\ImageException; +use Thelia\Model\Admin; +use Thelia\Tools\FileManager; + +/** + * Class FileManagerTest + * + * @package Thelia\Tests\Type + */ +class FileManagerTest extends \PHPUnit_Framework_TestCase { + + + /** + * @covers Thelia\Tools\FileManager::copyUploadedFile + */ + public function testCopyUploadedFile() + { + $this->markTestIncomplete( + 'Mock issue' + ); + + $stubTranslator = $this->getMockBuilder('\Thelia\Core\Translation\Translator') + ->disableOriginalConstructor() + ->getMock(); + $stubTranslator->expects($this->any()) + ->method('trans') + ->will($this->returnValue('translated')); + + $stubRequest = $this->getMockBuilder('\Thelia\Core\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $stubSecurity = $this->getMockBuilder('\Thelia\Core\Security\SecurityContext') + ->disableOriginalConstructor() + ->getMock(); + $stubSecurity->expects($this->any()) + ->method('getAdminUser') + ->will($this->returnValue(new Admin())); + + + + // Create a map of arguments to return values. + $map = array( + array('thelia.translator', $stubTranslator), + array('request', $stubRequest), + array('thelia.securityContext', $stubSecurity) + ); + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + $stubContainer->expects($this->any()) + ->method('get') + ->will($this->returnValueMap($map)); + + $stubProductImage = $this->getMockBuilder('\Thelia\Model\ProductImage') + ->disableOriginalConstructor() + ->getMock(); + $stubProductImage->expects($this->any()) + ->method('getUploadDir') + ->will($this->returnValue(THELIA_LOCAL_DIR . 'media/images/product')); + $stubProductImage->expects($this->any()) + ->method('getId') + ->will($this->returnValue(42)); + $stubProductImage->expects($this->any()) + ->method('setFile') + ->will($this->returnValue(true)); + $stubProductImage->expects($this->any()) + ->method('save') + ->will($this->returnValue(0)); + + $stubUploadedFile = $this->getMockBuilder('\Symfony\Component\HttpFoundation\File\UploadedFile') + ->disableOriginalConstructor() + ->getMock(); + $stubUploadedFile->expects($this->any()) + ->method('getClientOriginalName') + ->will($this->returnValue('goodName')); + $stubUploadedFile->expects($this->any()) + ->method('getClientOriginalExtension') + ->will($this->returnValue('png')); + $stubUploadedFile->expects($this->any()) + ->method('move') + ->will($this->returnValue($stubUploadedFile)); + + $fileManager = new FileManager($stubContainer); + + $newUploadedFiles = array(); + + $actual = $fileManager->copyUploadedFile(24, FileManager::TYPE_PRODUCT, $stubProductImage, $stubUploadedFile, $newUploadedFiles, FileManager::FILE_TYPE_IMAGES); + + $this->assertCount(1, $actual); + } + + + /** + * @covers Thelia\Tools\FileManager::copyUploadedFile + * @expectedException \Thelia\Exception\ImageException + */ + public function testCopyUploadedFileExceptionImageException() + { + $this->markTestIncomplete( + 'Mock issue' + ); + + $stubTranslator = $this->getMockBuilder('\Thelia\Core\Translation\Translator') + ->disableOriginalConstructor() + ->getMock(); + $stubTranslator->expects($this->any()) + ->method('trans') + ->will($this->returnValue('translated')); + + $stubRequest = $this->getMockBuilder('\Thelia\Core\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $stubSecurity = $this->getMockBuilder('\Thelia\Core\Security\SecurityContext') + ->disableOriginalConstructor() + ->getMock(); + $stubSecurity->expects($this->any()) + ->method('getAdminUser') + ->will($this->returnValue(new Admin())); + + + + // Create a map of arguments to return values. + $map = array( + array('thelia.translator', $stubTranslator), + array('request', $stubRequest), + array('thelia.securityContext', $stubSecurity) + ); + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + $stubContainer->expects($this->any()) + ->method('get') + ->will($this->returnValueMap($map)); + + $stubProductImage = $this->getMockBuilder('\Thelia\Model\ProductImage') + ->disableOriginalConstructor() + ->getMock(); + $stubProductImage->expects($this->any()) + ->method('getUploadDir') + ->will($this->returnValue(THELIA_LOCAL_DIR . 'media/images/product')); + $stubProductImage->expects($this->any()) + ->method('getId') + ->will($this->returnValue(42)); + $stubProductImage->expects($this->any()) + ->method('setFile') + ->will($this->returnValue(true)); + $stubProductImage->expects($this->any()) + ->method('save') + ->will($this->returnValue(0)); + + $stubUploadedFile = $this->getMockBuilder('\Symfony\Component\HttpFoundation\File\UploadedFile') + ->disableOriginalConstructor() + ->getMock(); + $stubUploadedFile->expects($this->any()) + ->method('getClientOriginalName') + ->will($this->returnValue('goodName')); + $stubUploadedFile->expects($this->any()) + ->method('getClientOriginalExtension') + ->will($this->returnValue('png')); + $stubUploadedFile->expects($this->any()) + ->method('move') + ->will($this->returnValue($stubUploadedFile)); + + $fileManager = new FileManager($stubContainer); + + $newUploadedFiles = array(); + + $actual = $fileManager->copyUploadedFile(24, FileManager::TYPE_PRODUCT, $stubProductImage, $stubUploadedFile, $newUploadedFiles, FileManager::FILE_TYPE_DOCUMENTS); + + } + + /** + * @covers Thelia\Tools\FileManager::saveImage + */ + public function testSaveImageProductImage() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $stubProductImage = $this->getMockBuilder('\Thelia\Model\ProductImage') + ->disableOriginalConstructor() + ->getMock(); + $stubProductImage->expects($this->any()) + ->method('save') + ->will($this->returnValue(10)); + $stubProductImage->expects($this->any()) + ->method('getFile') + ->will($this->returnValue('file')); + + $fileManager = new FileManager($stubContainer); + + $event = new ImageCreateOrUpdateEvent(FileManager::TYPE_PRODUCT, 24); + + $expected = 10; + $actual = $fileManager->saveImage($event, $stubProductImage); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::saveDocument + */ + public function testSaveDocumentProductDocument() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $stubProductDocument = $this->getMockBuilder('\Thelia\Model\ProductDocument') + ->disableOriginalConstructor() + ->getMock(); + $stubProductDocument->expects($this->any()) + ->method('save') + ->will($this->returnValue(10)); + $stubProductDocument->expects($this->any()) + ->method('getFile') + ->will($this->returnValue('file')); + + $fileManager = new FileManager($stubContainer); + + $event = new DocumentCreateOrUpdateEvent(FileManager::TYPE_PRODUCT, 24); + + $expected = 10; + $actual = $fileManager->saveDocument($event, $stubProductDocument); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::saveImage + */ + public function testSaveImageCategoryImage() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $stubCategoryImage = $this->getMockBuilder('\Thelia\Model\CategoryImage') + ->disableOriginalConstructor() + ->getMock(); + $stubCategoryImage->expects($this->any()) + ->method('save') + ->will($this->returnValue(10)); + $stubCategoryImage->expects($this->any()) + ->method('getFile') + ->will($this->returnValue('file')); + + $fileManager = new FileManager($stubContainer); + + $event = new ImageCreateOrUpdateEvent(FileManager::TYPE_CATEGORY, 24); + + $expected = 10; + $actual = $fileManager->saveImage($event, $stubCategoryImage); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::saveDocument + */ + public function testSaveDocumentCategoryDocument() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $stubCategoryDocument = $this->getMockBuilder('\Thelia\Model\CategoryDocument') + ->disableOriginalConstructor() + ->getMock(); + $stubCategoryDocument->expects($this->any()) + ->method('save') + ->will($this->returnValue(10)); + $stubCategoryDocument->expects($this->any()) + ->method('getFile') + ->will($this->returnValue('file')); + + $fileManager = new FileManager($stubContainer); + + $event = new DocumentCreateOrUpdateEvent(FileManager::TYPE_CATEGORY, 24); + + $expected = 10; + $actual = $fileManager->saveDocument($event, $stubCategoryDocument); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::saveImage + */ + public function testSaveImageFolderImage() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $stubFolderImage = $this->getMockBuilder('\Thelia\Model\FolderImage') + ->disableOriginalConstructor() + ->getMock(); + $stubFolderImage->expects($this->any()) + ->method('save') + ->will($this->returnValue(10)); + $stubFolderImage->expects($this->any()) + ->method('getFile') + ->will($this->returnValue('file')); + + $fileManager = new FileManager($stubContainer); + + $event = new ImageCreateOrUpdateEvent(FileManager::TYPE_FOLDER, 24); + + $expected = 10; + $actual = $fileManager->saveImage($event, $stubFolderImage); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::saveDocument + */ + public function testSaveDocumentFolderDocument() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $stubFolderDocument = $this->getMockBuilder('\Thelia\Model\FolderDocument') + ->disableOriginalConstructor() + ->getMock(); + $stubFolderDocument->expects($this->any()) + ->method('save') + ->will($this->returnValue(10)); + $stubFolderDocument->expects($this->any()) + ->method('getFile') + ->will($this->returnValue('file')); + + $fileManager = new FileManager($stubContainer); + + $event = new DocumentCreateOrUpdateEvent(FileManager::TYPE_FOLDER, 24); + + $expected = 10; + $actual = $fileManager->saveDocument($event, $stubFolderDocument); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::saveImage + */ + public function testSaveImageContentImage() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $stubContentImage = $this->getMockBuilder('\Thelia\Model\ContentImage') + ->disableOriginalConstructor() + ->getMock(); + $stubContentImage->expects($this->any()) + ->method('save') + ->will($this->returnValue(10)); + $stubContentImage->expects($this->any()) + ->method('getFile') + ->will($this->returnValue('file')); + + $fileManager = new FileManager($stubContainer); + + $event = new ImageCreateOrUpdateEvent(FileManager::TYPE_CONTENT, 24); + + $expected = 10; + $actual = $fileManager->saveImage($event, $stubContentImage); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::saveDocument + */ + public function testSaveDocumentContentDocument() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $stubContentDocument = $this->getMockBuilder('\Thelia\Model\ContentDocument') + ->disableOriginalConstructor() + ->getMock(); + $stubContentDocument->expects($this->any()) + ->method('save') + ->will($this->returnValue(10)); + $stubContentDocument->expects($this->any()) + ->method('getFile') + ->will($this->returnValue('file')); + + $fileManager = new FileManager($stubContainer); + + $event = new DocumentCreateOrUpdateEvent(FileManager::TYPE_CONTENT, 24); + + $expected = 10; + $actual = $fileManager->saveDocument($event, $stubContentDocument); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::saveImage + * @expectedException \Thelia\Exception\ImageException + */ + public function testSaveImageExceptionImageException() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + $fileManager = new FileManager($stubContainer); + + $stubProductImage = $this->getMockBuilder('\Thelia\Model\ProductImage') + ->disableOriginalConstructor() + ->getMock(); + $stubProductImage->expects($this->any()) + ->method('save') + ->will($this->returnValue(10)); + $stubProductImage->expects($this->any()) + ->method('getFile') + ->will($this->returnValue('file')); + + $event = new ImageCreateOrUpdateEvent('bad', 24); + + $fileManager->saveImage($event, $stubProductImage); + } + + /** + * @covers Thelia\Tools\FileManager::saveDocument + * @expectedException \Thelia\Model\Exception\InvalidArgumentException + */ + public function testSaveDocumentExceptionDocumentException() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + $fileManager = new FileManager($stubContainer); + + $stubProductDocument = $this->getMockBuilder('\Thelia\Model\ProductDocument') + ->disableOriginalConstructor() + ->getMock(); + $stubProductDocument->expects($this->any()) + ->method('save') + ->will($this->returnValue(10)); + $stubProductDocument->expects($this->any()) + ->method('getFile') + ->will($this->returnValue('file')); + + $event = new DocumentCreateOrUpdateEvent('bad', 24); + + $fileManager->saveDocument($event, $stubProductDocument); + } + + /** + * @covers Thelia\Tools\FileManager::saveImage + * @expectedException \Thelia\Exception\ImageException + */ + public function testSaveImageExceptionImageException2() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + $fileManager = new FileManager($stubContainer); + + $stubProductImage = $this->getMockBuilder('\Thelia\Model\ProductImage') + ->disableOriginalConstructor() + ->getMock(); + $stubProductImage->expects($this->any()) + ->method('save') + ->will($this->returnValue(0)); + $stubProductImage->expects($this->any()) + ->method('getFile') + ->will($this->returnValue('file')); + + $event = new ImageCreateOrUpdateEvent(FileManager::TYPE_PRODUCT, 24); + + $fileManager->saveImage($event, $stubProductImage); + } + + /** + * @covers Thelia\Tools\FileManager::saveDocument + * @expectedException \Thelia\Model\Exception\InvalidArgumentException + */ + public function testSaveDocumentExceptionDocumentException2() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + $fileManager = new FileManager($stubContainer); + + $stubProductDocument = $this->getMockBuilder('\Thelia\Model\ProductDocument') + ->disableOriginalConstructor() + ->getMock(); + $stubProductDocument->expects($this->any()) + ->method('save') + ->will($this->returnValue(0)); + $stubProductDocument->expects($this->any()) + ->method('getFile') + ->will($this->returnValue('file')); + + $event = new DocumentCreateOrUpdateEvent(FileManager::TYPE_PRODUCT, 24); + + $fileManager->saveDocument($event, $stubProductDocument); + } + + /** + * @covers Thelia\Tools\FileManager::sanitizeFileName + */ + public function testSanitizeFileName() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $fileManager = new FileManager($stubContainer); + $badFileName = 'a/ze\érà~çè§^"$*+-_°)(&é<>@#ty2/[\/:*?"<>|]/fi?.fUPPERile.exel../e*'; + + $expected = 'azer-_ty2fi.fupperile.exel..e'; + $actual = $fileManager->sanitizeFileName($badFileName); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::getImageModel + */ + public function testGetImageModel() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $fileManager = new FileManager($stubContainer); + $actual = $fileManager->getImageModel(FileManager::TYPE_PRODUCT); + $this->assertInstanceOf('\Thelia\Model\ProductImage', $actual); + $actual = $fileManager->getImageModel(FileManager::TYPE_CATEGORY); + $this->assertInstanceOf('\Thelia\Model\CategoryImage', $actual); + $actual = $fileManager->getImageModel(FileManager::TYPE_CONTENT); + $this->assertInstanceOf('\Thelia\Model\ContentImage', $actual); + $actual = $fileManager->getImageModel(FileManager::TYPE_FOLDER); + $this->assertInstanceOf('\Thelia\Model\FolderImage', $actual); + $actual = $fileManager->getImageModel('bad'); + $this->assertNull($actual); + } + + /** + * @covers Thelia\Tools\FileManager::getDocumentModel + */ + public function testGetDocumentModel() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $fileManager = new FileManager($stubContainer); + $actual = $fileManager->getDocumentModel(FileManager::TYPE_PRODUCT); + $this->assertInstanceOf('\Thelia\Model\ProductDocument', $actual); + $actual = $fileManager->getDocumentModel(FileManager::TYPE_CATEGORY); + $this->assertInstanceOf('\Thelia\Model\CategoryDocument', $actual); + $actual = $fileManager->getDocumentModel(FileManager::TYPE_CONTENT); + $this->assertInstanceOf('\Thelia\Model\ContentDocument', $actual); + $actual = $fileManager->getDocumentModel(FileManager::TYPE_FOLDER); + $this->assertInstanceOf('\Thelia\Model\FolderDocument', $actual); + $actual = $fileManager->getDocumentModel('bad'); + $this->assertNull($actual); + } + + /** + * @covers Thelia\Tools\FileManager::getImageModelQuery + */ + public function testGetImageModelQuery() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $fileManager = new FileManager($stubContainer); + $actual = $fileManager->getImageModelQuery(FileManager::TYPE_PRODUCT); + $this->assertInstanceOf('\Thelia\Model\ProductImageQuery', $actual); + $actual = $fileManager->getImageModelQuery(FileManager::TYPE_CATEGORY); + $this->assertInstanceOf('\Thelia\Model\CategoryImageQuery', $actual); + $actual = $fileManager->getImageModelQuery(FileManager::TYPE_CONTENT); + $this->assertInstanceOf('\Thelia\Model\ContentImageQuery', $actual); + $actual = $fileManager->getImageModelQuery(FileManager::TYPE_FOLDER); + $this->assertInstanceOf('\Thelia\Model\FolderImageQuery', $actual); + $actual = $fileManager->getImageModelQuery('bad'); + $this->assertNull($actual); + } + + /** + * @covers Thelia\Tools\FileManager::getDocumentModelQuery + */ + public function testGetDocumentModelQuery() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $fileManager = new FileManager($stubContainer); + $actual = $fileManager->getDocumentModelQuery(FileManager::TYPE_PRODUCT); + $this->assertInstanceOf('\Thelia\Model\ProductDocumentQuery', $actual); + $actual = $fileManager->getDocumentModelQuery(FileManager::TYPE_CATEGORY); + $this->assertInstanceOf('\Thelia\Model\CategoryDocumentQuery', $actual); + $actual = $fileManager->getDocumentModelQuery(FileManager::TYPE_CONTENT); + $this->assertInstanceOf('\Thelia\Model\ContentDocumentQuery', $actual); + $actual = $fileManager->getDocumentModelQuery(FileManager::TYPE_FOLDER); + $this->assertInstanceOf('\Thelia\Model\FolderDocumentQuery', $actual); + $actual = $fileManager->getDocumentModelQuery('bad'); + $this->assertNull($actual); + } + + /** + * @covers Thelia\Tools\FileManager::getParentFileModel + */ + public function testGetParentFileModel() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $fileManager = new FileManager($stubContainer); + $actual = $fileManager->getParentFileModel(FileManager::TYPE_PRODUCT, 1); + $this->assertInstanceOf('\Thelia\Model\Product', $actual); + $actual = $fileManager->getParentFileModel(FileManager::TYPE_CATEGORY, 1); + $this->assertInstanceOf('\Thelia\Model\Category', $actual); + $actual = $fileManager->getParentFileModel(FileManager::TYPE_CONTENT, 1); + $this->assertInstanceOf('\Thelia\Model\Content', $actual); + $actual = $fileManager->getParentFileModel(FileManager::TYPE_FOLDER, 1); + $this->assertInstanceOf('\Thelia\Model\Folder', $actual, 1); + $actual = $fileManager->getParentFileModel('bad', 1); + $this->assertNull($actual); + } + + /** + * @covers Thelia\Tools\FileManager::getImageForm + */ + public function testGetImageForm() + { + // Mock issue + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + /** + * @covers Thelia\Tools\FileManager::getDocumentForm + */ + public function testGetDocumentForm() + { + // Mock issue + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * @covers Thelia\Tools\FileManager::getUploadDir + */ + public function testGetUploadDir() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $fileManager = new FileManager($stubContainer); + + $actual = $fileManager->getUploadDir(FileManager::TYPE_PRODUCT, FileManager::FILE_TYPE_IMAGES); + $this->assertEquals(THELIA_LOCAL_DIR . 'media/images/product', $actual); + $actual = $fileManager->getUploadDir(FileManager::TYPE_CATEGORY, FileManager::FILE_TYPE_IMAGES); + $this->assertEquals(THELIA_LOCAL_DIR . 'media/images/category', $actual); + $actual = $fileManager->getUploadDir(FileManager::TYPE_CONTENT, FileManager::FILE_TYPE_IMAGES); + $this->assertEquals(THELIA_LOCAL_DIR . 'media/images/content', $actual); + $actual = $fileManager->getUploadDir(FileManager::TYPE_FOLDER, FileManager::FILE_TYPE_IMAGES); + $this->assertEquals(THELIA_LOCAL_DIR . 'media/images/folder', $actual); + $actual = $fileManager->getUploadDir('bad', FileManager::FILE_TYPE_IMAGES); + $this->assertEquals(false, $actual); + + $actual = $fileManager->getUploadDir(FileManager::TYPE_PRODUCT, FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals(THELIA_LOCAL_DIR . 'media/documents/product', $actual); + $actual = $fileManager->getUploadDir(FileManager::TYPE_CATEGORY, FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals(THELIA_LOCAL_DIR . 'media/documents/category', $actual); + $actual = $fileManager->getUploadDir(FileManager::TYPE_CONTENT, FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals(THELIA_LOCAL_DIR . 'media/documents/content', $actual); + $actual = $fileManager->getUploadDir(FileManager::TYPE_FOLDER, FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals(THELIA_LOCAL_DIR . 'media/documents/folder', $actual); + $actual = $fileManager->getUploadDir('bad', FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals(false, $actual); + + $actual = $fileManager->getUploadDir(FileManager::TYPE_FOLDER, 'bad'); + $this->assertEquals(false, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::getRedirectionUrl + */ + public function testGetRedirectionUrl() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $fileManager = new FileManager($stubContainer); + + $actual = $fileManager->getRedirectionUrl(FileManager::TYPE_PRODUCT, 1, FileManager::FILE_TYPE_IMAGES); + $this->assertEquals('/admin/products/update?product_id=1¤t_tab=images', $actual); + $actual = $fileManager->getRedirectionUrl(FileManager::TYPE_CATEGORY, 1, FileManager::FILE_TYPE_IMAGES); + $this->assertEquals('/admin/categories/update?category_id=1¤t_tab=images', $actual); + $actual = $fileManager->getRedirectionUrl(FileManager::TYPE_CONTENT, 1, FileManager::FILE_TYPE_IMAGES); + $this->assertEquals('/admin/content/update/1?current_tab=images', $actual); + $actual = $fileManager->getRedirectionUrl(FileManager::TYPE_FOLDER, 1, FileManager::FILE_TYPE_IMAGES); + $this->assertEquals('/admin/folders/update/1?current_tab=images', $actual); + $actual = $fileManager->getRedirectionUrl('bad', 1, FileManager::FILE_TYPE_IMAGES); + $this->assertEquals(false, $actual); + + $actual = $fileManager->getRedirectionUrl(FileManager::TYPE_PRODUCT, 1, FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals('/admin/products/update?product_id=1¤t_tab=documents', $actual); + $actual = $fileManager->getRedirectionUrl(FileManager::TYPE_CATEGORY, 1, FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals('/admin/categories/update?category_id=1¤t_tab=documents', $actual); + $actual = $fileManager->getRedirectionUrl(FileManager::TYPE_CONTENT, 1, FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals('/admin/content/update/1?current_tab=documents', $actual); + $actual = $fileManager->getRedirectionUrl(FileManager::TYPE_FOLDER, 1, FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals('/admin/folders/update/1?current_tab=documents', $actual); + $actual = $fileManager->getRedirectionUrl('bad', 1, FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals(false, $actual); + + $actual = $fileManager->getRedirectionUrl(FileManager::TYPE_FOLDER, 1, 'bad'); + $this->assertEquals(false, $actual); + } + + + /** + * @covers Thelia\Tools\FileManager::getFormId + */ + public function testGetFormId() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $fileManager = new FileManager($stubContainer); + + $actual = $fileManager->getFormId(FileManager::TYPE_PRODUCT, FileManager::FILE_TYPE_IMAGES); + $this->assertEquals('thelia.admin.product.image.modification', $actual); + $actual = $fileManager->getFormId(FileManager::TYPE_CATEGORY, FileManager::FILE_TYPE_IMAGES); + $this->assertEquals('thelia.admin.category.image.modification', $actual); + $actual = $fileManager->getFormId(FileManager::TYPE_CONTENT, FileManager::FILE_TYPE_IMAGES); + $this->assertEquals('thelia.admin.content.image.modification', $actual); + $actual = $fileManager->getFormId(FileManager::TYPE_FOLDER, FileManager::FILE_TYPE_IMAGES); + $this->assertEquals('thelia.admin.folder.image.modification', $actual); + $actual = $fileManager->getFormId('bad', FileManager::FILE_TYPE_IMAGES); + $this->assertEquals(false, $actual); + + $actual = $fileManager->getFormId(FileManager::TYPE_PRODUCT, FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals('thelia.admin.product.document.modification', $actual); + $actual = $fileManager->getFormId(FileManager::TYPE_CATEGORY, FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals('thelia.admin.category.document.modification', $actual); + $actual = $fileManager->getFormId(FileManager::TYPE_CONTENT, FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals('thelia.admin.content.document.modification', $actual); + $actual = $fileManager->getFormId(FileManager::TYPE_FOLDER, FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals('thelia.admin.folder.document.modification', $actual); + $actual = $fileManager->getFormId('bad', FileManager::FILE_TYPE_DOCUMENTS); + $this->assertEquals(false, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::renameFile + */ + public function testRenameFile() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $stubUploadedFile = $this->getMockBuilder('\Symfony\Component\HttpFoundation\File\UploadedFile') + ->disableOriginalConstructor() + ->getMock(); + $stubUploadedFile->expects($this->any()) + ->method('getClientOriginalExtension') + ->will($this->returnValue('yml')); + $stubUploadedFile->expects($this->any()) + ->method('getClientOriginalName') + ->will($this->returnValue('or1-g_n?al*/&é"filen@me#')); + + + $fileManager = new FileManager($stubContainer); + + $expected = 'or1-g_nalfilenme-1.yml'; + $actual = $fileManager->renameFile(1, $stubUploadedFile); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::renameFile + */ + public function testRenameFileWithoutExtension() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $stubUploadedFile = $this->getMockBuilder('\Symfony\Component\HttpFoundation\File\UploadedFile') + ->disableOriginalConstructor() + ->getMock(); + $stubUploadedFile->expects($this->any()) + ->method('getClientOriginalExtension') + ->will($this->returnValue('')); + $stubUploadedFile->expects($this->any()) + ->method('getClientOriginalName') + ->will($this->returnValue('or1-g_n?al*/&é"filen@me#')); + + + $fileManager = new FileManager($stubContainer); + + $expected = 'or1-g_nalfilenme-1'; + $actual = $fileManager->renameFile(1, $stubUploadedFile); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::isImage + */ + public function testIsImage() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $fileManager = new FileManager($stubContainer); + + $actual = $fileManager->isImage('image/jpeg'); + $this->assertTrue($actual); + $actual = $fileManager->isImage('image/png'); + $this->assertTrue($actual); + $actual = $fileManager->isImage('image/gif'); + $this->assertTrue($actual); + + $actual = $fileManager->isImage('bad'); + $this->assertFalse($actual); + $actual = $fileManager->isImage('image/jpg'); + $this->assertFalse($actual); + $actual = $fileManager->isImage('application/x-msdownload'); + $this->assertFalse($actual); + $actual = $fileManager->isImage('application/x-sh'); + $this->assertFalse($actual); + + } + + + /** + * @covers Thelia\Tools\FileManager::getAvailableTypes + */ + public function testGetAvailableTypes() + { + $expected = array( + FileManager::TYPE_CATEGORY, + FileManager::TYPE_CONTENT, + FileManager::TYPE_FOLDER, + FileManager::TYPE_PRODUCT, + FileManager::TYPE_MODULE, + ); + $actual = FileManager::getAvailableTypes(); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::adminLogAppend + */ + public function testAdminLogAppend() + { + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } + + /** + * @covers Thelia\Tools\FileManager::deleteFile + */ + public function testDeleteFile() + { + // @todo see http://tech.vg.no/2011/03/09/mocking-the-file-system-using-phpunit-and-vfsstream/ + $this->markTestIncomplete( + 'This test has not been implemented yet.' + ); + } +} diff --git a/core/lib/Thelia/Tools/FileManager.php b/core/lib/Thelia/Tools/FileManager.php new file mode 100644 index 000000000..ba5e1d24a --- /dev/null +++ b/core/lib/Thelia/Tools/FileManager.php @@ -0,0 +1,726 @@ +. */ +/* */ +/**********************************************************************************/ +namespace Thelia\Tools; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Thelia\Core\Event\DocumentCreateOrUpdateEvent; +use Thelia\Core\Event\ImageCreateOrUpdateEvent; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\Translation\Translator; +use Thelia\Exception\ImageException; +use Thelia\Form\CategoryDocumentModification; +use Thelia\Form\CategoryImageModification; +use Thelia\Form\ContentDocumentModification; +use Thelia\Form\ContentImageModification; +use Thelia\Form\FolderDocumentModification; +use Thelia\Form\FolderImageModification; +use Thelia\Form\ProductDocumentModification; +use Thelia\Form\ProductImageModification; +use Thelia\Model\AdminLog; +use Thelia\Model\CategoryDocument; +use Thelia\Model\CategoryDocumentQuery; +use Thelia\Model\CategoryImage; +use Thelia\Model\CategoryImageQuery; +use Thelia\Model\CategoryQuery; +use Thelia\Model\ContentDocument; +use Thelia\Model\ContentDocumentQuery; +use Thelia\Model\ContentImage; +use Thelia\Model\ContentImageQuery; +use Thelia\Model\ContentQuery; +use Thelia\Model\Exception\InvalidArgumentException; +use Thelia\Model\FolderDocument; +use Thelia\Model\FolderDocumentQuery; +use Thelia\Model\FolderImage; +use Thelia\Model\FolderImageQuery; +use Thelia\Model\FolderQuery; +use Thelia\Model\ProductDocument; +use Thelia\Model\ProductDocumentQuery; +use Thelia\Model\ProductImage; +use Thelia\Model\ProductImageQuery; +use Thelia\Model\ProductQuery; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/19/13 + * Time: 3:24 PM + * + * File Manager + * + * @package File + * @author Guillaume MOREL + * + */ +class FileManager +{ + CONST TYPE_PRODUCT = 'product'; + CONST TYPE_CATEGORY = 'category'; + CONST TYPE_CONTENT = 'content'; + CONST TYPE_FOLDER = 'folder'; + CONST TYPE_MODULE = 'module'; + + CONST FILE_TYPE_IMAGES = 'images'; + CONST FILE_TYPE_DOCUMENTS = 'documents'; + + /** @var ContainerInterface Service Container */ + protected $container = null; + + /** @var Translator Service Translator */ + protected $translator = null; + + /** + * Constructor + * + * @param ContainerInterface $container Service container + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + $this->translator = $this->container->get('thelia.translator'); + } + + /** + * Copy UploadedFile into the server storage directory + * + * @param int $parentId Parent id + * @param string $parentType Image type + * @param FolderImage|ContentImage|CategoryImage|ProductImage|FolderDocument|ContentDocument|CategoryDocument|ProductDocument $model Model saved + * @param UploadedFile $uploadedFile Ready to be uploaded file + * @param string $fileType File type ex FileManager::FILE_TYPE_IMAGES + * + * @throws \Thelia\Exception\ImageException + * @return UploadedFile + */ + public function copyUploadedFile($parentId, $parentType, $model, $uploadedFile, $fileType) + { + $newUploadedFile = null; + if ($uploadedFile !== null) { + $directory = $this->getUploadDir($parentType, $fileType); + $fileName = $this->renameFile($model->getId(), $uploadedFile); + + $this->adminLogAppend( + $this->translator->trans( + 'Uploading %type% %fileName% to %directory% for parent_id %parentId% (%parentType%)', + array( + '%type%' => $fileType, + '%fileName%' => $uploadedFile->getClientOriginalName(), + '%directory%' => $directory . '/' . $fileName, + '%parentId%' => $parentId, + '%parentType%' => $parentType + ), + 'image' + ) + ); + + $newUploadedFile = $uploadedFile->move($directory, $fileName); + $model->setFile($fileName); + + if (!$model->save()) { + throw new ImageException( + sprintf( + '%s %s (%s) failed to be saved (image file)', + ucfirst($parentType), + $model->getFile(), + $fileType + ) + ); + } + } + + return $newUploadedFile; + } + + /** + * Save image into the database + * + * @param ImageCreateOrUpdateEvent $event Image event + * @param FolderImage|ContentImage|CategoryImage|ProductImage $modelImage Image to save + * + * @return int Nb lines modified + * @throws \Thelia\Exception\ImageException + * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action + */ + public function saveImage(ImageCreateOrUpdateEvent $event, $modelImage) + { + $nbModifiedLines = 0; + + if ($modelImage->getFile() !== null) { + switch ($event->getImageType()) { + case self::TYPE_PRODUCT: + /** @var ProductImage $modelImage */ + $modelImage->setProductId($event->getParentId()); + break; + case self::TYPE_CATEGORY: + /** @var CategoryImage $modelImage */ + $modelImage->setCategoryId($event->getParentId()); + break; + case self::TYPE_CONTENT: + /** @var ContentImage $modelImage */ + $modelImage->setContentId($event->getParentId()); + break; + case self::TYPE_FOLDER: + /** @var FolderImage $modelImage */ + $modelImage->setFolderId($event->getParentId()); + break; + default: + throw new ImageException( + sprintf( + 'Picture parent type is unknown (available types : %s)', + implode( + ',', + self::getAvailableTypes() + ) + ) + ); + } + + $nbModifiedLines = $modelImage->save(); + if (!$nbModifiedLines) { + throw new ImageException( + sprintf( + 'Image %s failed to be saved (image content)', + $modelImage->getFile() + ) + ); + } + } + + return $nbModifiedLines; + } + + /** + * Save document into the database + * + * @param DocumentCreateOrUpdateEvent $event Image event + * @param FolderDocument|ContentDocument|CategoryDocument|ProductDocument $modelDocument Document to save + * + * @throws \Thelia\Model\Exception\InvalidArgumentException + * @return int Nb lines modified + * @todo refactor make all documents using propel inheritance and factorise image behaviour into one single clean action + */ + public function saveDocument(DocumentCreateOrUpdateEvent $event, $modelDocument) + { + $nbModifiedLines = 0; + + if ($modelDocument->getFile() !== null) { + switch ($event->getDocumentType()) { + case self::TYPE_PRODUCT: + /** @var ProductImage $modelImage */ + $modelDocument->setProductId($event->getParentId()); + break; + case self::TYPE_CATEGORY: + /** @var CategoryImage $modelImage */ + $modelDocument->setCategoryId($event->getParentId()); + break; + case self::TYPE_CONTENT: + /** @var ContentImage $modelImage */ + $modelDocument->setContentId($event->getParentId()); + break; + case self::TYPE_FOLDER: + /** @var FolderImage $modelImage */ + $modelDocument->setFolderId($event->getParentId()); + break; + default: + throw new InvalidArgumentException( + sprintf( + 'Document parent type is unknown (available types : %s)', + implode( + ',', + self::getAvailableTypes() + ) + ) + ); + } + + $nbModifiedLines = $modelDocument->save(); + if (!$nbModifiedLines) { + throw new InvalidArgumentException( + sprintf( + 'Document %s failed to be saved (document content)', + $modelDocument->getFile() + ) + ); + } + } + + return $nbModifiedLines; + } + + /** + * Sanitizes a filename replacing whitespace with dashes + * + * Removes special characters that are illegal in filenames on certain + * operating systems and special characters requiring special escaping + * to manipulate at the command line. + * + * @param string $string The filename to be sanitized + * + * @return string The sanitized filename + */ + public function sanitizeFileName($string) + { + return strtolower(preg_replace('/[^a-zA-Z0-9-_\.]/', '', $string)); + } + + /** + * Helper to append a message to the admin log. + * + * @param string $message + */ + public function adminLogAppend($message) + { + AdminLog::append( + $message, + $this->container->get('request'), + $this->container->get('thelia.securityContext')->getAdminUser() + ); + } + + + /** + * Delete image from file storage and database + * + * @param CategoryImage|ProductImage|ContentImage|FolderImage|CategoryDocument|ProductDocument|ContentDocument|FolderDocument $model File being deleted + * @param string $parentType Parent type ex : self::TYPE_PRODUCT + * @param string $fileType File type ex FileManager::FILE_TYPE_DOCUMENTS + * + * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action + */ + public function deleteFile($model, $parentType, $fileType) + { + $url = $this->getUploadDir($parentType, $fileType) . '/' . $model->getFile(); + unlink(str_replace('..', '', $url)); + $model->delete(); + } + + + /** + * Get image model from type + * + * @param string $parentType Parent type ex : self::TYPE_PRODUCT + * + * @return null|\Thelia\Model\CategoryImage|\Thelia\Model\ContentImage|\Thelia\Model\FolderImage|\Thelia\Model\ProductImage + * + * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action + */ + public function getImageModel($parentType) + { + switch ($parentType) { + case self::TYPE_PRODUCT: + $model = new ProductImage(); + break; + case self::TYPE_CATEGORY: + $model = new CategoryImage(); + break; + case self::TYPE_CONTENT: + $model = new ContentImage(); + break; + case self::TYPE_FOLDER: + $model = new FolderImage(); + break; + default: + $model = null; + } + + return $model; + } + + /** + * Get document model from type + * + * @param string $parentType Parent type ex : self::TYPE_PRODUCT + * + * @return null|ProductDocument|CategoryDocument|ContentDocument|FolderDocument + * + * @todo refactor make all documents using propel inheritance and factorise image behaviour into one single clean action + */ + public function getDocumentModel($parentType) + { + switch ($parentType) { + case self::TYPE_PRODUCT: + $model = new ProductDocument(); + break; + case self::TYPE_CATEGORY: + $model = new CategoryDocument(); + break; + case self::TYPE_CONTENT: + $model = new ContentDocument(); + break; + case self::TYPE_FOLDER: + $model = new FolderDocument(); + break; + default: + $model = null; + } + + return $model; + } + + /** + * Get image model query from type + * + * @param string $parentType Parent type ex : self::TYPE_PRODUCT + * + * @return null|\Thelia\Model\CategoryImageQuery|\Thelia\Model\ContentImageQuery|\Thelia\Model\FolderImageQuery|\Thelia\Model\ProductImageQuery + * + * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action + */ + public function getImageModelQuery($parentType) + { + switch ($parentType) { + case self::TYPE_PRODUCT: + $model = new ProductImageQuery(); + break; + case self::TYPE_CATEGORY: + $model = new CategoryImageQuery(); + break; + case self::TYPE_CONTENT: + $model = new ContentImageQuery(); + break; + case self::TYPE_FOLDER: + $model = new FolderImageQuery(); + break; + default: + $model = null; + } + + return $model; + } + + /** + * Get document model query from type + * + * @param string $parentType Parent type ex : self::TYPE_PRODUCT + * + * @return null|ProductDocumentQuery|CategoryDocumentQuery|ContentDocumentQuery|FolderDocumentQuery + * + * @todo refactor make all documents using propel inheritance and factorise image behaviour into one single clean action + */ + public function getDocumentModelQuery($parentType) + { + switch ($parentType) { + case self::TYPE_PRODUCT: + $model = new ProductDocumentQuery(); + break; + case self::TYPE_CATEGORY: + $model = new CategoryDocumentQuery(); + break; + case self::TYPE_CONTENT: + $model = new ContentDocumentQuery(); + break; + case self::TYPE_FOLDER: + $model = new FolderDocumentQuery(); + break; + default: + $model = null; + } + + return $model; + } + + /** + * Get form service id from type + * + * @param string $parentType Parent type ex : self::TYPE_PRODUCT + * @param string $fileType Parent id + * + * @return string + * + * @todo refactor make all documents using propel inheritance and factorise image behaviour into one single clean action + */ + public function getFormId($parentType, $fileType) + { + switch ($fileType) { + case self::FILE_TYPE_IMAGES: + $type = 'image'; + break; + case self::FILE_TYPE_DOCUMENTS: + $type = 'document'; + break; + default: + return false; + } + + switch ($parentType) { + case self::TYPE_PRODUCT: + $formId = 'thelia.admin.product.' . $type . '.modification'; + break; + case self::TYPE_CATEGORY: + $formId = 'thelia.admin.category.' . $type . '.modification'; + break; + case self::TYPE_CONTENT: + $formId = 'thelia.admin.content.' . $type . '.modification'; + break; + case self::TYPE_FOLDER: + $formId = 'thelia.admin.folder.' . $type . '.modification'; + break; + default: + $formId = false; + } + + return $formId; + } + + /** + * Get image parent model from type + * + * @param string $parentType Parent type ex : self::TYPE_PRODUCT + * @param int $parentId Parent Id + * + * @return null|\Thelia\Model\Category|\Thelia\Model\Content|\Thelia\Model\Folder|\Thelia\Model\Product + * + * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action + */ + public function getParentFileModel($parentType, $parentId) + { + switch ($parentType) { + case self::TYPE_PRODUCT: + $model = ProductQuery::create()->findPk($parentId); + break; + case self::TYPE_CATEGORY: + $model = CategoryQuery::create()->findPk($parentId); + break; + case self::TYPE_CONTENT: + $model = ContentQuery::create()->findPk($parentId); + break; + case self::TYPE_FOLDER: + $model = FolderQuery::create()->findPk($parentId); + break; + default: + $model = null; + } + + return $model; + } + + /** + * Get image parent model from type + * + * @param string $parentType Parent type ex : self::TYPE_PRODUCT + * @param Request $request Request service + * + * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action + * @return ProductImageModification|CategoryImageModification|ContentImageModification|FolderImageModification + */ + public function getImageForm($parentType, Request $request) + { + switch ($parentType) { + case self::TYPE_PRODUCT: + $form = new ProductImageModification($request); + break; + case self::TYPE_CATEGORY: + $form = new CategoryImageModification($request); + break; + case self::TYPE_CONTENT: + $form = new ContentImageModification($request); + break; + case self::TYPE_FOLDER: + $form = new FolderImageModification($request); + break; + default: + $form = null; + } + + return $form; + + } + + /** + * Get document parent model from type + * + * @param string $parentType Parent type ex : self::TYPE_PRODUCT + * @param Request $request Request service + * + * @todo refactor make all document using propel inheritance and factorise image behaviour into one single clean action + * @return ProductDocumentModification|CategoryDocumentModification|ContentDocumentModification|FolderDocumentModification + */ + public function getDocumentForm($parentType, Request $request) + { + switch ($parentType) { + case self::TYPE_PRODUCT: + $form = new ProductDocumentModification($request); + break; + case self::TYPE_CATEGORY: + $form = new CategoryDocumentModification($request); + break; + case self::TYPE_CONTENT: + $form = new ContentDocumentModification($request); + break; + case self::TYPE_FOLDER: + $form = new FolderDocumentModification($request); + break; + default: + $form = null; + } + + return $form; + + } + + /** + * Get image upload dir + * + * @param string $parentType Parent type ex FileManager::TYPE_PRODUCT + * @param string $fileType File type ex : self::FILE_TYPE_DOCUMENTS + * + * @return string Uri + */ + public function getUploadDir($parentType, $fileType) + { + if (!in_array($fileType, self::$availableFileType)) { + return false; + } + + switch ($parentType) { + case self::TYPE_PRODUCT: + $uri = THELIA_LOCAL_DIR . 'media/' . $fileType . '/' . self::TYPE_PRODUCT; + break; + case self::TYPE_CATEGORY: + $uri = THELIA_LOCAL_DIR . 'media/' . $fileType . '/' . self::TYPE_CATEGORY; + break; + case self::TYPE_CONTENT: + $uri = THELIA_LOCAL_DIR . 'media/' . $fileType . '/' . self::TYPE_CONTENT; + break; + case self::TYPE_FOLDER: + $uri = THELIA_LOCAL_DIR . 'media/' . $fileType . '/' . self::TYPE_FOLDER; + break; + default: + $uri = false; + } + + return $uri; + + } + + /** + * Deduce image redirecting URL from parent type + * + * @param string $parentType Parent type ex : self::TYPE_PRODUCT + * @param int $parentId Parent id + * @param string $fileType File type ex : self::FILE_TYPE_DOCUMENTS + * + * @return string + */ + public function getRedirectionUrl($parentType, $parentId, $fileType) + { + if (!in_array($fileType, self::$availableFileType)) { + return false; + } + + switch ($parentType) { + case self::TYPE_PRODUCT: + $uri = '/admin/products/update?product_id=' . $parentId . '¤t_tab=' . $fileType; + break; + case self::TYPE_CATEGORY: + $uri = '/admin/categories/update?category_id=' . $parentId . '¤t_tab=' . $fileType; + break; + case self::TYPE_CONTENT: + $uri = '/admin/content/update/' . $parentId . '?current_tab=' . $fileType; + break; + case self::TYPE_FOLDER: + $uri = '/admin/folders/update/' . $parentId . '?current_tab=' . $fileType; + break; + default: + $uri = false; + } + + return $uri; + + } + + /** @var array Available file parent type */ + public static $availableType = array( + self::TYPE_PRODUCT, + self::TYPE_CATEGORY, + self::TYPE_CONTENT, + self::TYPE_FOLDER, + self::TYPE_MODULE + ); + + /** @var array Available file type type */ + public static $availableFileType = array( + self::FILE_TYPE_DOCUMENTS, + self::FILE_TYPE_IMAGES + ); + + /** + * Rename file with image model id + * + * @param int $modelId Model id + * @param UploadedFile $uploadedFile File being saved + * + * @return string + */ + public function renameFile($modelId, $uploadedFile) + { + $extension = $uploadedFile->getClientOriginalExtension(); + if (!empty($extension)) { + $extension = '.' . strtolower($extension); + } + $fileName = $this->sanitizeFileName( + str_replace( + $extension, + '', + $uploadedFile->getClientOriginalName() + ) . '-' . $modelId . $extension + ); + + return $fileName; + } + + /** + * Check if a file is an image + * Check based on mime type + * + * @param string $mimeType File mime type + * + * @return bool + */ + public function isImage($mimeType) + { + $isValid = false; + + $allowedType = array('image/jpeg' , 'image/png' ,'image/gif'); + if (in_array($mimeType, $allowedType)) { + $isValid = true; + } + + return $isValid; + } + + /** + * Return all document and image types + * + * @return array + */ + public static function getAvailableTypes() + { + return array( + self::TYPE_CATEGORY, + self::TYPE_CONTENT, + self::TYPE_FOLDER, + self::TYPE_PRODUCT, + self::TYPE_MODULE, + ); + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Tools/Rest/ResponseRest.php b/core/lib/Thelia/Tools/Rest/ResponseRest.php index 75d511d78..0c9186436 100644 --- a/core/lib/Thelia/Tools/Rest/ResponseRest.php +++ b/core/lib/Thelia/Tools/Rest/ResponseRest.php @@ -26,7 +26,7 @@ class ResponseRest extends Response * Constructor. * * @param array $data Array to be serialized - * @param string $format serialization format, xml or json available + * @param string $format serialization format, text, xml or json available * @param integer $status The response status code * @param array $headers An array of response headers * @@ -38,14 +38,22 @@ class ResponseRest extends Response { parent::__construct('', $status, $headers); - $this->format = $format; - $serializer = $this->getSerializer(); + if ($format == 'text') { + if (isset($data)) { + $this->setContent($data); + } - if (isset($data)) { - $this->setContent($serializer->serialize($data, $this->format)); + $this->headers->set('Content-Type', 'text/plain'); + } else { + $this->format = $format; + $serializer = $this->getSerializer(); + + if (isset($data)) { + $this->setContent($serializer->serialize($data, $this->format)); + } + + $this->headers->set('Content-Type', 'application/' . $this->format); } - - $this->headers->set('Content-Type', 'application/' . $this->format); } /** diff --git a/install/faker.php b/install/faker.php index ad4fe3c05..268cd3abf 100755 --- a/install/faker.php +++ b/install/faker.php @@ -425,6 +425,7 @@ try { $stock->setPromo($faker->randomNumber(0,1)); $stock->setNewness($faker->randomNumber(0,1)); $stock->setWeight($faker->randomFloat(2, 100,10000)); + $stock->setIsDefault($i == 0); $stock->save(); $productPrice = new \Thelia\Model\ProductPrice(); @@ -463,8 +464,7 @@ try { $featureAvId[array_rand($featureAvId, 1)] ); } else { //no av - // @todo uncomment -// $featureProduct->setByDefault($faker->text(10)); + $featureProduct->setFreeTextValue($faker->text(10)); } $featureProduct->save(); diff --git a/install/insert.sql b/install/insert.sql index df252d366..ec4e1b02b 100755 --- a/install/insert.sql +++ b/install/insert.sql @@ -1153,7 +1153,7 @@ INSERT INTO `tax` (`id`, `type`, `serialized_requirements`, `created_at`, `upda INSERT INTO `tax_i18n` (`id`, `locale`, `title`) VALUES (1, 'fr_FR', 'TVA française à 19.6%'), - (1, 'en_US', 'french 19.6% tax'); + (1, 'en_US', 'French 19.6% VAT'); INSERT INTO `tax_rule` (`id`, `is_default`, `created_at`, `updated_at`) VALUES @@ -1162,7 +1162,7 @@ INSERT INTO `tax_rule` (`id`, `is_default`, `created_at`, `updated_at`) INSERT INTO `tax_rule_i18n` (`id`, `locale`, `title`) VALUES (1, 'fr_FR', 'TVA française à 19.6%'), - (1, 'en_US', 'french 19.6% tax'); + (1, 'en_US', 'French 19.6% VAT'); INSERT INTO `tax_rule_country` (`tax_rule_id`, `country_id`, `tax_id`, `position`, `created_at`, `updated_at`) VALUES diff --git a/install/thelia.sql b/install/thelia.sql index 9b4ea4880..d9f29588e 100755 --- a/install/thelia.sql +++ b/install/thelia.sql @@ -389,6 +389,7 @@ CREATE TABLE `attribute_template` `attribute_id` INTEGER NOT NULL, `template_id` INTEGER NOT NULL, `position` INTEGER, + `attribute_templatecol` VARCHAR(45), `created_at` DATETIME, `updated_at` DATETIME, PRIMARY KEY (`id`), @@ -818,7 +819,7 @@ CREATE TABLE `order_product_attribute_combination` `attribute_title` VARCHAR(255) NOT NULL, `attribute_chapo` TEXT, `attribute_description` LONGTEXT, - `attribute_postscriptum` TEXT, + `attribute_postscriptumn` TEXT, `attribute_av_title` VARCHAR(255) NOT NULL, `attribute_av_chapo` TEXT, `attribute_av_description` LONGTEXT, @@ -1589,7 +1590,6 @@ CREATE TABLE `order_product_tax` `title` VARCHAR(255) NOT NULL, `description` LONGTEXT, `amount` FLOAT NOT NULL, - `promo_amount` FLOAT, `created_at` DATETIME, `updated_at` DATETIME, PRIMARY KEY (`id`), diff --git a/local/modules/Cheque/images/cheque.png b/local/modules/Cheque/images/cheque.png old mode 100644 new mode 100755 diff --git a/local/modules/FakeCB/images/mastercard.png b/local/modules/FakeCB/images/mastercard.png old mode 100644 new mode 100755 diff --git a/local/modules/FakeCB/images/visa.png b/local/modules/FakeCB/images/visa.png old mode 100644 new mode 100755 diff --git a/templates/admin/default/ajax/product-attributes-tab.html b/templates/admin/default/ajax/product-attributes-tab.html new file mode 100644 index 000000000..2a62da2e3 --- /dev/null +++ b/templates/admin/default/ajax/product-attributes-tab.html @@ -0,0 +1,248 @@ +{loop name="product_edit" type="product" visible="*" id=$product_id backend_context="1" lang=$edit_language_id} +
+ +
+
+

{* <---- FIXME Lame ! *} + + + + + +
+
+
+

{intl + l="To use features or attributes on this product, please select a product template. You can define product templates in the configuration section of the administration." + tpl_mgmt_url={url path='/admin/configuration/templates'} + } +

+ + + +
+ + + + + +
+
+
+
+ +
+
+ + {* Check if a product template is defined *} + +
+
+ +
+ + + + + {include + file = "includes/inner-form-toolbar.html" + hide_submit_buttons = false + + page_url = "{url path='/admin/products/update' product_id=$ID}" + close_url = "{url path='/admin/categories' category_id=$DEFAULT_CATEGORY}" + } + + {* -- Begin attributes management ------------------------------- *} + +
+
+
+
+

{intl l='Product Attributes'}

+ +

+ {if $TEMPLATE} + {intl + l="You can change template attributes and their positions in the template configuration page." + tpl_mgmt_url={url path='/admin/configuration/templates/update' template_id=$TEMPLATE} + } + {else} + {intl + l="You can change attributes and their positions in the attributes configuration page." + tpl_mgmt_url={url path='/admin/configuration/attributes'} + } + {/if} +

+ +
+ + + + + + + {module_include location='product_attributes_table_header'} + + + + + {loop name="product-attributes" type="attribute" order="manual" product=$product_id backend_context="1" lang="$edit_language_id"} + + + + + + {module_include location='product_features_table_row'} + + {/loop} + + {elseloop rel="product-attributes"} + + + + {/elseloop} + +
{intl l='ID'}{intl l='Attribute Name'}
{$ID}{$TITLE}
+
+ {intl l="This product template does not contains any features"} +
+
+
+
+
+
+
+ + {* -- Begin features management ---------------------------------- *} + +
+
+
+
+

{intl l='Product Features'}

+ +

+ {if $TEMPLATE} + {intl + l="You can change templates features and their positions in the template configuration page." + tpl_mgmt_url={url path='/admin/configuration/templates/update' template_id=$TEMPLATE} + } + {else} + {intl + l="You can change feature and their positions in the features configuration page." + tpl_mgmt_url={url path='/admin/configuration/features'} + } + {/if} +

+ +
+ + + + + + + + {module_include location='product_features_table_header'} + + + + + + {loop name="product-features" type="feature" order="manual" product=$product_id backend_context="1" lang="$edit_language_id"} + + + + + + + + {module_include location='product_features_table_row'} + + + {/loop} + + {elseloop rel="product-features"} + + + + {/elseloop} + +
{intl l='ID'}{intl l='Feature Name'}{intl l='Feature value for this product'}
{$ID}{$TITLE} + {* Multiple values *} + + {ifloop rel="product-features-av"} + + {* load all selected values in an array to speed up things a little *} + + {$selected = array()} + + {loop name="free-text-value" exclude_free_text="true" type="feature_value" product=$product_id feature=$ID backend_context="1" lang="$edit_language_id"} + {$selected[] = $FEATURE_AV_ID} + {/loop} + + {capture "select_options"} + {loop name="product-features-av" type="feature-availability" feature=$ID order="manual" backend_context="1" lang="$edit_language_id"} + + + {$options_count = $LOOP_COUNT} {* LOOP_COUNT is only available inside the loop ! *} + {/loop} + {/capture} + +
+ +
+ + + {intl l='Use Ctrl+click to select more than one value. You can also clear selected values.' id=$ID} + + {/ifloop} + + {* Free text *} + + {elseloop rel="product-features-av"} + {* Get the free text value *} + + {loop name="free-text-value" exclude_feature_availability="1" type="feature_value" product=$product_id feature=$ID backend_context="1" lang="$edit_language_id"} + {$feature_value=$FREE_TEXT_VALUE} + {/loop} + + + {/elseloop} +
+
+ {intl l="This product template does not contains any features"} +
+
+
+
+
+
+
+
+
+
+ +
+{/loop} + + diff --git a/templates/admin/default/ajax/product-related-tab.html b/templates/admin/default/ajax/product-related-tab.html new file mode 100644 index 000000000..3c909b390 --- /dev/null +++ b/templates/admin/default/ajax/product-related-tab.html @@ -0,0 +1,576 @@ +{loop name="product_edit" type="product" visible="*" id=$product_id backend_context="1" lang=$edit_language_id} +
+ + {include + file = "includes/inner-form-toolbar.html" + hide_submit_buttons = true + + page_url = "{url path='/admin/products/update' product_id=$ID}" + close_url = "{url path='/admin/categories' category_id=$DEFAULT_CATEGORY}" + } + +
+ + {* -- Begin related content management ------------------------------ *} + +
+
+
+ +
+ +
+ + + + + + + + + + {module_include location='product_contents_table_header'} + + + + + + + {loop name="assigned_contents" type="associated_content" product="$product_id" backend_context="1" lang="$edit_language_id"} + + + + + + + + {module_include location='product_contents_table_row'} + + + + {/loop} + + {elseloop rel="assigned_contents"} + + + + {/elseloop} + +
{intl l='ID'}{intl l='Content title'}{intl l='Position'}{intl l="Actions"}
{$ID} + {$TITLE} + + {admin_position_block + permission="admin.products.edit" + path={url path='/admin/product/update-content-position' product_id=$product_id current_tab="related"} + url_parameter="content_id" + in_place_edit_class="contentPositionChange" + position=$POSITION + id=$ID + } + +
+ {loop type="auth" name="can_create" roles="ADMIN" permissions="admin.product.content.delete"} + + + + {/loop} +
+
+
+ {intl l="This product contains no contents"} +
+
+
+
+
+ + {* -- End related content management -------------------------------- *} + + {* -- Begin accessories management ---------------------------------- *} + +
+
+
+
+ +

{intl l='Product accessories'}

+

{intl l='Define here this product\'s accessories'}

+ + + + + {ifloop rel="categories"} +
+ + + {intl l='Select a category to get its products'} +
+ +
+
+ + + + +
+ + {intl l='Select a product and click (+) to add it as an accessory'} +
+ +
+
+ {intl l="No available product in this category"} +
+
+ + {/ifloop} + + {elseloop rel="categories"} +
{intl l="No categories found"}
+ {/elseloop} + +
+
+ +
+ + + + + + + + + + {module_include location='product_accessories_table_header'} + + + + + + + {loop name="assigned_accessories" order="accessory" type="accessory" product="$product_id" backend_context="1" lang="$edit_language_id"} + + + + + + + + {module_include location='product_accessories_table_row'} + + + + {/loop} + + {elseloop rel="assigned_accessories"} + + + + {/elseloop} + +
{intl l='ID'}{intl l='Accessory title'}{intl l='Position'}{intl l="Actions"}
{$ID} + {$TITLE} + + {admin_position_block + permission="admin.products.edit" + path={url path='/admin/product/update-accessory-position' product_id=$product_id current_tab="related"} + url_parameter="accessory_id" + in_place_edit_class="accessoryPositionChange" + position=$POSITION + id=$ID + } + +
+ {loop type="auth" name="can_create" roles="ADMIN" permissions="admin.product.accessory.delete"} + + + + {/loop} +
+
+
+ {intl l="This product contains no accessories"} +
+
+
+
+
+ + {* -- End accessories management ------------------------------------ *} + +
+ +
+ + {* -- Begin categories management ----------------------------------- *} + +
+
+
+ +
+ +
+ + + + + + + + {module_include location='product_categories_table_header'} + + + + + + + {loop name="additional_categories" type="category" product=$product_id exclude=$DEFAULT_CATEGORY backend_context="1" lang="$edit_language_id"} + + + + + + {module_include location='product_categories_table_row'} + + + + {/loop} + + {elseloop rel="additional_categories"} + + + + {/elseloop} + +
{intl l='ID'}{intl l='Category title'}{intl l="Actions"}
{$ID} + {$TITLE} + +
+ {loop type="auth" name="can_delete" roles="ADMIN" permissions="admin.product.category.delete"} + + + + {/loop} +
+
+
+ {intl l="This product doesn't belong to any additional category."} +
+
+
+
+
+ {* -- End categories management ------------------------------------- *} +
+ +
+ +{* Delete related content confirmation dialog *} + +{capture "delete_content_dialog"} + + + + + +{/capture} + +{include + file = "includes/generic-confirm-dialog.html" + + dialog_id = "delete_content_dialog" + dialog_title = {intl l="Remove related content"} + dialog_message = {intl l="Do you really want to remove this related content from the product ?"} + + form_action = {url path='/admin/products/content/delete'} + form_content = {$smarty.capture.delete_content_dialog nofilter} +} + +{* Delete accessory confirmation dialog *} + +{capture "delete_accessory_dialog"} + + + + + +{/capture} + +{include + file = "includes/generic-confirm-dialog.html" + + dialog_id = "delete_accessory_dialog" + dialog_title = {intl l="Remove an accessory"} + dialog_message = {intl l="Do you really want to remove this accessory from the product ?"} + + form_action = {url path='/admin/products/accessory/delete'} + form_content = {$smarty.capture.delete_accessory_dialog nofilter} +} + +{* Delete category confirmation dialog *} + +{capture "delete_category_dialog"} + + + + +{/capture} + +{include + file = "includes/generic-confirm-dialog.html" + + dialog_id = "delete_category_dialog" + dialog_title = {intl l="Remove from category"} + dialog_message = {intl l="Do you really want to remove the product from this category ?"} + + form_action = {url path='/admin/products/category/delete'} + form_content = {$smarty.capture.delete_category_dialog nofilter} +} + + +{/loop} \ No newline at end of file diff --git a/templates/admin/default/ajax/template-attribute-list.html b/templates/admin/default/ajax/template-attribute-list.html index a630aaa61..7c0c2fdb9 100644 --- a/templates/admin/default/ajax/template-attribute-list.html +++ b/templates/admin/default/ajax/template-attribute-list.html @@ -1,12 +1,12 @@
{ifloop rel="free_attributes"} -
+
"; + fields = Dropzone.createElement(fieldsString); + if (this.element.tagName !== "FORM") { + form = Dropzone.createElement(""); + form.appendChild(fields); + } else { + this.element.setAttribute("enctype", "multipart/form-data"); + this.element.setAttribute("method", this.options.method); + } + return form != null ? form : fields; + }; + + Dropzone.prototype.getExistingFallback = function() { + var fallback, getFallback, tagName, _i, _len, _ref; + getFallback = function(elements) { + var el, _i, _len; + for (_i = 0, _len = elements.length; _i < _len; _i++) { + el = elements[_i]; + if (/(^| )fallback($| )/.test(el.className)) { + return el; + } + } + }; + _ref = ["div", "form"]; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + tagName = _ref[_i]; + if (fallback = getFallback(this.element.getElementsByTagName(tagName))) { + return fallback; + } + } + }; + + Dropzone.prototype.setupEventListeners = function() { + var elementListeners, event, listener, _i, _len, _ref, _results; + _ref = this.listeners; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + elementListeners = _ref[_i]; + _results.push((function() { + var _ref1, _results1; + _ref1 = elementListeners.events; + _results1 = []; + for (event in _ref1) { + listener = _ref1[event]; + _results1.push(elementListeners.element.addEventListener(event, listener, false)); + } + return _results1; + })()); + } + return _results; + }; + + Dropzone.prototype.removeEventListeners = function() { + var elementListeners, event, listener, _i, _len, _ref, _results; + _ref = this.listeners; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + elementListeners = _ref[_i]; + _results.push((function() { + var _ref1, _results1; + _ref1 = elementListeners.events; + _results1 = []; + for (event in _ref1) { + listener = _ref1[event]; + _results1.push(elementListeners.element.removeEventListener(event, listener, false)); + } + return _results1; + })()); + } + return _results; + }; + + Dropzone.prototype.disable = function() { + var file, _i, _len, _ref, _results; + this.clickableElements.forEach(function(element) { + return element.classList.remove("dz-clickable"); + }); + this.removeEventListeners(); + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + _results.push(this.cancelUpload(file)); + } + return _results; + }; + + Dropzone.prototype.enable = function() { + this.clickableElements.forEach(function(element) { + return element.classList.add("dz-clickable"); + }); + return this.setupEventListeners(); + }; + + Dropzone.prototype.filesize = function(size) { + var string; + if (size >= 100000000000) { + size = size / 100000000000; + string = "TB"; + } else if (size >= 100000000) { + size = size / 100000000; + string = "GB"; + } else if (size >= 100000) { + size = size / 100000; + string = "MB"; + } else if (size >= 100) { + size = size / 100; + string = "KB"; + } else { + size = size * 10; + string = "b"; + } + return "" + (Math.round(size) / 10) + " " + string; + }; + + Dropzone.prototype._updateMaxFilesReachedClass = function() { + if (this.options.maxFiles && this.getAcceptedFiles().length >= this.options.maxFiles) { + return this.element.classList.add("dz-max-files-reached"); + } else { + return this.element.classList.remove("dz-max-files-reached"); + } + }; + + Dropzone.prototype.drop = function(e) { + var files, items; + if (!e.dataTransfer) { + return; + } + this.emit("drop", e); + files = e.dataTransfer.files; + this.emit("selectedfiles", files); + if (files.length) { + items = e.dataTransfer.items; + if (items && items.length && ((items[0].webkitGetAsEntry != null) || (items[0].getAsEntry != null))) { + this.handleItems(items); + } else { + this.handleFiles(files); + } + } + }; + + Dropzone.prototype.handleFiles = function(files) { + var file, _i, _len, _results; + _results = []; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + _results.push(this.addFile(file)); + } + return _results; + }; + + Dropzone.prototype.handleItems = function(items) { + var entry, item, _i, _len; + for (_i = 0, _len = items.length; _i < _len; _i++) { + item = items[_i]; + if (item.webkitGetAsEntry != null) { + entry = item.webkitGetAsEntry(); + if (entry.isFile) { + this.addFile(item.getAsFile()); + } else if (entry.isDirectory) { + this.addDirectory(entry, entry.name); + } + } else { + this.addFile(item.getAsFile()); + } + } + }; + + Dropzone.prototype.accept = function(file, done) { + if (file.size > this.options.maxFilesize * 1024 * 1024) { + return done(this.options.dictFileTooBig.replace("{{filesize}}", Math.round(file.size / 1024 / 10.24) / 100).replace("{{maxFilesize}}", this.options.maxFilesize)); + } else if (!Dropzone.isValidFile(file, this.options.acceptedFiles)) { + return done(this.options.dictInvalidFileType); + } else if (this.options.maxFiles && this.getAcceptedFiles().length >= this.options.maxFiles) { + done(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}", this.options.maxFiles)); + return this.emit("maxfilesexceeded", file); + } else { + return this.options.accept.call(this, file, done); + } + }; + + Dropzone.prototype.addFile = function(file) { + var _this = this; + file.upload = { + progress: 0, + total: file.size, + bytesSent: 0 + }; + this.files.push(file); + file.status = Dropzone.ADDED; + this.emit("addedfile", file); + if (this.options.createImageThumbnails && file.type.match(/image.*/) && file.size <= this.options.maxThumbnailFilesize * 1024 * 1024) { + this.createThumbnail(file); + } + return this.accept(file, function(error) { + if (error) { + file.accepted = false; + return _this._errorProcessing([file], error); + } else { + return _this.enqueueFile(file); + } + }); + }; + + Dropzone.prototype.enqueueFiles = function(files) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + this.enqueueFile(file); + } + return null; + }; + + Dropzone.prototype.enqueueFile = function(file) { + var _this = this; + file.accepted = true; + if (file.status === Dropzone.ADDED) { + file.status = Dropzone.QUEUED; + if (this.options.autoProcessQueue) { + return setTimeout((function() { + return _this.processQueue(); + }), 1); + } + } else { + throw new Error("This file can't be queued because it has already been processed or was rejected."); + } + }; + + Dropzone.prototype.addDirectory = function(entry, path) { + var dirReader, entriesReader, + _this = this; + dirReader = entry.createReader(); + entriesReader = function(entries) { + var _i, _len; + for (_i = 0, _len = entries.length; _i < _len; _i++) { + entry = entries[_i]; + if (entry.isFile) { + entry.file(function(file) { + if (_this.options.ignoreHiddenFiles && file.name.substring(0, 1) === '.') { + return; + } + file.fullPath = "" + path + "/" + file.name; + return _this.addFile(file); + }); + } else if (entry.isDirectory) { + _this.addDirectory(entry, "" + path + "/" + entry.name); + } + } + }; + return dirReader.readEntries(entriesReader, function(error) { + return typeof console !== "undefined" && console !== null ? typeof console.log === "function" ? console.log(error) : void 0 : void 0; + }); + }; + + Dropzone.prototype.removeFile = function(file) { + if (file.status === Dropzone.UPLOADING) { + this.cancelUpload(file); + } + this.files = without(this.files, file); + this.emit("removedfile", file); + if (this.files.length === 0) { + return this.emit("reset"); + } + }; + + Dropzone.prototype.removeAllFiles = function(cancelIfNecessary) { + var file, _i, _len, _ref; + if (cancelIfNecessary == null) { + cancelIfNecessary = false; + } + _ref = this.files.slice(); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.status !== Dropzone.UPLOADING || cancelIfNecessary) { + this.removeFile(file); + } + } + return null; + }; + + Dropzone.prototype.createThumbnail = function(file) { + var fileReader, + _this = this; + fileReader = new FileReader; + fileReader.onload = function() { + var img; + img = new Image; + img.onload = function() { + var canvas, ctx, resizeInfo, thumbnail, _ref, _ref1, _ref2, _ref3; + file.width = img.width; + file.height = img.height; + resizeInfo = _this.options.resize.call(_this, file); + if (resizeInfo.trgWidth == null) { + resizeInfo.trgWidth = _this.options.thumbnailWidth; + } + if (resizeInfo.trgHeight == null) { + resizeInfo.trgHeight = _this.options.thumbnailHeight; + } + canvas = document.createElement("canvas"); + ctx = canvas.getContext("2d"); + canvas.width = resizeInfo.trgWidth; + canvas.height = resizeInfo.trgHeight; + ctx.drawImage(img, (_ref = resizeInfo.srcX) != null ? _ref : 0, (_ref1 = resizeInfo.srcY) != null ? _ref1 : 0, resizeInfo.srcWidth, resizeInfo.srcHeight, (_ref2 = resizeInfo.trgX) != null ? _ref2 : 0, (_ref3 = resizeInfo.trgY) != null ? _ref3 : 0, resizeInfo.trgWidth, resizeInfo.trgHeight); + thumbnail = canvas.toDataURL("image/png"); + return _this.emit("thumbnail", file, thumbnail); + }; + return img.src = fileReader.result; + }; + return fileReader.readAsDataURL(file); + }; + + Dropzone.prototype.processQueue = function() { + var i, parallelUploads, processingLength, queuedFiles; + parallelUploads = this.options.parallelUploads; + processingLength = this.getUploadingFiles().length; + i = processingLength; + if (processingLength >= parallelUploads) { + return; + } + queuedFiles = this.getQueuedFiles(); + if (!(queuedFiles.length > 0)) { + return; + } + if (this.options.uploadMultiple) { + return this.processFiles(queuedFiles.slice(0, parallelUploads - processingLength)); + } else { + while (i < parallelUploads) { + if (!queuedFiles.length) { + return; + } + this.processFile(queuedFiles.shift()); + i++; + } + } + }; + + Dropzone.prototype.processFile = function(file) { + return this.processFiles([file]); + }; + + Dropzone.prototype.processFiles = function(files) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.processing = true; + file.status = Dropzone.UPLOADING; + this.emit("processing", file); + } + if (this.options.uploadMultiple) { + this.emit("processingmultiple", files); + } + return this.uploadFiles(files); + }; + + Dropzone.prototype._getFilesWithXhr = function(xhr) { + var file, files; + return files = (function() { + var _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.xhr === xhr) { + _results.push(file); + } + } + return _results; + }).call(this); + }; + + Dropzone.prototype.cancelUpload = function(file) { + var groupedFile, groupedFiles, _i, _j, _len, _len1, _ref; + if (file.status === Dropzone.UPLOADING) { + groupedFiles = this._getFilesWithXhr(file.xhr); + for (_i = 0, _len = groupedFiles.length; _i < _len; _i++) { + groupedFile = groupedFiles[_i]; + groupedFile.status = Dropzone.CANCELED; + } + file.xhr.abort(); + for (_j = 0, _len1 = groupedFiles.length; _j < _len1; _j++) { + groupedFile = groupedFiles[_j]; + this.emit("canceled", groupedFile); + } + if (this.options.uploadMultiple) { + this.emit("canceledmultiple", groupedFiles); + } + } else if ((_ref = file.status) === Dropzone.ADDED || _ref === Dropzone.QUEUED) { + file.status = Dropzone.CANCELED; + this.emit("canceled", file); + if (this.options.uploadMultiple) { + this.emit("canceledmultiple", [file]); + } + } + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + }; + + Dropzone.prototype.uploadFile = function(file) { + return this.uploadFiles([file]); + }; + + Dropzone.prototype.uploadFiles = function(files) { + var file, formData, handleError, headerName, headerValue, headers, input, inputName, inputType, key, progressObj, response, updateProgress, value, xhr, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, + _this = this; + xhr = new XMLHttpRequest(); + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.xhr = xhr; + } + xhr.open(this.options.method, this.options.url, true); + xhr.withCredentials = !!this.options.withCredentials; + response = null; + handleError = function() { + var _j, _len1, _results; + _results = []; + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + _results.push(_this._errorProcessing(files, response || _this.options.dictResponseError.replace("{{statusCode}}", xhr.status), xhr)); + } + return _results; + }; + updateProgress = function(e) { + var allFilesFinished, progress, _j, _k, _l, _len1, _len2, _len3, _results; + if (e != null) { + progress = 100 * e.loaded / e.total; + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + file.upload = { + progress: progress, + total: e.total, + bytesSent: e.loaded + }; + } + } else { + allFilesFinished = true; + progress = 100; + for (_k = 0, _len2 = files.length; _k < _len2; _k++) { + file = files[_k]; + if (!(file.upload.progress === 100 && file.upload.bytesSent === file.upload.total)) { + allFilesFinished = false; + } + file.upload.progress = progress; + file.upload.bytesSent = file.upload.total; + } + if (allFilesFinished) { + return; + } + } + _results = []; + for (_l = 0, _len3 = files.length; _l < _len3; _l++) { + file = files[_l]; + _results.push(_this.emit("uploadprogress", file, progress, file.upload.bytesSent)); + } + return _results; + }; + xhr.onload = function(e) { + var _ref; + if (files[0].status === Dropzone.CANCELED) { + return; + } + if (xhr.readyState !== 4) { + return; + } + response = xhr.responseText; + if (xhr.getResponseHeader("content-type") && ~xhr.getResponseHeader("content-type").indexOf("application/json")) { + try { + response = JSON.parse(response); + } catch (_error) { + e = _error; + response = "Invalid JSON response from server."; + } + } + updateProgress(); + if (!((200 <= (_ref = xhr.status) && _ref < 300))) { + return handleError(); + } else { + return _this._finished(files, response, e); + } + }; + xhr.onerror = function() { + if (files[0].status === Dropzone.CANCELED) { + return; + } + return handleError(); + }; + progressObj = (_ref = xhr.upload) != null ? _ref : xhr; + progressObj.onprogress = updateProgress; + headers = { + "Accept": "application/json", + "Cache-Control": "no-cache", + "X-Requested-With": "XMLHttpRequest" + }; + if (this.options.headers) { + extend(headers, this.options.headers); + } + for (headerName in headers) { + headerValue = headers[headerName]; + xhr.setRequestHeader(headerName, headerValue); + } + formData = new FormData(); + if (this.options.params) { + _ref1 = this.options.params; + for (key in _ref1) { + value = _ref1[key]; + formData.append(key, value); + } + } + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + this.emit("sending", file, xhr, formData); + } + if (this.options.uploadMultiple) { + this.emit("sendingmultiple", files, xhr, formData); + } + if (this.element.tagName === "FORM") { + _ref2 = this.element.querySelectorAll("input, textarea, select, button"); + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + input = _ref2[_k]; + inputName = input.getAttribute("name"); + inputType = input.getAttribute("type"); + if (!inputType || ((_ref3 = inputType.toLowerCase()) !== "checkbox" && _ref3 !== "radio") || input.checked) { + formData.append(inputName, input.value); + } + } + } + for (_l = 0, _len3 = files.length; _l < _len3; _l++) { + file = files[_l]; + formData.append("" + this.options.paramName + (this.options.uploadMultiple ? "[]" : ""), file, file.name); + } + return xhr.send(formData); + }; + + Dropzone.prototype._finished = function(files, responseText, e) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.status = Dropzone.SUCCESS; + this.emit("success", file, responseText, e); + this.emit("complete", file); + } + if (this.options.uploadMultiple) { + this.emit("successmultiple", files, responseText, e); + this.emit("completemultiple", files); + } + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + }; + + Dropzone.prototype._errorProcessing = function(files, message, xhr) { + var file, _i, _len; + for (_i = 0, _len = files.length; _i < _len; _i++) { + file = files[_i]; + file.status = Dropzone.ERROR; + this.emit("error", file, message, xhr); + this.emit("complete", file); + } + if (this.options.uploadMultiple) { + this.emit("errormultiple", files, message, xhr); + this.emit("completemultiple", files); + } + if (this.options.autoProcessQueue) { + return this.processQueue(); + } + }; + + return Dropzone; + + })(Em); + + Dropzone.version = "3.7.1"; + + Dropzone.options = {}; + + Dropzone.optionsForElement = function(element) { + if (element.id) { + return Dropzone.options[camelize(element.id)]; + } else { + return void 0; + } + }; + + Dropzone.instances = []; + + Dropzone.forElement = function(element) { + if (typeof element === "string") { + element = document.querySelector(element); + } + if ((element != null ? element.dropzone : void 0) == null) { + throw new Error("No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone."); + } + return element.dropzone; + }; + + Dropzone.autoDiscover = true; + + Dropzone.discover = function() { + var checkElements, dropzone, dropzones, _i, _len, _results; + if (document.querySelectorAll) { + dropzones = document.querySelectorAll(".dropzone"); + } else { + dropzones = []; + checkElements = function(elements) { + var el, _i, _len, _results; + _results = []; + for (_i = 0, _len = elements.length; _i < _len; _i++) { + el = elements[_i]; + if (/(^| )dropzone($| )/.test(el.className)) { + _results.push(dropzones.push(el)); + } else { + _results.push(void 0); + } + } + return _results; + }; + checkElements(document.getElementsByTagName("div")); + checkElements(document.getElementsByTagName("form")); + } + _results = []; + for (_i = 0, _len = dropzones.length; _i < _len; _i++) { + dropzone = dropzones[_i]; + if (Dropzone.optionsForElement(dropzone) !== false) { + _results.push(new Dropzone(dropzone)); + } else { + _results.push(void 0); + } + } + return _results; + }; + + Dropzone.blacklistedBrowsers = [/opera.*Macintosh.*version\/12/i]; + + Dropzone.isBrowserSupported = function() { + var capableBrowser, regex, _i, _len, _ref; + capableBrowser = true; + if (window.File && window.FileReader && window.FileList && window.Blob && window.FormData && document.querySelector) { + if (!("classList" in document.createElement("a"))) { + capableBrowser = false; + } else { + _ref = Dropzone.blacklistedBrowsers; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + regex = _ref[_i]; + if (regex.test(navigator.userAgent)) { + capableBrowser = false; + continue; + } + } + } + } else { + capableBrowser = false; + } + return capableBrowser; + }; + + without = function(list, rejectedItem) { + var item, _i, _len, _results; + _results = []; + for (_i = 0, _len = list.length; _i < _len; _i++) { + item = list[_i]; + if (item !== rejectedItem) { + _results.push(item); + } + } + return _results; + }; + + camelize = function(str) { + return str.replace(/[\-_](\w)/g, function(match) { + return match[1].toUpperCase(); + }); + }; + + Dropzone.createElement = function(string) { + var div; + div = document.createElement("div"); + div.innerHTML = string; + return div.childNodes[0]; + }; + + Dropzone.elementInside = function(element, container) { + if (element === container) { + return true; + } + while (element = element.parentNode) { + if (element === container) { + return true; + } + } + return false; + }; + + Dropzone.getElement = function(el, name) { + var element; + if (typeof el === "string") { + element = document.querySelector(el); + } else if (el.nodeType != null) { + element = el; + } + if (element == null) { + throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector or a plain HTML element."); + } + return element; + }; + + Dropzone.getElements = function(els, name) { + var e, el, elements, _i, _j, _len, _len1, _ref; + if (els instanceof Array) { + elements = []; + try { + for (_i = 0, _len = els.length; _i < _len; _i++) { + el = els[_i]; + elements.push(this.getElement(el, name)); + } + } catch (_error) { + e = _error; + elements = null; + } + } else if (typeof els === "string") { + elements = []; + _ref = document.querySelectorAll(els); + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + el = _ref[_j]; + elements.push(el); + } + } else if (els.nodeType != null) { + elements = [els]; + } + if (!((elements != null) && elements.length)) { + throw new Error("Invalid `" + name + "` option provided. Please provide a CSS selector, a plain HTML element or a list of those."); + } + return elements; + }; + + Dropzone.confirm = function(question, accepted, rejected) { + if (window.confirm(question)) { + return accepted(); + } else if (rejected != null) { + return rejected(); + } + }; + + Dropzone.isValidFile = function(file, acceptedFiles) { + var baseMimeType, mimeType, validType, _i, _len; + if (!acceptedFiles) { + return true; + } + acceptedFiles = acceptedFiles.split(","); + mimeType = file.type; + baseMimeType = mimeType.replace(/\/.*$/, ""); + for (_i = 0, _len = acceptedFiles.length; _i < _len; _i++) { + validType = acceptedFiles[_i]; + validType = validType.trim(); + if (validType.charAt(0) === ".") { + if (file.name.indexOf(validType, file.name.length - validType.length) !== -1) { + return true; + } + } else if (/\/\*$/.test(validType)) { + if (baseMimeType === validType.replace(/\/.*$/, "")) { + return true; + } + } else { + if (mimeType === validType) { + return true; + } + } + } + return false; + }; + + if (typeof jQuery !== "undefined" && jQuery !== null) { + jQuery.fn.dropzone = function(options) { + return this.each(function() { + return new Dropzone(this, options); + }); + }; + } + + if (typeof module !== "undefined" && module !== null) { + module.exports = Dropzone; + } else { + window.Dropzone = Dropzone; + } + + Dropzone.ADDED = "added"; + + Dropzone.QUEUED = "queued"; + + Dropzone.ACCEPTED = Dropzone.QUEUED; + + Dropzone.UPLOADING = "uploading"; + + Dropzone.PROCESSING = Dropzone.UPLOADING; + + Dropzone.CANCELED = "canceled"; + + Dropzone.ERROR = "error"; + + Dropzone.SUCCESS = "success"; + + /* + # contentloaded.js + # + # Author: Diego Perini (diego.perini at gmail.com) + # Summary: cross-browser wrapper for DOMContentLoaded + # Updated: 20101020 + # License: MIT + # Version: 1.2 + # + # URL: + # http://javascript.nwbox.com/ContentLoaded/ + # http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE + */ + + + contentLoaded = function(win, fn) { + var add, doc, done, init, poll, pre, rem, root, top; + done = false; + top = true; + doc = win.document; + root = doc.documentElement; + add = (doc.addEventListener ? "addEventListener" : "attachEvent"); + rem = (doc.addEventListener ? "removeEventListener" : "detachEvent"); + pre = (doc.addEventListener ? "" : "on"); + init = function(e) { + if (e.type === "readystatechange" && doc.readyState !== "complete") { + return; + } + (e.type === "load" ? win : doc)[rem](pre + e.type, init, false); + if (!done && (done = true)) { + return fn.call(win, e.type || e); + } + }; + poll = function() { + var e; + try { + root.doScroll("left"); + } catch (_error) { + e = _error; + setTimeout(poll, 50); + return; + } + return init("poll"); + }; + if (doc.readyState !== "complete") { + if (doc.createEventObject && root.doScroll) { + try { + top = !win.frameElement; + } catch (_error) {} + if (top) { + poll(); + } + } + doc[add](pre + "DOMContentLoaded", init, false); + doc[add](pre + "readystatechange", init, false); + return win[add](pre + "load", init, false); + } + }; + + Dropzone._autoDiscoverFunction = function() { + if (Dropzone.autoDiscover) { + return Dropzone.discover(); + } + }; + + contentLoaded(window, Dropzone._autoDiscoverFunction); + + }).call(this); + + }); + require.alias("component-emitter/index.js", "dropzone/deps/emitter/index.js"); + require.alias("component-emitter/index.js", "emitter/index.js"); + if (typeof exports == "object") { + module.exports = require("dropzone"); + } else if (typeof define == "function" && define.amd) { + define(function(){ return require("dropzone"); }); + } else { + this["Dropzone"] = require("dropzone"); + }})(); \ No newline at end of file diff --git a/templates/admin/default/assets/js/image-upload.js b/templates/admin/default/assets/js/image-upload.js new file mode 100644 index 000000000..c1414c73b --- /dev/null +++ b/templates/admin/default/assets/js/image-upload.js @@ -0,0 +1,102 @@ +$(function($){ + // Manage picture upload + $.imageUploadManager = {}; + + Dropzone.autoDiscover = false; + + + + // Remove image on click + $.imageUploadManager.initImageDropZone = function() { + $.imageUploadManager.onClickDeleteImage(); + + var imageDropzone = new Dropzone("#images-dropzone", { + dictDefaultMessage : $('.btn-browse').html(), + uploadMultiple: false, + maxFilesize: 8, + acceptedFiles: 'image/png, image/gif, image/jpeg' + }); + + var totalFiles = 0, + completedFiles = 0; + + imageDropzone.on("addedfile", function(file){ + totalFiles += 1; + + if(totalFiles == 1){ + $('.dz-message').hide(); + } + }); + + imageDropzone.on("complete", function(file){ + completedFiles += 1; + + if (completedFiles === totalFiles){ + $('.dz-message').slideDown(); + } + }); + + imageDropzone.on("success", function(file) { + imageDropzone.removeFile(file); + $.imageUploadManager.updateImageListAjax(); + $.imageUploadManager.onClickDeleteImage(); + }); + + + + }; + + // Update picture list via AJAX call + $.imageUploadManager.updateImageListAjax = function() { + var $imageListArea = $(".image-manager .existing-image"); + $imageListArea.html('
'); + $.ajax({ + type: "POST", + url: imageListUrl, + statusCode: { + 404: function() { + $imageListArea.html( + imageListErrorMessage + ); + } + } + }).done(function(data) { + $imageListArea.html( + data + ); + $.imageUploadManager.onClickDeleteImage(); + }); + }; + + // Remove image on click + $.imageUploadManager.onClickDeleteImage = function() { + $('.image-manager .image-delete-btn').on('click', function (e) { + e.preventDefault(); + var $this = $(this); + var $parent = $this.parent(); + var $greatParent = $parent.parent(); + + $greatParent.append('
'); + $greatParent.find('.btn-group').remove(); + var $url = $this.attr("href"); + var errorMessage = $this.attr("data-error-message"); + $.ajax({ + type: "POST", + url: $url, + statusCode: { + 404: function() { + $(".image-manager .message").html( + errorMessage + ); + } + } + }).done(function(data) { + $greatParent.remove(); + $(".image-manager .message").html( + data + ); + }); + return false; + }); + }; +}); diff --git a/templates/admin/default/assets/less/thelia/dropzone.less b/templates/admin/default/assets/less/thelia/dropzone.less new file mode 100644 index 000000000..68d483655 --- /dev/null +++ b/templates/admin/default/assets/less/thelia/dropzone.less @@ -0,0 +1,160 @@ +.dropzone{ + cursor: pointer; + border: 4px dashed @gray-lighter; + padding: 70px; + margin: 20px 0; + + &.dz-drag-hover{ + border-color: @brand-primary; + } + + .dz-message { + text-align: center; + + span{ + font-size: @font-size-large; + display: block; + color: @gray; + + span{ + display: block; + font-weight: bold; + margin: 10px 0; + font-size: @font-size-small; + } + + button{ + span{ + display: inline-block; + font-size: @font-size-base; + margin: 0; + color: inherit; + } + } + } + } + + .dz-error{ + .alert(); + .alert-danger(); + margin: 10px 0; + } + + .dz-preview, + .dropzone-previews .dz-preview { + background: rgba(255,255,255,0.8); + position: relative; + display: inline-block; + margin: 17px; + vertical-align: top; + border: 1px solid #acacac; + padding: 6px 6px 6px 6px; + } + .dz-preview.dz-file-preview [data-dz-thumbnail], + .dropzone-previews .dz-preview.dz-file-preview [data-dz-thumbnail] { + display: none; + } + .dz-preview .dz-details, + .dropzone-previews .dz-preview .dz-details { + width: 100px; + height: 100px; + position: relative; + background: #ebebeb; + padding: 5px; + margin-bottom: 22px; + } + .dz-preview .dz-details .dz-filename, + .dropzone-previews .dz-preview .dz-details .dz-filename { + overflow: hidden; + height: 100%; + } + .dz-preview .dz-details img, + .dropzone-previews .dz-preview .dz-details img { + position: absolute; + top: 0; + left: 0; + width: 100px; + height: 100px; + } + .dz-preview .dz-details .dz-size, + .dropzone-previews .dz-preview .dz-details .dz-size { + position: absolute; + bottom: -28px; + left: 3px; + height: 28px; + line-height: 28px; + } + .dz-preview.dz-error .dz-error-mark, + .dropzone-previews .dz-preview.dz-error .dz-error-mark { + display: block; + } + .dz-preview.dz-success .dz-success-mark, + .dropzone-previews .dz-preview.dz-success .dz-success-mark { + display: block; + } + .dz-preview:hover .dz-details img, + .dropzone-previews .dz-preview:hover .dz-details img { + display: none; + } + .dz-preview .dz-success-mark, + .dropzone-previews .dz-preview .dz-success-mark, + .dz-preview .dz-error-mark, + .dropzone-previews .dz-preview .dz-error-mark { + display: none; + position: absolute; + width: 40px; + height: 40px; + font-size: 30px; + text-align: center; + right: -10px; + top: -10px; + } + .dz-preview .dz-success-mark, + .dropzone-previews .dz-preview .dz-success-mark { + color: #8cc657; + } + .dz-preview .dz-error-mark, + .dropzone-previews .dz-preview .dz-error-mark { + color: #ee162d; + } + .dz-preview .dz-progress, + .dropzone-previews .dz-preview .dz-progress { + position: absolute; + top: 100px; + left: 6px; + right: 6px; + height: 6px; + background: #d7d7d7; + display: none; + } + .dz-preview .dz-progress .dz-upload, + .dropzone-previews .dz-preview .dz-progress .dz-upload { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 0%; + background-color: #8cc657; + } + .dz-preview.dz-processing .dz-progress, + .dropzone-previews .dz-preview.dz-processing .dz-progress { + display: block; + } + .dz-preview .dz-error-message, + .dropzone-previews .dz-preview .dz-error-message { + display: none; + position: absolute; + top: -5px; + left: -20px; + background: rgba(245,245,245,0.8); + padding: 8px 10px; + color: #800; + min-width: 140px; + max-width: 500px; + z-index: 500; + } + .dz-preview:hover.dz-error .dz-error-message, + .dropzone-previews .dz-preview:hover.dz-error .dz-error-message { + display: block; + } +} \ No newline at end of file diff --git a/templates/admin/default/assets/less/thelia/thelia.less b/templates/admin/default/assets/less/thelia/thelia.less index 822130a5c..1b898052b 100644 --- a/templates/admin/default/assets/less/thelia/thelia.less +++ b/templates/admin/default/assets/less/thelia/thelia.less @@ -14,6 +14,7 @@ @import "bootstrap-switch.less"; @import "bootstrap-select.less"; @import "jqplot.less"; +@import "dropzone.less"; // -- Base styling ------------------------------------------------------------ @@ -226,6 +227,14 @@ } } +.tab-content { + // Center loading indicator + .loading { + margin: 8em auto; + text-align: center; + } +} + // The overall form container .form-container { @@ -275,5 +284,33 @@ .loading{ background: url("@{imgDir}/ajax-loader.gif") no-repeat; height: 30px; - width: 30px; -} \ No newline at end of file + display: inline-block; + line-height: 30px; + padding-left: 40px; + width: auto; +} + + +.existing-image .col-sm-6{ + position: relative; + margin-bottom: 30px; + + .btn-group{ + position: absolute; + bottom: 5px; + right: 20px; + } + + .loading{ + position: absolute; + bottom: 5px; + right: 20px; + display: block; + line-height: 1; + padding: 0; + margin: 0 auto; + z-index: 2; + width: 30px; + height: 30px; + } +} diff --git a/templates/admin/default/attribute-edit.html b/templates/admin/default/attribute-edit.html index 676a99baa..d15703910 100644 --- a/templates/admin/default/attribute-edit.html +++ b/templates/admin/default/attribute-edit.html @@ -303,7 +303,7 @@ placement : 'left', success : function(response, newValue) { // The URL template - var url = "{url path='/admin/configuration/attributes-av/update-position' attributeav_id='__ID__' position='__POS__' attribute_id=$attribute_id}"; + var url = "{url noamp='1' path='/admin/configuration/attributes-av/update-position' attributeav_id='__ID__' position='__POS__' attribute_id=$attribute_id}"; // Perform subtitutions url = url.replace('__ID__', $(this).data('id')).replace('__POS__', newValue); diff --git a/templates/admin/default/attributes.html b/templates/admin/default/attributes.html index c207d02fa..dd2572a2b 100644 --- a/templates/admin/default/attributes.html +++ b/templates/admin/default/attributes.html @@ -312,7 +312,7 @@ placement : 'left', success : function(response, newValue) { // The URL template - var url = "{url path='/admin/configuration/attributes/update-position' attribute_id='__ID__' position='__POS__'}"; + var url = "{url noamp='1' path='/admin/configuration/attributes/update-position' attribute_id='__ID__' position='__POS__'}"; // Perform subtitutions url = url.replace('__ID__', $(this).data('id')) diff --git a/templates/admin/default/categories.html b/templates/admin/default/categories.html index 990cafd56..21161a386 100755 --- a/templates/admin/default/categories.html +++ b/templates/admin/default/categories.html @@ -256,7 +256,7 @@ } -   + {intl l="Actions"} @@ -303,7 +303,7 @@ } - +
{loop type="auth" name="can_change" roles="ADMIN" permissions="admin.product.edit"} @@ -445,7 +445,7 @@ {form_field form=$form field='title'}
- {loop type="lang" name="default-lang" default_only="1"} + {loop type="lang" name="default-lang" default_only="1" backend_context="1"}
$TITLE @@ -463,6 +463,64 @@
{/form_field} + {form_field form=$form field='price'} +
+ + {loop type="currency" name="default-currency" default_only="1" backend_context="1"} + +
+
+
+ + {$SYMBOL} +
+
+
+ +
{intl l='Enter here the product price in the default currency (%title)' title=$NAME}
+ + {form_field form=$form field='currency'} + + {/form_field} + + {/loop} +
+ {/form_field} + + {form_field form=$form field='tax_rule'} +
+ +
+ +
+ +
{intl l='Select here the tax applicable to this product'}
+ +
+ {/form_field} + + {form_field form=$form field='weight'} +
+ + +
+
+
+ + {intl l="Kg"} +
+
+
+ +
{intl l='Enter here the product weight, in Kilogrammes'}
+
+ {/form_field} + {form_field form=$form field='visible'}
@@ -604,7 +662,7 @@ placement : 'left', success : function(response, newValue) { // The URL template - var url = "{url path='/admin/categories/update-position' category_id='__ID__' position='__POS__'}"; + var url = "{url noamp='1' path='/admin/categories/update-position' category_id='__ID__' position='__POS__'}"; // Perform subtitutions url = url.replace('__ID__', $(this).data('id')) @@ -623,7 +681,7 @@ placement : 'left', success : function(response, newValue) { // The URL template - var url = "{url path='/admin/products/update-position' product_id='__ID__' position='__POS__'}"; + var url = "{url noamp='1' path='/admin/products/update-position' product_id='__ID__' position='__POS__'}"; // Perform subtitutions url = url.replace('__ID__', $(this).data('id')) diff --git a/templates/admin/default/category-edit.html b/templates/admin/default/category-edit.html index b974a4564..f8b564822 100755 --- a/templates/admin/default/category-edit.html +++ b/templates/admin/default/category-edit.html @@ -163,7 +163,7 @@
- + @@ -256,9 +256,11 @@
-
+ {include file='includes/image-upload-form.html' imageType='category' parentId=$category_id} +
+ {include file='includes/document-upload-form.html' documentType='category' parentId=$category_id}
@@ -296,6 +298,17 @@ {/block} {block name="javascript-initialization"} + {javascripts file='assets/js/dropzone.js'} + + {/javascripts} + {javascripts file='assets/js/image-upload.js'} + + {/javascripts} + {javascripts file='assets/js/document-upload.js'} + + {/javascripts} + + - - + {/javascripts} + {javascripts file='assets/js/image-upload.js'} + + {/javascripts} + {javascripts file='assets/js/document-upload.js'} + + {/javascripts} + + {/javascripts} + + + + +{/block} \ No newline at end of file diff --git a/templates/admin/default/feature-edit.html b/templates/admin/default/feature-edit.html index a21c63f61..25c2d6017 100644 --- a/templates/admin/default/feature-edit.html +++ b/templates/admin/default/feature-edit.html @@ -1,6 +1,6 @@ {extends file="admin-layout.tpl"} -{block name="page-title"}{intl l='Edit an feature'}{/block} +{block name="page-title"}{intl l='Edit a feature'}{/block} {block name="check-permissions"}admin.configuration.features.edit{/block} @@ -75,9 +75,9 @@

- {intl l="Enter here all possible feature values."} + {intl l="Enter here all possible feature values. To get a free text feature in product forms, don't add any value."}
- +
@@ -122,7 +122,7 @@ - {loop name="list" type="feature_availability" feature=$feature_id backend_context="1" lang=$edit_language_id order=$featureav_order} + {loop name="list" type="feature-availability" feature=$feature_id backend_context="1" lang=$edit_language_id order=$featureav_order} @@ -303,7 +303,7 @@ placement : 'left', success : function(response, newValue) { // The URL template - var url = "{url path='/admin/configuration/features-av/update-position' featureav_id='__ID__' position='__POS__' feature_id=$feature_id}"; + var url = "{url noamp='1' path='/admin/configuration/features-av/update-position' featureav_id='__ID__' position='__POS__' feature_id=$feature_id}"; // Perform subtitutions url = url.replace('__ID__', $(this).data('id')).replace('__POS__', newValue); diff --git a/templates/admin/default/features.html b/templates/admin/default/features.html index 683374aa8..f58a19bc2 100644 --- a/templates/admin/default/features.html +++ b/templates/admin/default/features.html @@ -312,7 +312,7 @@ placement : 'left', success : function(response, newValue) { // The URL template - var url = "{url path='/admin/configuration/features/update-position' feature_id='__ID__' position='__POS__'}"; + var url = "{url noamp='1' path='/admin/configuration/features/update-position' feature_id='__ID__' position='__POS__'}"; // Perform subtitutions url = url.replace('__ID__', $(this).data('id')) diff --git a/templates/admin/default/folder-edit.html b/templates/admin/default/folder-edit.html index 1821f3bcc..bac087096 100644 --- a/templates/admin/default/folder-edit.html +++ b/templates/admin/default/folder-edit.html @@ -161,7 +161,7 @@
- + @@ -246,9 +246,11 @@
+ {include file='includes/image-upload-form.html' imageType='folder' parentId=$folder_id}
+ {include file='includes/document-upload-form.html' documentType='folder' parentId=$folder_id}
@@ -286,59 +288,70 @@ {/block} {block name="javascript-initialization"} - + {/javascripts} + {javascripts file='assets/js/image-upload.js'} + + {/javascripts} + {javascripts file='assets/js/document-upload.js'} + + {/javascripts} -$(function() { + + {/block} \ No newline at end of file diff --git a/templates/admin/default/folders.html b/templates/admin/default/folders.html index d4c3cea94..99c811311 100644 --- a/templates/admin/default/folders.html +++ b/templates/admin/default/folders.html @@ -7,8 +7,8 @@ {block name="main-content"}
-
- +
+ {* include file="includes/folder-breadcrumb.html" *} {module_include location='folders_top'} @@ -20,7 +20,7 @@
{$ID}
{module_include location='folder_list_caption'} {loop type="auth" name="can_create" roles="ADMIN" permissions="admin.folders.create"} @@ -50,7 +45,7 @@ current_order=$folder_order order='id' reverse_order='id_reverse' - path={url path='/admin/folders' parent=$parent} + path={url path='/admin/folders' id_folder=$folder_id} request_parameter_name='folder_order' label="{intl l='ID'}" } @@ -63,7 +58,7 @@ current_order=$folder_order order='alpha' reverse_order='alpha_reverse' - path={url path='/admin/folders' parent=$parent} + path={url path='/admin/folders' id_folder=$folder_id} request_parameter_name='folder_order' label="{intl l='Folder title'}" } @@ -76,7 +71,7 @@ current_order=$folder_order order='visible' reverse_order='visible_reverse' - path={url path='/admin/folders' parent=$parent} + path={url path='/admin/folders' id_folder=$folder_id} request_parameter_name='folder_order' label="{intl l='Online'}" } @@ -87,7 +82,7 @@ current_order=$folder_order order='manual' reverse_order='manual_reverse' - path={url path='/admin/folders' parent=$parent} + path={url path='/admin/folders' id_folder=$folder_id} request_parameter_name='folder_order' label="{intl l='Position'}" } @@ -98,18 +93,18 @@ - {loop name="folder_list" type="folder" visible="*" parent=$parent order=$folder_order backend_context="1" lang=$lang_id} + {loop name="folder_list" type="folder" visible="*" parent=$folder_id order=$folder_order backend_context="1" lang=$lang_id} @@ -143,10 +138,10 @@
{* display parent folder name, and get current folder ID *} - {loop name="folder_title" type="folder" visible="*" id=$parent} + {loop name="folder_title" type="folder" visible="*" id=$folder_id} {intl l="Folders in %fold" fold=$TITLE} {$fold_id = $ID} {/loop} @@ -28,11 +28,6 @@ {intl l="Top level folders"} {/elseloop} -
- - {$TITLE} - -
{$ID} {loop type="image" name="folder_image" source="folder" source_id="$ID" limit="1" width="50" height="50" resize_mode="crop" backend_context="1"} - {$TITLE} + {$TITLE} {/loop} - + {$TITLE}
- + {loop type="auth" name="can_change" roles="ADMIN" permissions="admin.folders.edit"} - + {/loop} {loop type="auth" name="can_delete" roles="ADMIN" permissions="admin.folders.delete"} @@ -191,7 +186,7 @@ + @@ -246,7 +241,7 @@ current_order=$content_order order='manual' reverse_order='manual_reverse' - path={url path='/admin/folders' parent=$parent target='contents'} + path={url path='/admin/folders' id_folder=$folder_id target='contents'} label="{intl l='Position'}" } @@ -256,13 +251,13 @@ - {loop name="content_list" type="content" visible="*" folder_default=$parent order=$content_order} + {loop name="content_list" type="content" visible="*" folder_default=$folder_id order=$content_order}
{* display parent folder name *} - {loop name="folder_title" type="folder" visible="*" id=$parent} + {loop name="folder_title" type="folder" visible="*" id=$folder_id} {intl l="Contents in %fold" fold=$TITLE} {/loop} @@ -214,18 +209,18 @@ current_order=$content_order order='id' reverse_order='id_reverse' - path={url path='/admin/folders' parent=$parent target='contents'} + path={url path='/admin/folders' id_folder=$folder_id target='contents'} label="{intl l='ID'}" } -
   {admin_sortable_header current_order=$content_order order='alpha' reverse_order='alpha_reverse' - path={url path='/admin/folders' parent=$parent target='contents'} + path={url path='/admin/folders' id_folder=$folder_id target='contents'} label="{intl l='Content title'}" } @@ -236,7 +231,7 @@ current_order=$content_order order='visible' reverse_order='visible_reverse' - path={url path='/admin/folders' parent=$parent target='contents'} + path={url path='/admin/folders' id_folder=$folder_id target='contents'} label="{intl l='Online'}" }
{$ID} {loop type="image" name="folder_image" source="content" source_id="$ID" limit="1" width="50" height="50" resize_mode="crop" backend_context="1"} - + {$TITLE} {/loop} @@ -344,11 +339,11 @@ {form_field form=$form field='success_url'} {* on success, redirect to the edition page, _ID_ is replaced with the created object ID, see controller *} - + {/form_field} {form_field form=$form field='parent'} - + {/form_field} {form_field form=$form field='title'} @@ -412,16 +407,16 @@ {form_hidden_fields form=$form} {* Be sure to get the folder_id, even if the form could not be validated *} - + {form_field form=$form field='success_url'} {* on success, redirect to the edition page, _ID_ is replaced with the created object ID, see controller *} - + {/form_field} {form_field form=$form field='default_folder'} - - {/form_field} + + {/form_field} {form_field form=$form field='title'}
@@ -468,7 +463,7 @@ dialog_ok_label = {intl l="Create this content"} - form_action = {url path='/admin/content/create'} + form_action = {url path='/admin/contents/create'} form_enctype = {form_enctype form=$form} form_error_message = $form_error_message } @@ -498,7 +493,7 @@ {capture "content_delete_dialog"} - + {module_include location='content_delete_form'} @@ -585,7 +580,7 @@ placement : 'left', success : function(response, newValue) { // The URL template - var url = "{url path='/admin/folders/update-position' folder_id='__ID__' position='__POS__'}"; + var url = "{url noamp='1' path='/admin/folders/update-position' folder_id='__ID__' position='__POS__'}"; // Perform subtitutions url = url.replace('__ID__', $(this).data('id')) @@ -604,7 +599,7 @@ placement : 'left', success : function(response, newValue) { // The URL template - var url = "{url path='/admin/contents/update-position' content_id='__ID__' position='__POS__'}"; + var url = "{url noamp='1' path='/admin/contents/update-position' content_id='__ID__' position='__POS__'}"; // Perform subtitutions url = url.replace('__ID__', $(this).data('id')) diff --git a/templates/admin/default/image-edit.html b/templates/admin/default/image-edit.html new file mode 100644 index 000000000..034dea7f5 --- /dev/null +++ b/templates/admin/default/image-edit.html @@ -0,0 +1,167 @@ +{extends file="admin-layout.tpl"} + +{block name="page-title"}{intl l='Edit an image'}{/block} + +{block name="check-permissions"}admin.image.edit{/block} + +{block name="main-content"} +
+ +
+ + {loop type="image" name="image_edit" source="{$imageType}" id="{$imageId}" width="580" backend_context="1" lang="$edit_language_id"} + + +
+
+
+ +
+ {intl l="Edit image $TITLE"} +
+ +
+
+ + {form name="$formId"} +
+ +
+
+ {intl l='Back'} + +
+
+ + {form_hidden_fields form=$form} + + {form_field form=$form field='success_url'} + + {/form_field} + + {if $form_error}
{$form_error_message}
{/if} + +

{intl l="Image informations"}

+ +
+
+
+ +

+ + {$TITLE} + +

+
+
+ +
+ {form_field form=$form field='file'} +
+ + +
+ {/form_field} + + {form_field form=$form field='title'} +
+ + +
+ {/form_field} + + {form_field form=$form field='chapo'} +
+ + +
+ {/form_field} + + {form_field form=$form field='postscriptum'} +
+ + +
+ {/form_field} +
+
+
+
+ {form_field form=$form field='description'} +
+ + +
+ {/form_field} +
+
+ +
+
+ {intl l='Back'} + +
+
+ +
+ {/form} + +
+
+ +
+
+ +
+ + {/loop} + + {elseloop rel="image_edit"} +
+
+ +
+ {intl l="Sorry, image ID=$imageId was not found."} +
+
+
+ {/elseloop} + +
+
+{/block} + +{block name="javascript-initialization"} + {javascripts file='assets/js/main.js'} + + {/javascripts} + + + + +{/block} \ No newline at end of file diff --git a/templates/admin/default/includes/document-upload-form.html b/templates/admin/default/includes/document-upload-form.html new file mode 100644 index 000000000..58627b688 --- /dev/null +++ b/templates/admin/default/includes/document-upload-form.html @@ -0,0 +1,36 @@ +{* +A generic document upload form + +Parameters: + documentType = Document type (category, product, folder, content, module) + parentId = Document parent id, ex: category id + +*} + +
+
+
+ + +
+ +
+ {intl l="Drop files to upload"} + Or + +
+ + +
+ +
+ {include file='includes/document-upload-list-ajax.html'} +
+ +
+ + diff --git a/templates/admin/default/includes/document-upload-list-ajax.html b/templates/admin/default/includes/document-upload-list-ajax.html new file mode 100644 index 000000000..1233e06a8 --- /dev/null +++ b/templates/admin/default/includes/document-upload-list-ajax.html @@ -0,0 +1,31 @@ +{* + +A generic document upload form + +Parameters: + documentType = Document type (category, product, folder, content, module) + parentId = Document parent id, ex: category id + +*} + +{ifloop rel="document"} + + {loop type="document" name="document" source="{$documentType}" order="manual-reverse" source_id="{$parentId}"} + + + + {/loop} +
+ {$TITLE} + + + +
+{/ifloop} diff --git a/templates/admin/default/includes/generic-confirm-dialog.html b/templates/admin/default/includes/generic-confirm-dialog.html index be1f4b63d..63f59eca8 100644 --- a/templates/admin/default/includes/generic-confirm-dialog.html +++ b/templates/admin/default/includes/generic-confirm-dialog.html @@ -29,7 +29,7 @@ Parameters: {$dialog_message nofilter}
-
+ {$form_content nofilter} diff --git a/templates/admin/default/includes/generic-create-dialog.html b/templates/admin/default/includes/generic-create-dialog.html index f30825d5a..fe3488535 100755 --- a/templates/admin/default/includes/generic-create-dialog.html +++ b/templates/admin/default/includes/generic-create-dialog.html @@ -12,6 +12,8 @@ A generic modal creation dialog template. Parameters form_action = The form action URL. Form is submitted when OK button is clicked form_enctype = The form encoding form_error_message = The form error message (optional) + + ok_button_id (optionnal) = the id of the OK button *}
\ No newline at end of file diff --git a/templates/admin/default/includes/product-details-tab.html b/templates/admin/default/includes/product-details-tab.html new file mode 100644 index 000000000..13e241e59 --- /dev/null +++ b/templates/admin/default/includes/product-details-tab.html @@ -0,0 +1,389 @@ +
+ + {loop name="product.sales.elements.test" type="product_sale_elements" product=$product_id currency=$edit_currency_id backend_context="1"} + {/loop} + + {elseloop rel="product.sales.elements.test"} + {form name="thelia.admin.product.details.modification"} +
+ + {include + file = "includes/inner-form-toolbar.html" + hide_submit_buttons = false + show_currencies = true + + page_url = "{url path='/admin/products/update' product_id=$ID}" + close_url = "{url path='/admin/categories' category_id=$DEFAULT_CATEGORY}" + } + + {* Be sure to get the product ID, even if the form could not be validated *} + + + + + {form_hidden_fields form=$form} + + {form_field form=$form field='id'} + + {/form_field} + + {form_field form=$form field='success_url'} + + {/form_field} + + {loop type="currency" name="product-currency" id=$edit_currency_id backend_context="1"} + + {$currency_symbol = $SYMBOL} + {$currency_name = $NAME} + + {form_field form=$form field='currency'} + + {/form_field} + {/loop} + +

{intl l='Default pricing'}

+ +

{intl l="The default pricing is used with product that do not have any combinations. It is also used for product with combinations which share the same pricing information"}

+ +
+ + {* -- Pricing ------------------------------------------------------- *} + +
+
+

{intl l='Pricing'}

+ +

+ + {form_field form=$form field='price'} +
+ + +
+ + {$currency_symbol} +
+
+ {/form_field} + + {form_field form=$form field='tax_rule'} +
+ +
+ +
+ +
+ {/form_field} + + {form_field form=$form field='price_with_tax'} +
+ +
+ + {$currency_symbol} +
+
+ {/form_field} + + {module_include location='product_details_pricing_form'} +
+
+ + + {* -- Promotion ------------------------------------------------- *} + +
+
+

{intl l='Promotion'}

+ + {form_field form=$form field='sale_price'} +
+ + +
+ + {$currency_symbol} +
+
+ {/form_field} + + {form_field form=$form field='onsale'} +
+
+ +
+
+ {/form_field} + + {form_field form=$form field='isnew'} +
+
+ +
+
+ {/form_field} + + {module_include location='product_details_promotion_form'} +
+
+ + + {* -- Shipping -------------------------------------------------- *} + +
+
+

{intl l='Shipping'}

+ + {form_field form=$form field='weight'} +
+ + +
+ + {intl l="Kg"} +
+
+ {/form_field} + + {module_include location='product_details_shipping_form'} + +

{intl l='Quantity'}

+ + {form_field form=$form field='quantity'} +
+ + +
+ +
+
+ {/form_field} + + {module_include location='product_details_quantity_form'} +
+
+
+
+ {/form} + {/elseloop} + + {* -- Attribute combinations -------------------------------------------- *} + +
+
+ + {ifloop rel="product.sales.elements"} +
+ + {include + file = "includes/inner-form-toolbar.html" + hide_submit_buttons = false + show_currencies = true + page_url = "{url path='/admin/products/update' product_id=$ID}" + close_url = "{url path='/admin/categories' category_id=$DEFAULT_CATEGORY}" + } + + {module_include location='product_before_combinations'} + + + + + + + + + + + + + + + + + + + + + + {loop name="product.sales.elements" type="product_sale_elements" product=$product_id currency=$edit_currency_id backend_context="1"} + + + + + + + + + + + + + + + + + + + + {/loop} + +
+ {intl l='Attribute Combinations'} + + {module_include location='product_combinations_list_caption'} + + {loop type="auth" name="can_create" roles="ADMIN" permissions="admin.products.update"} + + + + {/loop} +
{intl l='Attributes'}{intl l='Quantity'}{intl l='Price
w/o taxes (%currency)' currency=$currency_symbol}
{intl l='Price
w/ taxes (%currency)' currency=$currency_symbol}
{intl l='Weight (Kg)'}{intl l='Default'}{intl l='Is new'}{intl l='On sale'}{intl l='Sale price
w/o taxes (%currency)' currency=$currency_symbol}
{intl l='Sale price
w/ taxes (%currency)' currency=$currency_symbol}
{intl l='Actions'}
+ {loop name="product.sales.elements.combinations" type="attribute_combination" product_sale_elements=$ID backend_context="1"} + {$ATTRIBUTE_TITLE}  + {/loop} + + + + + + + + +
+ + {module_include location='product_after_combinations'} +
+ + {/ifloop} + + {elseloop rel="product.sales.elements"} +

{intl l='Attribute Combinations'}

+ +
+ {intl + l='This product has no combination. The default price is used. Click here to create a new combination' + url='#combination_creation_dialog' + } +
+ {/elseloop} + + {module_include location='product_after_combinations'} + +
+
+
+ +{* -- Adding a new combination ------------------------------------------------- *} + +{* Capture the dialog body, to pass it to the generic dialog *} + +{capture "combination_creation_dialog"} + + + + +
+ + + {intl l='Select an attribute and click (+) to view available values'} +
+ + +
+
+ {* *} + + + + + + +
+ + {intl l='Select a value click (+) to add it to the combination'} +
+ +
+
+ {intl l="No available value for this attribute"} +
+
+ +
+
+ + + +
+ {intl l='To remove a value from the combination, select it and click "remove"'} + +
+ +
+
+
+ +{/capture} + +{include + file = "includes/generic-create-dialog.html" + + dialog_id = "combination_creation_dialog" + dialog_title = {intl l="Create a new combination"} + dialog_body = {$smarty.capture.combination_creation_dialog nofilter} + + dialog_ok_label = {intl l="Create this combination"} + + form_action = {url path='/admin/product/combination/add'} + form_enctype = '' + form_error_message = '' + + ok_button_id = "combination_creation_dialog_ok" +} + + +{* -- Delete combination confirmation dialog ----------------------------------- *} + +{capture "combination_delete_dialog"} + + + + + + + {module_include location='category_delete_form'} + +{/capture} + +{include + file = "includes/generic-confirm-dialog.html" + + dialog_id = "combination_delete_dialog" + dialog_title = {intl l="Delete a combunation"} + dialog_message = {intl l="Do you really want to delete this combination ?"} + + form_action = {url path='/admin/product/combination/delete'} + form_content = {$smarty.capture.combination_delete_dialog nofilter} +} diff --git a/templates/admin/default/includes/product-general-tab.html b/templates/admin/default/includes/product-general-tab.html new file mode 100644 index 000000000..838e993cb --- /dev/null +++ b/templates/admin/default/includes/product-general-tab.html @@ -0,0 +1,112 @@ +
+ + {form name="thelia.admin.product.modification"} +
+ + {include + file = "includes/inner-form-toolbar.html" + hide_submit_buttons = false + + page_url = "{url path='/admin/products/update' product_id=$ID}" + close_url = "{url path='/admin/categories' category_id=$DEFAULT_CATEGORY}" + } + + {* Be sure to get the product ID, even if the form could not be validated *} + + + + + {form_hidden_fields form=$form} + + {form_field form=$form field='id'} + + {/form_field} + + {form_field form=$form field='success_url'} + + {/form_field} + + {form_field form=$form field='locale'} + + {/form_field} + + {if $form_error}
{$form_error_message}
{/if} + +
+ + +
{$REF}
+ + {form_field form=$form field='ref'} + + {/form_field} + +
+ + {include file="includes/standard-description-form-fields.html"} + + {form_field form=$form field='url'} +
+ + + +
+ {/form_field} + +
+
+ + {form_field form=$form field='default_category'} +
+ + + + + {intl l='You can attach this product to more categories in the details tab.'} +
+ {/form_field} + +
+ +
+ {form_field form=$form field='visible'} +
+ +
+ +
+
+ {/form_field} +
+
+ +
+
+
+ +
+

{intl l='Product created on %date_create. Last modification: %date_change' date_create="{format_date date=$CREATE_DATE}" date_change="{format_date date=$UPDATE_DATE}"}

+
+
+
+
+ +
+ {/form} +
\ No newline at end of file diff --git a/templates/admin/default/includes/standard-description-form-fields.html b/templates/admin/default/includes/standard-description-form-fields.html index 17c340b27..80b892e81 100644 --- a/templates/admin/default/includes/standard-description-form-fields.html +++ b/templates/admin/default/includes/standard-description-form-fields.html @@ -1,4 +1,6 @@ -{* The standard description fields, used by many Thelia objects *} +{* +The standard description fields, used by many Thelia objects +*} {form_field form=$form field='title'}
@@ -22,7 +24,7 @@
diff --git a/templates/admin/default/product-attributes-edit.html b/templates/admin/default/product-attributes-edit.html deleted file mode 100644 index e69de29bb..000000000 diff --git a/templates/admin/default/product-edit.html b/templates/admin/default/product-edit.html index d80d9ca0a..2e3ef3615 100644 --- a/templates/admin/default/product-edit.html +++ b/templates/admin/default/product-edit.html @@ -6,6 +6,7 @@ {block name="main-content"}
+
{include file="includes/catalog-breadcrumb.html" editing_category="false" editing_product="true"} @@ -40,353 +41,73 @@
-
- -
- - {form name="thelia.admin.product.modification"} -
- - {include file="includes/inner-form-toolbar.html" close_url="{url path='/admin/products' product_id=$product_id}"} - - {* Be sure to get the product ID, even if the form could not be validated *} - - - - - {form_hidden_fields form=$form} - - {form_field form=$form field='success_url'} - - {/form_field} - - {form_field form=$form field='locale'} - - {/form_field} - - {if $form_error}
{$form_error_message}
{/if} - -
- - -
{$REF}
-
- - {include file="includes/standard-description-form-fields.html"} - - {form_field form=$form field='url'} -
- - - -
- {/form_field} - -
-
- - {form_field form=$form field='default_category'} -
- - - - - {intl l='You can attach this product to more categories in the details tab.'} -
- {/form_field} - -
- -
- {form_field form=$form field='visible'} -
- -
- -
-
- {/form_field} -
-
- -
-
-
-   -
-

{intl l='Product created on %date_create. Last modification: %date_change' date_create="{format_date date=$CREATE_DATE}" date_change="{format_date date=$UPDATE_DATE}"}

-
-
-
-
- -
- {/form} -
+
+ {include file="includes/product-general-tab.html"}
-
-
+
+ {include file="includes/product-details-tab.html"} +
- {include - file="includes/inner-form-toolbar.html" - hide_submit_buttons=true - close_url="{url path='/admin/categories' category_id=$DEFAULT_CATEGORY}" - } +
+
{intl l="Please wait, loading"}
+
- {* -- Begin related content management -- *} - -
-
- -
- - - - - - - - - {module_include location='product_contents_table_header'} - - - - - - - {loop name="assigned_contents" type="associated_content" product="$product_id" backend_context="1" lang="$edit_language_id"} - - - - - - {module_include location='product_contents_table_row'} - - - - {/loop} - - {elseloop rel="assigned_contents"} - - - - {/elseloop} - -
{intl l='ID'}{intl l='Content title'}{intl l="Actions"}
{$ID} - {$TITLE} - -
- {loop type="auth" name="can_create" roles="ADMIN" permissions="admin.configuration.product.content.delete"} - - - - {/loop} -
-
-
- {intl l="This product contains no contents"} -
-
-
- - {* -- End related content management ---- *} - - {* -- Begin accessories management ------ *} - -
-
-
- -

{intl l='Product accessories'}

-

{intl l='Define here this product\'s accessories'}

- - - - - {ifloop rel="categories"} -
- - - {intl l='Select a category to get its products'} -
- -
-
- - - - -
- - {intl l='Select a product and click (+) to add it as an accessory'} -
- -
-
- {intl l="No available product in this category"} -
-
- - {/ifloop} - - {elseloop rel="categories"} -
{intl l="No categories found"}
- {/elseloop} - -
-
- - - - - - - - - - - {module_include location='product_accessories_table_header'} - - - - - - - {loop name="assigned_accessories" order="accessory" type="accessory" product="$product_id" backend_context="1" lang="$edit_language_id"} - - - - - - - - {module_include location='product_accessories_table_row'} - - - - {/loop} - - {elseloop rel="assigned_accessories"} - - - - {/elseloop} - -
{intl l='ID'}{intl l='Accessory title'}{intl l='Position'}{intl l="Actions"}
{$ID} - {$TITLE} - - {admin_position_block - permission="admin.products.edit" - path={url path='/admin/products/update-accessory-position' product_id=$ID} - url_parameter="accessory_id" - in_place_edit_class="accessoryPositionChange" - position=$POSITION - id=$ID - } - -
- {loop type="auth" name="can_create" roles="ADMIN" permissions="admin.configuration.product.accessory.delete"} - - - - {/loop} -
-
-
- {intl l="This product contains no accessories"} -
-
-
- - {* -- End accessories management -------- *} - -
+ -
+
+
{intl l="Please wait, loading"}
-
+
+
{intl l="Please wait, loading"}
-
+
+
{intl l="Please wait, loading"}
@@ -397,53 +118,25 @@
- -{* Delete related content confirmation dialog *} - -{capture "delete_content_dialog"} - - - - - -{/capture} - -{include - file = "includes/generic-confirm-dialog.html" - - dialog_id = "delete_content_dialog" - dialog_title = {intl l="Remove related content"} - dialog_message = {intl l="Do you really want to remove this related content from the product ?"} - - form_action = {url path='/admin/products/related-content/delete'} - form_content = {$smarty.capture.delete_content_dialog nofilter} -} - -{* Delete accessory confirmation dialog *} - -{capture "delete_accessory_dialog"} - - - - - -{/capture} - -{include - file = "includes/generic-confirm-dialog.html" - - dialog_id = "delete_accessory_dialog" - dialog_title = {intl l="Remove an accessory"} - dialog_message = {intl l="Do you really want to remove this accessory from the product ?"} - - form_action = {url path='/admin/products/accessory/delete'} - form_content = {$smarty.capture.delete_accessory_dialog nofilter} -} {/block} {block name="javascript-initialization"} -{javascripts file='assets/js/bootstrap-editable/bootstrap-editable.js'} + {javascripts file='assets/js/dropzone.js'} + + {/javascripts} + {javascripts file='assets/js/image-upload.js'} + + {/javascripts} + {javascripts file='assets/js/document-upload.js'} + + {/javascripts} + {javascripts file='assets/js/bootstrap-editable/bootstrap-editable.js'} + + {/javascripts} + + +{javascripts file='assets/js/bootstrap-switch/bootstrap-switch.js'} {/javascripts} @@ -470,130 +163,114 @@ + + {/block} \ No newline at end of file diff --git a/templates/admin/default/template-edit.html b/templates/admin/default/template-edit.html index 7b9ddd4aa..17e48b950 100644 --- a/templates/admin/default/template-edit.html +++ b/templates/admin/default/template-edit.html @@ -59,30 +59,32 @@ {/form}
+
-
-
-
+
+
+
+

{intl l='Attributes'}

Manage attributes included in this product templates

+
+
-
- -
+
+

{intl l='Features'}

Manage features included in this product templates

-
+
-
- +
@@ -106,10 +108,14 @@ {block name="javascript-initialization"} + {javascripts file='assets/js/bootstrap-editable/bootstrap-editable.js'} + + {/javascripts} + {/block} \ No newline at end of file diff --git a/templates/admin/default/templates.html b/templates/admin/default/templates.html index f7e912e9c..839f8b5ed 100644 --- a/templates/admin/default/templates.html +++ b/templates/admin/default/templates.html @@ -25,7 +25,7 @@ {if ! empty($general_error) }
{$general_error}
{/if} - +
@@ -194,10 +194,6 @@ {block name="javascript-initialization"} - {javascripts file='assets/js/bootstrap-editable/bootstrap-editable.js'} - - {/javascripts} -