diff --git a/core/lib/Thelia/Action/BaseAction.php b/core/lib/Thelia/Action/BaseAction.php index 56565ddc6..6aceb033a 100755 --- a/core/lib/Thelia/Action/BaseAction.php +++ b/core/lib/Thelia/Action/BaseAction.php @@ -23,6 +23,7 @@ namespace Thelia\Action; use Symfony\Component\DependencyInjection\ContainerInterface; +use Thelia\Model\AdminLog; class BaseAction { @@ -45,4 +46,18 @@ class BaseAction { return $this->container->get('event_dispatcher'); } + + /** + * 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/Image.php b/core/lib/Thelia/Action/Image.php index 4660f93b8..41ce957d4 100755 --- a/core/lib/Thelia/Action/Image.php +++ b/core/lib/Thelia/Action/Image.php @@ -23,10 +23,19 @@ 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\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 +48,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 +87,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 +251,107 @@ class Image extends BaseCachedFile implements EventSubscriberInterface $event->setOriginalFileUrl(URL::getInstance()->absoluteUrl($original_image_url, null, URL::PATH_TO_FILE)); } + /** + * Take care of saving images 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 saveImages(ImageCreateOrUpdateEvent $event) + { + $fileManager = new FileManager($this->container); + + $this->adminLogAppend( + $this->container->get('thelia.translator')->trans( + 'Saving images for parent id %parentId% (%parentType%)', + array( + '%parentId%' => $event->getParentId(), + '%parentType%' => $event->getImageType() + ), + 'image' + ) + ); + + $newUploadedFiles = array(); + $uploadedFiles = $event->getUploadedFiles(); + + foreach ($event->getModelImages() as $i => $modelImage) { + // Save image to database in order to get image id + $fileManager->saveImage($event, $modelImage); + + if (isset($uploadedFiles) && isset($uploadedFiles[$i])) { + /** @var UploadedFile $uploadedFile */ + $uploadedFile = $uploadedFiles[$i]['file']; + + // Copy uploaded file into the storage directory + $newUploadedFiles = $fileManager->copyUploadedFile($event->getParentId(), $event->getImageType(), $modelImage, $uploadedFile, $newUploadedFiles); + } else { + throw new ImageException( + sprintf( + 'File with name %s not found on the server', + $modelImage->getFile() + ) + ); + } + $event->setUploadedFiles($newUploadedFiles); + } + } + + /** + * Take care of deleting 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 deleteImage(ImageDeleteEvent $event) + { + $fileManager = new FileManager($this->container); + + try { + $fileManager->deleteImage($event->getImageToDelete()); + + $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' + ) + ); + } + } + + /** + * The absolute directory path where uploaded + * documents should be saved + * + * @param $modelImage Image model + * + * @return string + */ + public function getUploadRootDir($modelImage) + { + return __DIR__.'/../../../../' . $modelImage->getUploadDir(); + } + /** * Process image resizing, with borders or cropping. If $dest_width and $dest_height * are both null, no resize is performed. @@ -362,6 +474,8 @@ class Image extends BaseCachedFile implements EventSubscriberInterface return array( TheliaEvents::IMAGE_PROCESS => array("processImage", 128), TheliaEvents::IMAGE_CLEAR_CACHE => array("clearCache", 128), + TheliaEvents::IMAGE_SAVE => array("saveImages", 128), + TheliaEvents::IMAGE_DELETE => array("deleteImage", 128), ); } } diff --git a/core/lib/Thelia/Config/Resources/config.xml b/core/lib/Thelia/Config/Resources/config.xml index db2b1db82..bdf6a01e0 100755 --- a/core/lib/Thelia/Config/Resources/config.xml +++ b/core/lib/Thelia/Config/Resources/config.xml @@ -54,7 +54,7 @@
- + @@ -225,6 +225,11 @@ + + + + + diff --git a/core/lib/Thelia/Config/Resources/routing/admin.xml b/core/lib/Thelia/Config/Resources/routing/admin.xml index 8f2e54f86..0fbf38696 100755 --- a/core/lib/Thelia/Config/Resources/routing/admin.xml +++ b/core/lib/Thelia/Config/Resources/routing/admin.xml @@ -31,6 +31,19 @@ Thelia\Controller\Admin\CategoryController::defaultAction + + + + Thelia\Controller\Admin\FileController::saveImagesAction + .* + \d+ + + + Thelia\Controller\Admin\FileController::deleteImagesAction + .* + \d+ + + diff --git a/core/lib/Thelia/Controller/Admin/CategoryController.php b/core/lib/Thelia/Controller/Admin/CategoryController.php index 1c7854416..34d7a5f04 100755 --- a/core/lib/Thelia/Controller/Admin/CategoryController.php +++ b/core/lib/Thelia/Controller/Admin/CategoryController.php @@ -23,11 +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\Form\CategoryPictureCreationForm; +use Thelia\Log\Tlog; use Thelia\Model\CategoryQuery; use Thelia\Form\CategoryModificationForm; use Thelia\Form\CategoryCreationForm; @@ -35,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; @@ -366,29 +367,4 @@ class CategoryController extends AbstractCrudController $this->redirectToEditionTemplate(); } - /** - * @todo remove - * @return Symfony\Component\HttpFoundation\Response|void - */ - public function updateAction() - { - var_dump('updateAction'); - if ($this->getRequest()->isMethod('POST')) { - var_dump('getRequest', $this->getRequest()->files); - // Create the form from the request - $creationForm = new CategoryPictureCreationForm($this->getRequest()); - - // Check the form against constraints violations - $form = $this->validateForm($creationForm, 'POST'); - var_dump('$form', $form); - - // Get the form field values - $data = $form->getData(); - var_dump('$data', $data); - } - - - return parent::updateAction(); - } - } diff --git a/core/lib/Thelia/Controller/Admin/FileController.php b/core/lib/Thelia/Controller/Admin/FileController.php new file mode 100755 index 000000000..4be913bcb --- /dev/null +++ b/core/lib/Thelia/Controller/Admin/FileController.php @@ -0,0 +1,290 @@ +. */ +/* */ +/**********************************************************************************/ + +namespace Thelia\Controller\Admin; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Router; +use Thelia\Core\Event\ImageCreateOrUpdateEvent; +use Thelia\Core\Event\ImageDeleteEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\Translation\Translator; +use Thelia\Form\CategoryImageCreationForm; +use Thelia\Form\Exception\FormValidationException; +use Thelia\Log\Tlog; +use Thelia\Model\CategoryImageQuery; +use Thelia\Model\ContentImageQuery; +use Thelia\Model\FolderImageQuery; +use Thelia\Model\ProductImageQuery; + +/** + * 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 file collection has to be saved + * + * @param int $parentId Parent id owning files being saved + * @param string $parentType Parent Type owning files being saved + * @param string $successUrl Success URL to be redirected to + * + * @return Response + */ + public function saveFilesAction($parentId, $parentType, $successUrl) + { + + } + + /** + * 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 saveImagesAction($parentId, $parentType) + { + // Check current user authorization + if (null !== $response = $this->checkAuth("admin.image.save")) { + return $response; + } + + $message = $this->getTranslator() + ->trans( + 'Images saved successfully', + array(), + 'image' + ); + + if ($this->isParentTypeValid($parentType)) { + if ($this->getRequest()->isMethod('POST')) { + // Create the form from the request + $creationForm = $this->getImageForm($parentType, $this->getRequest()); + + try { + // Check the form against constraints violations + $form = $this->validateForm($creationForm, 'POST'); + + // Get the form field values + $data = $form->getData(); + + // Feed event + $imageCreateOrUpdateEvent = new ImageCreateOrUpdateEvent( + $parentType, + $parentId + ); + if (isset($data) && isset($data['pictures'])) { + $imageCreateOrUpdateEvent->setModelImages($data['pictures']); + $imageCreateOrUpdateEvent->setUploadedFiles($this->getRequest()->files->get($creationForm->getName())['pictures']); + } + + // Dispatch Event to the Action + $this->dispatch( + TheliaEvents::IMAGE_SAVE, + $imageCreateOrUpdateEvent + ); + + } catch (FormValidationException $e) { + // Invalid data entered + $message = 'Please check your input:'; + $this->logError($parentType, 'image saving', $message, $e); + + } catch (\Exception $e) { + // Any other error + $message = 'Sorry, an error occurred:'; + $this->logError($parentType, 'image saving', $message, $e); + } + + if ($message !== false) { + // Mark the form as with error + $creationForm->setErrorMessage($message); + + // Send the form and the error to the parser + $this->getParserContext() + ->addForm($creationForm) + ->setGeneralError($message); + + // Set flash message to be displayed + $flashMessage = $this->getSession()->get('flashMessage'); + $flashMessage['imageMessage'] = $message; + $this->getSession()->set('flashMessage', $flashMessage); + } + } + } + + $this->redirectSuccess($creationForm); + } + + /** + * Manage how a image has to be deleted (AJAX) + * + * @param int $imageId Parent id owning images being saved + * @param string $parentType Parent Type owning images being saved + * + * @return Response + */ + public function deleteImagesAction($imageId, $parentType) + { + $this->checkAuth('ADMIN', 'admin.image.delete'); + $this->checkXmlHttpRequest(); + + $model = $this->getImageModel($parentType, $imageId); + if ($model == null) { + return $this->pageNotFound(); + } + + // Feed event + $imageDeleteEvent = new ImageDeleteEvent( + $model + ); + + // Dispatch Event to the Action + $this->dispatch( + TheliaEvents::IMAGE_DELETE, + $imageDeleteEvent + ); + + $message = $this->getTranslator() + ->trans( + 'Images deleted successfully', + array(), + 'image' + ); + + 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, ImageCreateOrUpdateEvent::getAvailableType())); + } + + /** + * Get Image form + * + * @param string $parentType Parent type + * @param Request $request Request Service + * + * @return null|CategoryImageCreationForm|ContentImageCreationForm|FolderImageCreationForm|ProductImageCreationForm + * + * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action + */ + public function getImageForm($parentType, Request $request) + { + // @todo implement other forms + switch ($parentType) { +// case ImageCreateOrUpdateEvent::TYPE_PRODUCT: +// $creationForm = new ProductImageCreationForm($request); +// break; + case ImageCreateOrUpdateEvent::TYPE_CATEGORY: + $creationForm = new CategoryImageCreationForm($request); + break; +// case ImageCreateOrUpdateEvent::TYPE_CONTENT: +// $creationForm = new ContentImageCreationForm($request); +// break; +// case ImageCreateOrUpdateEvent::TYPE_FOLDER: +// $creationForm = new FolderImageCreationForm($request); +// break; + default: + $creationForm = null; + } + + return $creationForm; + + } + + /** + * Get image model from type + * + * @param string $parentType + * @param int $imageId + * + * @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, $imageId) + { + switch ($parentType) { + case ImageCreateOrUpdateEvent::TYPE_PRODUCT: + $model = ProductImageQuery::create()->findPk($imageId); + break; + case ImageCreateOrUpdateEvent::TYPE_CATEGORY: + $model = CategoryImageQuery::create()->findPk($imageId); + break; + case ImageCreateOrUpdateEvent::TYPE_CONTENT: + $model = ContentImageQuery::create()->findPk($imageId); + break; + case ImageCreateOrUpdateEvent::TYPE_FOLDER: + $model = FolderImageQuery::create()->findPk($imageId); + break; + default: + $model = null; + } + + return $model; + + } +} diff --git a/core/lib/Thelia/Core/Event/ImageCreateOrUpdateEvent.php b/core/lib/Thelia/Core/Event/ImageCreateOrUpdateEvent.php new file mode 100755 index 000000000..afae53e7a --- /dev/null +++ b/core/lib/Thelia/Core/Event/ImageCreateOrUpdateEvent.php @@ -0,0 +1,184 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Occurring when a Image list is saved + * + * @package Image + * @author Guillaume MOREL + * + */ +class ImageCreateOrUpdateEvent extends ActionEvent +{ + CONST TYPE_PRODUCT = 'product'; + CONST TYPE_CATEGORY = 'category'; + CONST TYPE_CONTENT = 'content'; + CONST TYPE_FOLDER = 'folder'; + + /** @var array Images model to save */ + protected $modelImages = array(); + + /** @var array Images file to save */ + protected $uploadedFiles = array(); + + /** @var int Image parent id */ + protected $parentId = null; + + /** @var string Image type */ + protected $imageType = null; + + /** @var array Available image parent type */ + protected static $availableType = array( + self::TYPE_PRODUCT, + self::TYPE_CATEGORY, + self::TYPE_CONTENT, + self::TYPE_FOLDER, + ); + + /** + * Constructor + * + * @param string $pictureType Picture type + * ex : ImageCreateOrUpdateEvent::TYPE_CATEGORY + * @param int $parentId Image parent id + */ + public function __construct($pictureType, $parentId) + { + $this->imageType = $pictureType; + $this->parentId = $parentId; + } + + /** + * Set Images to save + * + * @param array $images Thelia\Model\CategoryImage Array + * + * @return $this + */ + public function setModelImages($images) + { + $this->modelImages = $images; + + return $this; + } + + /** + * Get Images being saved + * + * @return array Array of Thelia\Model\CategoryImage + */ + public function getModelImages() + { + return $this->modelImages; + } + + /** + * Set Images to save + * + * @param array $images Thelia\Model\CategoryImage Array + * + * @return $this + */ + public function setUploadedFiles($images) + { + $this->uploadedFiles = $images; + + return $this; + } + + /** + * Get Images being saved + * + * @return array Array of Thelia\Model\CategoryImage + */ + public function getUploadedFiles() + { + return $this->uploadedFiles; + } + + /** + * Set picture type + * + * @param string $pictureType Picture type + * + * @return $this + */ + public function setImageType($pictureType) + { + $this->imageType = $pictureType; + + return $this; + } + + /** + * Get picture type + * + * @return string + */ + public function getImageType() + { + return $this->imageType; + } + + /** + * Get all image parent type available + * + * @return array + */ + public static function getAvailableType() + { + return self::$availableType; + } + + /** + * 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; + } + + +} diff --git a/core/lib/Thelia/Core/Event/ImageDeleteEvent.php b/core/lib/Thelia/Core/Event/ImageDeleteEvent.php new file mode 100755 index 000000000..54e042e79 --- /dev/null +++ b/core/lib/Thelia/Core/Event/ImageDeleteEvent.php @@ -0,0 +1,82 @@ +. */ +/* */ +/*************************************************************************************/ + +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 CategoryImage|ProductImage|ContentImage|FolderImage Image about to be deleted */ + protected $imageToDelete = null; + + /** + * Constructor + * + * @param CategoryImage|ProductImage|ContentImage|FolderImage $imageToDelete Image about to be deleted + */ + public function __construct($imageToDelete) + { + $this->imageToDelete = $imageToDelete; + } + + /** + * 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/TheliaEvents.php b/core/lib/Thelia/Core/Event/TheliaEvents.php index dab2db208..2450143e7 100755 --- a/core/lib/Thelia/Core/Event/TheliaEvents.php +++ b/core/lib/Thelia/Core/Event/TheliaEvents.php @@ -234,6 +234,16 @@ final class TheliaEvents */ const IMAGE_CLEAR_CACHE = "action.clearImageCache"; + /** + * Save given images + */ + const IMAGE_SAVE = "action.saveImages"; + + /** + * Delete given image + */ + const IMAGE_DELETE = "action.deleteImage"; + /** * Sent when creating a Coupon */ 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/Form/CategoryImageCreationForm.php b/core/lib/Thelia/Form/CategoryImageCreationForm.php new file mode 100644 index 000000000..d0b3be229 --- /dev/null +++ b/core/lib/Thelia/Form/CategoryImageCreationForm.php @@ -0,0 +1,71 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form; + +use Thelia\Core\Translation\Translator; +use Thelia\Form\Type\ImageCategoryType; + +/** + * 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 CategoryImageCreationForm extends BaseForm +{ + /** + * Allow to build a form + */ + protected function buildForm() + { + $this->formBuilder + ->add('pictures', + 'collection', + array( + 'type' => new ImageCategoryType(), + 'options' => array( + 'required' => false + ), + 'allow_add' => true, + 'allow_delete' => true, + 'by_reference' => false, + ) + ) + ->add('formSuccessUrl'); + } + + /** + * Get form name + * + * @return string + */ + public function getName() + { + return 'thelia_category_image_creation'; + } +} diff --git a/core/lib/Thelia/Form/CategoryPictureCreationForm.php b/core/lib/Thelia/Form/CategoryPictureCreationForm.php deleted file mode 100644 index 2dffc61c3..000000000 --- a/core/lib/Thelia/Form/CategoryPictureCreationForm.php +++ /dev/null @@ -1,100 +0,0 @@ -. */ -/* */ -/*************************************************************************************/ -namespace Thelia\Form; - -use Symfony\Component\Validator\Constraints\Image; -use Symfony\Component\Validator\Constraints\NotBlank; -use Thelia\Core\Translation\Translator; - -class CategoryPictureCreationForm extends BaseForm -{ - protected function buildForm() - { - $this->formBuilder -// ->add('alt') - ->add('file', 'file', array( - 'constraints' => array( - new NotBlank(), - new Image( - array( - 'minWidth' => 200, - 'maxWidth' => 400, - 'minHeight' => 200, - 'maxHeight' => 400, - ) - ) - ))) -// ->add('category_id', 'model', array( -// 'disabled' => false, -// 'class' => 'Thelia\Model\ProductImage' -// )) -// ->add('position', 'integer', array( -// 'constraints' => array( -// new NotBlank() -// ) -// )) - ->add('title', 'text', array( - 'constraints' => array( -// new NotBlank() - ), -// 'label' => Translator::getInstance()->trans('Category picture title *'), -// 'label_attr' => array( -// 'for' => 'title' -// ) - )) -// ->add('description', 'text', array( -// 'constraints' => array( -// new NotBlank() -// ), -// 'label' => Translator::getInstance()->trans('Category picture description *'), -// 'label_attr' => array( -// 'for' => 'description' -// ) -// )) -// ->add('chapo', 'text', array( -// 'constraints' => array( -// new NotBlank() -// ), -// 'label' => Translator::getInstance()->trans('Category picture chapo *'), -// 'label_attr' => array( -// 'for' => 'chapo' -// ) -// )) -// ->add('postscriptum', 'text', array( -// 'constraints' => array( -// new NotBlank() -// ), -// 'label' => Translator::getInstance()->trans('Category picture postscriptum *'), -// 'label_attr' => array( -// 'for' => 'postscriptum' -// ) -// )) - - ; - } - - public function getName() - { - return 'thelia_category_picture_creation'; - } -} diff --git a/core/lib/Thelia/Form/Type/ImageCategoryType.php b/core/lib/Thelia/Form/Type/ImageCategoryType.php new file mode 100644 index 000000000..7b8a3c474 --- /dev/null +++ b/core/lib/Thelia/Form/Type/ImageCategoryType.php @@ -0,0 +1,79 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form\Type; + +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolverInterface; +use Thelia\Form\Type\ImageType; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Form allowing to process a category picture + * + * @package Image + * @author Guillaume MOREL + * + */ +class ImageCategoryType extends ImageType +{ + /** + * Build a Picture form + * + * @param FormBuilderInterface $builder Form builder + * @param array $options Form options + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->add('category_id', 'integer'); + + parent::buildForm($builder, $options); + } + + /** + * Set default options + * Map the form to the given Model + * + * @param OptionsResolverInterface $resolver Option resolver + */ + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults( + array( + 'data_class' => 'Thelia\Model\CategoryImage' + ) + ); + } + + /** + * Get form name + * + * @return string + */ + public function getName() + { + return 'thelia_category_picture_creation_type'; + } +} diff --git a/core/lib/Thelia/Form/Type/ImageType.php b/core/lib/Thelia/Form/Type/ImageType.php new file mode 100644 index 000000000..662e2b3f7 --- /dev/null +++ b/core/lib/Thelia/Form/Type/ImageType.php @@ -0,0 +1,82 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Validator\Constraints\Image; +use Symfony\Component\Validator\Constraints\NotBlank; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/18/13 + * Time: 3:56 PM + * + * Form allowing to process a picture + * + * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action + * + * @package Image + * @author Guillaume MOREL + * + */ +abstract class ImageType extends AbstractType +{ + /** + * Build a Picture form + * + * @param FormBuilderInterface $builder Form builder + * @param array $options Form options + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { +// $builder->add('position'); + $builder->add( + 'title', + 'text', + array( + 'constraints' => new NotBlank() + ) + ); + $builder->add( + 'file', + 'file', + array( + 'constraints' => array( + new NotBlank(), + new Image( + array( + 'minWidth' => 200, +// 'maxWidth' => 400, + 'minHeight' => 200, +// 'maxHeight' => 400, + ) + ) + ) + ) + ); +// $builder->add('description'); +// $builder->add('chapo'); +// $builder->add('postscriptum'); + } +} diff --git a/core/lib/Thelia/Model/CategoryImage.php b/core/lib/Thelia/Model/CategoryImage.php index aca77615a..bb7fa0dcb 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; @@ -28,6 +30,7 @@ class CategoryImage extends BaseCategoryImage /** * Get picture absolute path + * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action * * @return null|string */ @@ -35,11 +38,12 @@ class CategoryImage extends BaseCategoryImage { return null === $this->file ? null - : $this->getUploadRootDir().'/'.$this->file; + : $this->getUploadDir().'/'.$this->file; } /** * Get picture web path + * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action * * @return null|string */ @@ -50,23 +54,25 @@ class CategoryImage extends BaseCategoryImage : $this->getUploadDir().'/'.$this->file; } - /** - * The absolute directory path where uploaded - * documents should be saved - * @return string - */ - protected function getUploadRootDir() - { - return __DIR__.'/../../../../../'.$this->getUploadDir(); - } /** * Get rid of the __DIR__ so it doesn't screw up * when displaying uploaded doc/image in the view. * @return string */ - protected function getUploadDir() + public function getUploadDir() { - return 'local/media/images/category'; + return THELIA_LOCAL_DIR . 'media/images/category'; } + + /** + * Get Image parent id + * + * @return int parent id + */ + public function getParentId() + { + return $this->getCategoryId(); + } + } diff --git a/core/lib/Thelia/Model/ContentImage.php b/core/lib/Thelia/Model/ContentImage.php index ac1dcf755..ddb33cbe8 100755 --- a/core/lib/Thelia/Model/ContentImage.php +++ b/core/lib/Thelia/Model/ContentImage.php @@ -25,4 +25,14 @@ class ContentImage extends BaseContentImage return true; } + + /** + * 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/FolderImage.php b/core/lib/Thelia/Model/FolderImage.php index 58d8f928e..0983f0244 100755 --- a/core/lib/Thelia/Model/FolderImage.php +++ b/core/lib/Thelia/Model/FolderImage.php @@ -25,4 +25,14 @@ class FolderImage extends BaseFolderImage return true; } + + /** + * Get Image parent id + * + * @return int parent id + */ + public function getParentId() + { + return $this->getFolderId(); + } } diff --git a/core/lib/Thelia/Model/ProductImage.php b/core/lib/Thelia/Model/ProductImage.php index 4bf0c40a6..058cbc863 100755 --- a/core/lib/Thelia/Model/ProductImage.php +++ b/core/lib/Thelia/Model/ProductImage.php @@ -25,4 +25,14 @@ class ProductImage extends BaseProductImage return true; } + + /** + * Get Image parent id + * + * @return int parent id + */ + public function getParentId() + { + return $this->getProductId(); + } } diff --git a/core/lib/Thelia/Tests/Tools/FileManagerTest.php b/core/lib/Thelia/Tests/Tools/FileManagerTest.php new file mode 100644 index 000000000..7ee1fd493 --- /dev/null +++ b/core/lib/Thelia/Tests/Tools/FileManagerTest.php @@ -0,0 +1,361 @@ + + */ + +namespace Thelia\Tests\Type; + + +use Thelia\Core\Event\ImageCreateOrUpdateEvent; +use Thelia\Exception\ImageException; +use Thelia\Model\Admin; +use Thelia\Model\ProductImage; +use Thelia\Tools\FileManager; + +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, ImageCreateOrUpdateEvent::TYPE_PRODUCT, $stubProductImage, $stubUploadedFile, $newUploadedFiles); + + $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, ImageCreateOrUpdateEvent::TYPE_PRODUCT, $stubProductImage, $stubUploadedFile, $newUploadedFiles); + + } + + /** + * @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)); + + $fileManager = new FileManager($stubContainer); + + $event = new ImageCreateOrUpdateEvent(ImageCreateOrUpdateEvent::TYPE_PRODUCT, 24); + + $expected = 10; + $actual = $fileManager->saveImage($event, $stubProductImage); + + $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)); + + $fileManager = new FileManager($stubContainer); + + $event = new ImageCreateOrUpdateEvent(ImageCreateOrUpdateEvent::TYPE_CATEGORY, 24); + + $expected = 10; + $actual = $fileManager->saveImage($event, $stubCategoryImage); + + $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)); + + $fileManager = new FileManager($stubContainer); + + $event = new ImageCreateOrUpdateEvent(ImageCreateOrUpdateEvent::TYPE_FOLDER, 24); + + $expected = 10; + $actual = $fileManager->saveImage($event, $stubFolderImage); + + $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)); + + $fileManager = new FileManager($stubContainer); + + $event = new ImageCreateOrUpdateEvent(ImageCreateOrUpdateEvent::TYPE_CONTENT, 24); + + $expected = 10; + $actual = $fileManager->saveImage($event, $stubContentImage); + + $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)); + + $event = new ImageCreateOrUpdateEvent('bad', 24); + $modelImage = new ProductImage(); + + $fileManager->saveImage($event, $modelImage); + } + + /** + * @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)); + + $event = new ImageCreateOrUpdateEvent(ImageCreateOrUpdateEvent::TYPE_PRODUCT, 24); + + $fileManager->saveImage($event, $stubProductImage); + } + + /** + * @covers Thelia\Tools\FileManager::sanitizeFileName + */ + public function testSanitizeFileName() + { + $stubContainer = $this->getMockBuilder('\Symfony\Component\DependencyInjection\ContainerInterface') + ->disableOriginalConstructor() + ->getMock(); + + $fileManager = new FileManager($stubContainer); + $badFileName = 'azeéràçè§^"$*+-_°)(&é<>@#ty'; + + $expected = 'azeyryZyy-_yty'; + $actual = $fileManager->sanitizeFileName($badFileName); + + $this->assertEquals($expected, $actual); + } + + /** + * @covers Thelia\Tools\FileManager::adminLogAppend + */ + public function testAdminLogAppend() + { + $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..533dcb203 --- /dev/null +++ b/core/lib/Thelia/Tools/FileManager.php @@ -0,0 +1,222 @@ +. */ +/* */ +/**********************************************************************************/ +namespace Thelia\Tools; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Thelia\Core\Event\ImageCreateOrUpdateEvent; +use Thelia\Core\Event\ImageDeleteEvent; +use Thelia\Exception\ImageException; +use Thelia\Model\AdminLog; +use Thelia\Model\Base\CategoryImage; +use Thelia\Model\ContentImage; +use Thelia\Model\FolderImage; +use Thelia\Model\ProductImage; + +/** + * Created by JetBrains PhpStorm. + * Date: 9/19/13 + * Time: 3:24 PM + * + * File Manager + * + * @package File + * @author Guillaume MOREL + * + */ +class FileManager +{ + /** @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 $imageType Image type + * @param FolderImage|ContentImage|CategoryImage|ProductImage $modelImage Image saved + * @param UploadedFile $uploadedFile Ready to be uploaded file + * @param array $newUploadedFiles UploadedFile array to update + * + * @throws \Thelia\Exception\ImageException + * @return array Updated UploadedFile array + */ + public function copyUploadedFile($parentId, $imageType, $modelImage, $uploadedFile, $newUploadedFiles) + { + if ($uploadedFile !== null) { + $directory = $modelImage->getUploadDir(); + $fileName = $this->sanitizeFileName( + $uploadedFile->getClientOriginalName() . "-" . $modelImage->getId() . "." . strtolower( + $uploadedFile->getClientOriginalExtension() + ) + ); + + $this->adminLogAppend( + $this->translator->trans( + 'Uploading picture %pictureName% to %directory% for parent_id %parentId% (%parentType%)', + array( + '%pictureName%' => $uploadedFile->getClientOriginalName(), + '%directory%' => $directory . '/' . $fileName, + '%parentId%' => $parentId, + '%parentType%' => $imageType + ), + 'image' + ) + ); + + $newUploadedFiles[] = array('file' => $uploadedFile->move($directory, $fileName)); + $modelImage->setFile($fileName); + + if (!$modelImage->save()) { + throw new ImageException( + sprintf( + 'Image %s (%s) failed to be saved (image file)', + $modelImage->getFile(), + $imageType + ) + ); + } + } + + return $newUploadedFiles; + } + + /** + * 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 ImageCreateOrUpdateEvent::TYPE_PRODUCT: + /** @var ProductImage $modelImage */ + $modelImage->setProductId($event->getParentId()); + break; + case ImageCreateOrUpdateEvent::TYPE_CATEGORY: + /** @var CategoryImage $modelImage */ + $modelImage->setCategoryId($event->getParentId()); + break; + case ImageCreateOrUpdateEvent::TYPE_CONTENT: + /** @var ContentImage $modelImage */ + $modelImage->setContentId($event->getParentId()); + break; + case ImageCreateOrUpdateEvent::TYPE_FOLDER: + /** @var FolderImage $modelImage */ + $modelImage->setFolderId($event->getParentId()); + break; + default: + throw new ImageException( + sprintf( + 'Picture parent type is unknown (available types : %s)', + implode( + ',', + $event->getAvailableType() + ) + ) + ); + } + + $nbModifiedLines = $modelImage->save(); + if (!$nbModifiedLines) { + throw new ImageException( + sprintf( + 'Image %s failed to be saved (image content)', + $modelImage->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) + { + $cleanName = strtr($string, 'ŠŽšžŸÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöøùúûüýÿ', 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'); + $cleanName = strtr($cleanName, array('Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', 'Œ' => 'OE', 'œ' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u')); + + $cleanName = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $cleanName); + + return $cleanName; + } + + /** + * 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 $imageModel Image being deleted + */ + public function deleteImage($imageModel) + { + unlink($imageModel->getAbsolutePath()); + $imageModel->delete(); + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Tools/I18n.php b/core/lib/Thelia/Tools/I18n.php index 1f3ff57dd..f043190c2 100644 --- a/core/lib/Thelia/Tools/I18n.php +++ b/core/lib/Thelia/Tools/I18n.php @@ -54,4 +54,5 @@ class I18n return \DateTime::createFromFormat($currentDateFormat, $date); } + } 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..45e63ffbd --- /dev/null +++ b/templates/admin/default/assets/js/image-upload.js @@ -0,0 +1,67 @@ +// Manage picture upload +$(function($){ + + var pictureUploadManager = {}; + + // Set selected image as preview + pictureUploadManager.onChangePreviewPicture = function() { + $('#images input:file').on('change', function () { + var $this = $(this); + if ($this.prop("files") && $this.prop("files")[0]) { + var reader = new FileReader(); + + reader.onload = function (e) { + $this.parent() + .find('img.preview') + .attr('src', e.target.result) + .width(150) + .height(200); + } + + reader.readAsDataURL($this.prop("files")[0]); + } + }); + }; + pictureUploadManager.onChangePreviewPicture(); + + // Remove image on click + pictureUploadManager.onClickDeleteImage = function() { + $('.image-manager .image-delete-btn').on('click', function (e) { + console.log('deletingImage'); + e.preventDefault(); + var $this = $(this); + $this.parent().append('
'); + 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) { + $this.parent().remove(); + $(".image-manager .message").html( + data + ); + }); + }); + }; + pictureUploadManager.onClickDeleteImage(); + + // Remove image on click + pictureUploadManager.clonePictureInputs = function() { + var $inputs = $(".image-manager .picture-input"); + if ($inputs.size == 1) { + console.log('1'); + $(".image-manager .picture-input").last().show(); + } else { + console.log('+d1'); + $(".image-manager .picture-input").last().clone().appendTo(".image-manager .pictures-input"); + } + } +}); \ No newline at end of file diff --git a/templates/admin/default/category-edit.html b/templates/admin/default/category-edit.html index 6d1c4a9c2..8d1fcfd45 100755 --- a/templates/admin/default/category-edit.html +++ b/templates/admin/default/category-edit.html @@ -246,31 +246,7 @@
- image - {form name="thelia.admin.category.picture.creation"} - - {form_hidden_fields form=$form} - - - {form_field form=$form field='title'} -
- - - {if $error}{$message}{/if} -
- {/form_field} - - {form_field form=$form field='file'} -
- - - {if $error}{$message}{/if} -
- {/form_field} - - - - {/form} + {include file='includes/image-upload-form.html' formName="thelia.admin.category.image.creation" formSuccessUrl={url path="/admin/categories/update?category_id=$category_id"} imageType='category' parentId=$category_id}
@@ -311,6 +287,9 @@ {/block} {block name="javascript-initialization"} +{javascripts file='assets/js/image-upload.js'} + +{/javascripts}