From b91a11536b03cf1a01293981f41dcd658e82899a Mon Sep 17 00:00:00 2001 From: gmorel Date: Sun, 22 Sep 2013 20:50:42 +0200 Subject: [PATCH] Working : Image management set on Category --- composer.json | 3 +- composer.lock | 67 +- core/lib/Thelia/Action/Image.php | 111 +- core/lib/Thelia/Config/Resources/config.xml | 2 +- .../Thelia/Config/Resources/routing/admin.xml | 17 +- .../Controller/Admin/FileController.php | 306 +-- .../Core/Event/ImageCreateOrUpdateEvent.php | 159 +- .../Thelia/Core/Event/ImageDeleteEvent.php | 32 +- .../Core/Event/ImagesCreateOrUpdateEvent.php | 184 ++ core/lib/Thelia/Core/Event/TheliaEvents.php | 5 + ...Form.php => CategoryImageModification.php} | 28 +- ...yType.php => ContentImageModification.php} | 42 +- ...geType.php => FolderImageModification.php} | 53 +- .../Thelia/Form/Image/ImageModification.php | 183 ++ .../Thelia/Form/ProductImageModification.php | 53 + core/lib/Thelia/Model/CategoryImage.php | 35 +- core/lib/Thelia/Model/ContentImage.php | 14 + core/lib/Thelia/Model/FolderImage.php | 14 + core/lib/Thelia/Model/ProductImage.php | 14 + .../Thelia/Tests/Tools/FileManagerTest.php | 18 +- core/lib/Thelia/Tools/FileManager.php | 275 ++- local/modules/Cheque/images/cheque.png | Bin local/modules/FakeCB/images/mastercard.png | Bin local/modules/FakeCB/images/visa.png | Bin templates/admin/default/assets/js/dropzone.js | 1758 +++++++++++++++++ .../admin/default/assets/js/image-upload.js | 65 +- templates/admin/default/category-edit.html | 13 +- templates/admin/default/image-edit.html | 120 ++ .../default/includes/image-upload-form.html | 536 ++++- .../includes/image-upload-list-ajax.html | 23 + web/test_to_remove/admin-stats.json | 0 31 files changed, 3620 insertions(+), 510 deletions(-) create mode 100755 core/lib/Thelia/Core/Event/ImagesCreateOrUpdateEvent.php rename core/lib/Thelia/Form/{CategoryImageCreationForm.php => CategoryImageModification.php} (75%) rename core/lib/Thelia/Form/{Type/ImageCategoryType.php => ContentImageModification.php} (66%) rename core/lib/Thelia/Form/{Type/ImageType.php => FolderImageModification.php} (58%) create mode 100644 core/lib/Thelia/Form/Image/ImageModification.php create mode 100644 core/lib/Thelia/Form/ProductImageModification.php mode change 100644 => 100755 local/modules/Cheque/images/cheque.png mode change 100644 => 100755 local/modules/FakeCB/images/mastercard.png mode change 100644 => 100755 local/modules/FakeCB/images/visa.png create mode 100644 templates/admin/default/assets/js/dropzone.js create mode 100644 templates/admin/default/image-edit.html create mode 100644 templates/admin/default/includes/image-upload-list-ajax.html mode change 100644 => 100755 web/test_to_remove/admin-stats.json 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/Image.php b/core/lib/Thelia/Action/Image.php index 41ce957d4..989c9c516 100755 --- a/core/lib/Thelia/Action/Image.php +++ b/core/lib/Thelia/Action/Image.php @@ -28,6 +28,7 @@ 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; @@ -252,21 +253,20 @@ class Image extends BaseCachedFile implements EventSubscriberInterface } /** - * Take care of saving images in the database and file storage + * 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 saveImages(ImageCreateOrUpdateEvent $event) + public function saveImage(ImageCreateOrUpdateEvent $event) { - $fileManager = new FileManager($this->container); - $this->adminLogAppend( $this->container->get('thelia.translator')->trans( - 'Saving images for parent id %parentId% (%parentType%)', + 'Saving images for %parentName% parent id %parentId% (%parentType%)', array( + '%parentName%' => $event->getParentName(), '%parentId%' => $event->getParentId(), '%parentType%' => $event->getImageType() ), @@ -274,37 +274,75 @@ class Image extends BaseCachedFile implements EventSubscriberInterface ) ); - $newUploadedFiles = array(); - $uploadedFiles = $event->getUploadedFiles(); + $fileManager = new FileManager($this->container); + $model = $event->getModelImage(); - foreach ($event->getModelImages() as $i => $modelImage) { - // Save image to database in order to get image id - $fileManager->saveImage($event, $modelImage); + $nbModifiedLines = $model->save(); + $event->setModelImage($model); - 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); + 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()); + $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()) . '/' . $event->getOldModelImage()->getFile(); + unlink(str_replace('..', '', $url)); + + $newUploadedFile = $fileManager->copyUploadedFile( + $event->getModelImage()->getParentId(), + $event->getImageType(), + $event->getModelImage(), $event->getUploadedFile()); + $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 ImageCreateOrUpdateEvent $event Image event + * @param ImageDeleteEvent $event Image event * - * @throws \Thelia\Exception\ImageException + * @throws \Exception * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action */ public function deleteImage(ImageDeleteEvent $event) @@ -312,7 +350,7 @@ class Image extends BaseCachedFile implements EventSubscriberInterface $fileManager = new FileManager($this->container); try { - $fileManager->deleteImage($event->getImageToDelete()); + $fileManager->deleteImage($event->getImageToDelete(), $event->getImageType()); $this->adminLogAppend( $this->container->get('thelia.translator')->trans( @@ -336,22 +374,10 @@ class Image extends BaseCachedFile implements EventSubscriberInterface 'image' ) ); + throw $e; } } - /** - * 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. @@ -474,8 +500,9 @@ 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), + TheliaEvents::IMAGE_SAVE => array("saveImage", 128), + TheliaEvents::IMAGE_UPDATE => array("updateImage", 128), ); } } diff --git a/core/lib/Thelia/Config/Resources/config.xml b/core/lib/Thelia/Config/Resources/config.xml index 2c755550d..8832cdd2d 100755 --- a/core/lib/Thelia/Config/Resources/config.xml +++ b/core/lib/Thelia/Config/Resources/config.xml @@ -58,7 +58,7 @@
- + diff --git a/core/lib/Thelia/Config/Resources/routing/admin.xml b/core/lib/Thelia/Config/Resources/routing/admin.xml index 8b91500d1..c9e1be4d4 100755 --- a/core/lib/Thelia/Config/Resources/routing/admin.xml +++ b/core/lib/Thelia/Config/Resources/routing/admin.xml @@ -39,11 +39,24 @@ - - Thelia\Controller\Admin\FileController::saveImagesAction + + Thelia\Controller\Admin\FileController::saveImageAjaxAction .* \d+ + + Thelia\Controller\Admin\FileController::getImageListAjaxAction + .* + \d+ + + + Thelia\Controller\Admin\FileController::viewImageAction + \d+ + + + Thelia\Controller\Admin\FileController::updateImageAction + \d+ + Thelia\Controller\Admin\FileController::deleteImagesAction .* diff --git a/core/lib/Thelia/Controller/Admin/FileController.php b/core/lib/Thelia/Controller/Admin/FileController.php index 4be913bcb..2592db192 100755 --- a/core/lib/Thelia/Controller/Admin/FileController.php +++ b/core/lib/Thelia/Controller/Admin/FileController.php @@ -23,20 +23,20 @@ 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\ImageCreateOrUpdateEvent; +use Thelia\Core\Event\ImagesCreateOrUpdateEvent; 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; +use Thelia\Tools\FileManager; +use Thelia\Tools\Rest\ResponseRest; /** * Created by JetBrains PhpStorm. @@ -57,13 +57,14 @@ class FileController extends BaseAdminController * * @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) + public function saveFilesAction($parentId, $parentType) { + + } /** @@ -74,77 +75,165 @@ class FileController extends BaseAdminController * * @return Response */ - public function saveImagesAction($parentId, $parentType) + public function saveImageAjaxAction($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' - ); + $this->checkAuth('ADMIN', 'admin.image.save'); + $this->checkXmlHttpRequest(); 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'); + /** @var UploadedFile $fileBeingUploaded */ + $fileBeingUploaded = $this->getRequest()->files->get('file'); - // Get the form field values - $data = $form->getData(); + $fileManager = new FileManager($this->container); + $parentModel = $fileManager->getParentImageModel($parentType, $parentId); + $imageModel = $fileManager->getImageModel($parentType); - // 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 ($parentModel === null || $imageModel === null || $fileBeingUploaded === null) { + return new Response('', 404); } - if ($message !== false) { - // Mark the form as with error - $creationForm->setErrorMessage($message); + $defaultTitle = $parentModel->getTitle(); + $imageModel->setParentId($parentId); + $imageModel->setTitle($defaultTitle); - // Send the form and the error to the parser - $this->getParserContext() - ->addForm($creationForm) - ->setGeneralError($message); + $imageCreateOrUpdateEvent = new ImageCreateOrUpdateEvent( + $parentType, + $parentId + ); + $imageCreateOrUpdateEvent->setModelImage($imageModel); + $imageCreateOrUpdateEvent->setUploadedFile($fileBeingUploaded); + $imageCreateOrUpdateEvent->setParentName($parentModel->getTitle()); - // Set flash message to be displayed - $flashMessage = $this->getSession()->get('flashMessage'); - $flashMessage['imageMessage'] = $message; - $this->getSession()->set('flashMessage', $flashMessage); - } + + // Dispatch Event to the Action + $this->dispatch( + TheliaEvents::IMAGE_SAVE, + $imageCreateOrUpdateEvent + ); + + + return new ResponseRest(array('status' => true, 'message' => '')); } } - $this->redirectSuccess($creationForm); + 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 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()); + + return $this->render('image-edit', array( + 'imageId' => $imageId, + 'imageType' => $parentType, + 'redirectUrl' => $redirectUrl + )); + } 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 exists', $imageId)); + } + + $form = $this->validateForm($imageModification); + + $event = $this->createEventInstance($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) + ; + } + + return $this->render('image-edit', array( + 'imageId' => $imageId, + 'imageType' => $parentType + )); } /** @@ -160,14 +249,18 @@ class FileController extends BaseAdminController $this->checkAuth('ADMIN', 'admin.image.delete'); $this->checkXmlHttpRequest(); - $model = $this->getImageModel($parentType, $imageId); + $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 + $model, + $parentType ); // Dispatch Event to the Action @@ -218,73 +311,42 @@ class FileController extends BaseAdminController */ public function isParentTypeValid($parentType) { - return (in_array($parentType, ImageCreateOrUpdateEvent::getAvailableType())); + return (in_array($parentType, ImagesCreateOrUpdateEvent::getAvailableType())); } /** - * Get Image form + * Create Event instance * - * @param string $parentType Parent type - * @param Request $request Request Service + * @param string $parentType Parent Type owning images being saved + * @param \Thelia\Model\CategoryImage|\Thelia\Model\ProductImage|\Thelia\Model\ContentImage|\Thelia\Model\FolderImage $model Image model + * @param array $data Post data * - * @return null|CategoryImageCreationForm|ContentImageCreationForm|FolderImageCreationForm|ProductImageCreationForm - * - * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action + * @return ImageCreateOrUpdateEvent */ - public function getImageForm($parentType, Request $request) + private function createEventInstance($parentType, $model, $data) { - // @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; + $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']); } - return $creationForm; + $imageCreateEvent->setModelImage($model); + return $imageCreateEvent; } - /** - * 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 index afae53e7a..e7d5f3449 100755 --- a/core/lib/Thelia/Core/Event/ImageCreateOrUpdateEvent.php +++ b/core/lib/Thelia/Core/Event/ImageCreateOrUpdateEvent.php @@ -22,13 +22,14 @@ /*************************************************************************************/ namespace Thelia\Core\Event; +use Symfony\Component\HttpFoundation\File\UploadedFile; /** * Created by JetBrains PhpStorm. * Date: 9/18/13 * Time: 3:56 PM * - * Occurring when a Image list is saved + * Occurring when an Image is saved * * @package Image * @author Guillaume MOREL @@ -36,16 +37,15 @@ namespace Thelia\Core\Event; */ 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 \Thelia\Model\CategoryImage|\Thelia\Model\ProductImage|\Thelia\Model\ContentImage|\Thelia\Model\FolderImage model to save */ + protected $modelImage = array(); - /** @var array Images file to save */ - protected $uploadedFiles = 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; @@ -53,85 +53,56 @@ class ImageCreateOrUpdateEvent extends ActionEvent /** @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, - ); + /** @var string Parent name */ + protected $parentName = null; /** * Constructor * - * @param string $pictureType Picture type - * ex : ImageCreateOrUpdateEvent::TYPE_CATEGORY + * @param string $imageType Image type + * ex : FileManager::TYPE_CATEGORY * @param int $parentId Image parent id */ - public function __construct($pictureType, $parentId) + public function __construct($imageType, $parentId) { - $this->imageType = $pictureType; + $this->imageType = $imageType; $this->parentId = $parentId; } /** - * Set Images to save + * Set Image to save * - * @param array $images Thelia\Model\CategoryImage Array + * @param $image \Thelia\Model\CategoryImage|\Thelia\Model\ProductImage|\Thelia\Model\ContentImage|\Thelia\Model\FolderImage * * @return $this */ - public function setModelImages($images) + public function setModelImage($image) { - $this->modelImages = $images; + $this->modelImage = $image; return $this; } /** - * Get Images being saved + * Get Image being saved * - * @return array Array of Thelia\Model\CategoryImage + * @return \Thelia\Model\CategoryImage|\Thelia\Model\ProductImage|\Thelia\Model\ContentImage|\Thelia\Model\FolderImage */ - public function getModelImages() + public function getModelImage() { - 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; + return $this->modelImage; } /** * Set picture type * - * @param string $pictureType Picture type + * @param string $imageType Image type * * @return $this */ - public function setImageType($pictureType) + public function setImageType($imageType) { - $this->imageType = $pictureType; + $this->imageType = $imageType; return $this; } @@ -146,16 +117,6 @@ class ImageCreateOrUpdateEvent extends ActionEvent return $this->imageType; } - /** - * Get all image parent type available - * - * @return array - */ - public static function getAvailableType() - { - return self::$availableType; - } - /** * Set Image parent id * @@ -180,5 +141,73 @@ class ImageCreateOrUpdateEvent extends ActionEvent 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 index 54e042e79..9e890e999 100755 --- a/core/lib/Thelia/Core/Event/ImageDeleteEvent.php +++ b/core/lib/Thelia/Core/Event/ImageDeleteEvent.php @@ -41,6 +41,9 @@ use Thelia\Model\ProductImage; */ class ImageDeleteEvent extends ActionEvent { + /** @var string Image type */ + protected $imageType = null; + /** @var CategoryImage|ProductImage|ContentImage|FolderImage Image about to be deleted */ protected $imageToDelete = null; @@ -48,10 +51,37 @@ class ImageDeleteEvent extends ActionEvent * 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) + 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; } /** diff --git a/core/lib/Thelia/Core/Event/ImagesCreateOrUpdateEvent.php b/core/lib/Thelia/Core/Event/ImagesCreateOrUpdateEvent.php new file mode 100755 index 000000000..62c189fc5 --- /dev/null +++ b/core/lib/Thelia/Core/Event/ImagesCreateOrUpdateEvent.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 ImagesCreateOrUpdateEvent 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/TheliaEvents.php b/core/lib/Thelia/Core/Event/TheliaEvents.php index 7b47910d0..d41b6b5cd 100755 --- a/core/lib/Thelia/Core/Event/TheliaEvents.php +++ b/core/lib/Thelia/Core/Event/TheliaEvents.php @@ -304,6 +304,11 @@ final class TheliaEvents */ const IMAGE_SAVE = "action.saveImages"; + /** + * Save given images + */ + const IMAGE_UPDATE = "action.updateImages"; + /** * Delete given image */ diff --git a/core/lib/Thelia/Form/CategoryImageCreationForm.php b/core/lib/Thelia/Form/CategoryImageModification.php similarity index 75% rename from core/lib/Thelia/Form/CategoryImageCreationForm.php rename to core/lib/Thelia/Form/CategoryImageModification.php index d0b3be229..06674d9ef 100644 --- a/core/lib/Thelia/Form/CategoryImageCreationForm.php +++ b/core/lib/Thelia/Form/CategoryImageModification.php @@ -22,8 +22,9 @@ /*************************************************************************************/ namespace Thelia\Form; + use Thelia\Core\Translation\Translator; -use Thelia\Form\Type\ImageCategoryType; +use Thelia\Form\Image\ImageModification; /** * Created by JetBrains PhpStorm. @@ -36,36 +37,17 @@ use Thelia\Form\Type\ImageCategoryType; * @author Guillaume MOREL * */ -class CategoryImageCreationForm extends BaseForm +class CategoryImageModification extends ImageModification { - /** - * 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 + * This name must be unique * * @return string */ public function getName() { - return 'thelia_category_image_creation'; + return 'thelia_category_image_modification'; } } diff --git a/core/lib/Thelia/Form/Type/ImageCategoryType.php b/core/lib/Thelia/Form/ContentImageModification.php similarity index 66% rename from core/lib/Thelia/Form/Type/ImageCategoryType.php rename to core/lib/Thelia/Form/ContentImageModification.php index 7b8a3c474..f40fc5497 100644 --- a/core/lib/Thelia/Form/Type/ImageCategoryType.php +++ b/core/lib/Thelia/Form/ContentImageModification.php @@ -20,60 +20,34 @@ /* along with this program. If not, see . */ /* */ /*************************************************************************************/ -namespace Thelia\Form\Type; +namespace Thelia\Form; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\OptionsResolver\OptionsResolverInterface; -use Thelia\Form\Type\ImageType; + +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 a category picture + * Form allowing to process an image collection * * @package Image * @author Guillaume MOREL * */ -class ImageCategoryType extends ImageType +class ContentImageModification extends ImageModification { - /** - * 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 + * This name must be unique * * @return string */ public function getName() { - return 'thelia_category_picture_creation_type'; + return 'thelia_content_image_modification'; } } diff --git a/core/lib/Thelia/Form/Type/ImageType.php b/core/lib/Thelia/Form/FolderImageModification.php similarity index 58% rename from core/lib/Thelia/Form/Type/ImageType.php rename to core/lib/Thelia/Form/FolderImageModification.php index 662e2b3f7..a9305433b 100644 --- a/core/lib/Thelia/Form/Type/ImageType.php +++ b/core/lib/Thelia/Form/FolderImageModification.php @@ -20,63 +20,34 @@ /* along with this program. If not, see . */ /* */ /*************************************************************************************/ -namespace Thelia\Form\Type; +namespace Thelia\Form; -use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Validator\Constraints\Image; -use Symfony\Component\Validator\Constraints\NotBlank; + +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 a picture - * - * @todo refactor make all pictures using propel inheritance and factorise image behaviour into one single clean action + * Form allowing to process an image collection * * @package Image * @author Guillaume MOREL * */ -abstract class ImageType extends AbstractType +class FolderImageModification extends ImageModification { + /** - * Build a Picture form + * Get form name + * This name must be unique * - * @param FormBuilderInterface $builder Form builder - * @param array $options Form options + * @return string */ - public function buildForm(FormBuilderInterface $builder, array $options) + public function getName() { -// $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'); + return 'thelia_folder_image_modification'; } } diff --git a/core/lib/Thelia/Form/Image/ImageModification.php b/core/lib/Thelia/Form/Image/ImageModification.php new file mode 100644 index 000000000..4d0d10694 --- /dev/null +++ b/core/lib/Thelia/Form/Image/ImageModification.php @@ -0,0 +1,183 @@ +. */ +/* */ +/*************************************************************************************/ +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 +{ + +// public function __construct(Request $request, $type= "form", $data = array(), $options = array(), $isUpdate = false) +// { +// parent::__construct($request, $type, $data, $options); +// $this->setIsUpdate($isUpdate); +// } + +// /** @var bool Flag for update/create mode */ +// protected $isUpdate = false; + + /** + * + * 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() + { +// if (false === $this->isUpdate) { + $this->formBuilder->add( + 'file', + 'file', + array( + 'constraints' => array( +// new NotBlank(), + new Image( + array( + 'minWidth' => 200, + 'minHeight' => 200 + ) + ) + ), + 'label' => Translator::getInstance()->trans('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' + ) + ) + ); + + + } + +// /** +// * Set form in update or create mode +// * +// * @param boolean $isUpdate +// */ +// public function setIsUpdate($isUpdate) +// { +// $this->isUpdate = $isUpdate; +// } +// +// /** +// * Get for mode +// * +// * @return boolean +// */ +// public function getIsUpdate() +// { +// return $this->isUpdate; +// } + + + +} 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/CategoryImage.php b/core/lib/Thelia/Model/CategoryImage.php index bb7fa0dcb..17ee387b5 100755 --- a/core/lib/Thelia/Model/CategoryImage.php +++ b/core/lib/Thelia/Model/CategoryImage.php @@ -29,40 +29,17 @@ 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 + * Set Image parent id * - * @return null|string - */ - public function getAbsolutePath() - { - return null === $this->file - ? null - : $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 + * @param int $parentId parent id * - * @return null|string + * @return $this */ - public function getWebPath() + public function setParentId($parentId) { - return null === $this->file - ? null - : $this->getUploadDir().'/'.$this->file; - } + $this->setCategoryId($parentId); - - /** - * Get rid of the __DIR__ so it doesn't screw up - * when displaying uploaded doc/image in the view. - * @return string - */ - public function getUploadDir() - { - return THELIA_LOCAL_DIR . 'media/images/category'; + return $this; } /** diff --git a/core/lib/Thelia/Model/ContentImage.php b/core/lib/Thelia/Model/ContentImage.php index ddb33cbe8..b6a3085d4 100755 --- a/core/lib/Thelia/Model/ContentImage.php +++ b/core/lib/Thelia/Model/ContentImage.php @@ -26,6 +26,20 @@ 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 * diff --git a/core/lib/Thelia/Model/FolderImage.php b/core/lib/Thelia/Model/FolderImage.php index 0983f0244..f9491c9a5 100755 --- a/core/lib/Thelia/Model/FolderImage.php +++ b/core/lib/Thelia/Model/FolderImage.php @@ -26,6 +26,20 @@ 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 * diff --git a/core/lib/Thelia/Model/ProductImage.php b/core/lib/Thelia/Model/ProductImage.php index 058cbc863..6cc3ddd8c 100755 --- a/core/lib/Thelia/Model/ProductImage.php +++ b/core/lib/Thelia/Model/ProductImage.php @@ -26,6 +26,20 @@ 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 * diff --git a/core/lib/Thelia/Tests/Tools/FileManagerTest.php b/core/lib/Thelia/Tests/Tools/FileManagerTest.php index 7ee1fd493..25ecd52af 100644 --- a/core/lib/Thelia/Tests/Tools/FileManagerTest.php +++ b/core/lib/Thelia/Tests/Tools/FileManagerTest.php @@ -10,7 +10,7 @@ namespace Thelia\Tests\Type; -use Thelia\Core\Event\ImageCreateOrUpdateEvent; +use Thelia\Core\Event\ImagesCreateOrUpdateEvent; use Thelia\Exception\ImageException; use Thelia\Model\Admin; use Thelia\Model\ProductImage; @@ -94,7 +94,7 @@ class FileManagerTest extends \PHPUnit_Framework_TestCase { $newUploadedFiles = array(); - $actual = $fileManager->copyUploadedFile(24, ImageCreateOrUpdateEvent::TYPE_PRODUCT, $stubProductImage, $stubUploadedFile, $newUploadedFiles); + $actual = $fileManager->copyUploadedFile(24, ImagesCreateOrUpdateEvent::TYPE_PRODUCT, $stubProductImage, $stubUploadedFile, $newUploadedFiles); $this->assertCount(1, $actual); } @@ -176,7 +176,7 @@ class FileManagerTest extends \PHPUnit_Framework_TestCase { $newUploadedFiles = array(); - $actual = $fileManager->copyUploadedFile(24, ImageCreateOrUpdateEvent::TYPE_PRODUCT, $stubProductImage, $stubUploadedFile, $newUploadedFiles); + $actual = $fileManager->copyUploadedFile(24, ImagesCreateOrUpdateEvent::TYPE_PRODUCT, $stubProductImage, $stubUploadedFile, $newUploadedFiles); } @@ -198,7 +198,7 @@ class FileManagerTest extends \PHPUnit_Framework_TestCase { $fileManager = new FileManager($stubContainer); - $event = new ImageCreateOrUpdateEvent(ImageCreateOrUpdateEvent::TYPE_PRODUCT, 24); + $event = new ImagesCreateOrUpdateEvent(ImagesCreateOrUpdateEvent::TYPE_PRODUCT, 24); $expected = 10; $actual = $fileManager->saveImage($event, $stubProductImage); @@ -224,7 +224,7 @@ class FileManagerTest extends \PHPUnit_Framework_TestCase { $fileManager = new FileManager($stubContainer); - $event = new ImageCreateOrUpdateEvent(ImageCreateOrUpdateEvent::TYPE_CATEGORY, 24); + $event = new ImagesCreateOrUpdateEvent(ImagesCreateOrUpdateEvent::TYPE_CATEGORY, 24); $expected = 10; $actual = $fileManager->saveImage($event, $stubCategoryImage); @@ -250,7 +250,7 @@ class FileManagerTest extends \PHPUnit_Framework_TestCase { $fileManager = new FileManager($stubContainer); - $event = new ImageCreateOrUpdateEvent(ImageCreateOrUpdateEvent::TYPE_FOLDER, 24); + $event = new ImagesCreateOrUpdateEvent(ImagesCreateOrUpdateEvent::TYPE_FOLDER, 24); $expected = 10; $actual = $fileManager->saveImage($event, $stubFolderImage); @@ -276,7 +276,7 @@ class FileManagerTest extends \PHPUnit_Framework_TestCase { $fileManager = new FileManager($stubContainer); - $event = new ImageCreateOrUpdateEvent(ImageCreateOrUpdateEvent::TYPE_CONTENT, 24); + $event = new ImagesCreateOrUpdateEvent(ImagesCreateOrUpdateEvent::TYPE_CONTENT, 24); $expected = 10; $actual = $fileManager->saveImage($event, $stubContentImage); @@ -302,7 +302,7 @@ class FileManagerTest extends \PHPUnit_Framework_TestCase { ->method('save') ->will($this->returnValue(10)); - $event = new ImageCreateOrUpdateEvent('bad', 24); + $event = new ImagesCreateOrUpdateEvent('bad', 24); $modelImage = new ProductImage(); $fileManager->saveImage($event, $modelImage); @@ -326,7 +326,7 @@ class FileManagerTest extends \PHPUnit_Framework_TestCase { ->method('save') ->will($this->returnValue(0)); - $event = new ImageCreateOrUpdateEvent(ImageCreateOrUpdateEvent::TYPE_PRODUCT, 24); + $event = new ImagesCreateOrUpdateEvent(ImagesCreateOrUpdateEvent::TYPE_PRODUCT, 24); $fileManager->saveImage($event, $stubProductImage); } diff --git a/core/lib/Thelia/Tools/FileManager.php b/core/lib/Thelia/Tools/FileManager.php index 533dcb203..4b1d90e89 100644 --- a/core/lib/Thelia/Tools/FileManager.php +++ b/core/lib/Thelia/Tools/FileManager.php @@ -24,14 +24,27 @@ 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\Core\Event\ImagesCreateOrUpdateEvent; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\Translation\Translator; use Thelia\Exception\ImageException; +use Thelia\Form\CategoryImageModification; +use Thelia\Form\ContentImageModification; +use Thelia\Form\FolderImageModification; +use Thelia\Form\ProductImageModification; use Thelia\Model\AdminLog; -use Thelia\Model\Base\CategoryImage; +use Thelia\Model\CategoryImage; +use Thelia\Model\CategoryImageQuery; +use Thelia\Model\CategoryQuery; use Thelia\Model\ContentImage; +use Thelia\Model\ContentImageQuery; +use Thelia\Model\ContentQuery; use Thelia\Model\FolderImage; +use Thelia\Model\FolderImageQuery; +use Thelia\Model\FolderQuery; use Thelia\Model\ProductImage; +use Thelia\Model\ProductImageQuery; +use Thelia\Model\ProductQuery; /** * Created by JetBrains PhpStorm. @@ -46,6 +59,12 @@ use Thelia\Model\ProductImage; */ class FileManager { + CONST TYPE_PRODUCT = 'product'; + CONST TYPE_CATEGORY = 'category'; + CONST TYPE_CONTENT = 'content'; + CONST TYPE_FOLDER = 'folder'; + CONST TYPE_MODULE = 'module'; + /** @var ContainerInterface Service Container */ protected $container = null; @@ -70,20 +89,15 @@ class FileManager * @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 + * @return UploadedFile */ - public function copyUploadedFile($parentId, $imageType, $modelImage, $uploadedFile, $newUploadedFiles) + public function copyUploadedFile($parentId, $imageType, $modelImage, $uploadedFile) { if ($uploadedFile !== null) { - $directory = $modelImage->getUploadDir(); - $fileName = $this->sanitizeFileName( - $uploadedFile->getClientOriginalName() . "-" . $modelImage->getId() . "." . strtolower( - $uploadedFile->getClientOriginalExtension() - ) - ); + $directory = $this->getUploadDir($imageType); + $fileName = $this->renameFile($modelImage->getId(), $uploadedFile); $this->adminLogAppend( $this->translator->trans( @@ -98,7 +112,7 @@ class FileManager ) ); - $newUploadedFiles[] = array('file' => $uploadedFile->move($directory, $fileName)); + $newUploadedFile = $uploadedFile->move($directory, $fileName); $modelImage->setFile($fileName); if (!$modelImage->save()) { @@ -112,38 +126,38 @@ class FileManager } } - return $newUploadedFiles; + return $newUploadedFile; } /** * Save image into the database * - * @param ImageCreateOrUpdateEvent $event Image event + * @param ImagesCreateOrUpdateEvent $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) + public function saveImage(ImagesCreateOrUpdateEvent $event, $modelImage) { $nbModifiedLines = 0; if ($modelImage->getFile() !== null) { switch ($event->getImageType()) { - case ImageCreateOrUpdateEvent::TYPE_PRODUCT: + case ImagesCreateOrUpdateEvent::TYPE_PRODUCT: /** @var ProductImage $modelImage */ $modelImage->setProductId($event->getParentId()); break; - case ImageCreateOrUpdateEvent::TYPE_CATEGORY: + case ImagesCreateOrUpdateEvent::TYPE_CATEGORY: /** @var CategoryImage $modelImage */ $modelImage->setCategoryId($event->getParentId()); break; - case ImageCreateOrUpdateEvent::TYPE_CONTENT: + case ImagesCreateOrUpdateEvent::TYPE_CONTENT: /** @var ContentImage $modelImage */ $modelImage->setContentId($event->getParentId()); break; - case ImageCreateOrUpdateEvent::TYPE_FOLDER: + case ImagesCreateOrUpdateEvent::TYPE_FOLDER: /** @var FolderImage $modelImage */ $modelImage->setFolderId($event->getParentId()); break; @@ -213,10 +227,227 @@ class FileManager * Delete image from file storage and database * * @param CategoryImage|ProductImage|ContentImage|FolderImage $imageModel Image being deleted + * @param string $parentType Parent type */ - public function deleteImage($imageModel) + public function deleteImage($imageModel, $parentType) { - unlink($imageModel->getAbsolutePath()); + $url = $this->getUploadDir($parentType) . '/' . $imageModel->getFile(); + unlink(str_replace('..', '', $url)); $imageModel->delete(); } + + + /** + * Get image model from type + * + * @param string $parentType Parent type + * + * @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 ImagesCreateOrUpdateEvent::TYPE_PRODUCT: + $model = new ProductImage(); + break; + case ImagesCreateOrUpdateEvent::TYPE_CATEGORY: + $model = new CategoryImage(); + break; + case ImagesCreateOrUpdateEvent::TYPE_CONTENT: + $model = new ContentImage(); + break; + case ImagesCreateOrUpdateEvent::TYPE_FOLDER: + $model = new FolderImage(); + break; + default: + $model = null; + } + + return $model; + } + + /** + * Get image model query from type + * + * @param string $parentType + * + * @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 ImagesCreateOrUpdateEvent::TYPE_PRODUCT: + $model = new ProductImageQuery(); + break; + case ImagesCreateOrUpdateEvent::TYPE_CATEGORY: + $model = new CategoryImageQuery(); + break; + case ImagesCreateOrUpdateEvent::TYPE_CONTENT: + $model = new ContentImageQuery(); + break; + case ImagesCreateOrUpdateEvent::TYPE_FOLDER: + $model = new FolderImageQuery(); + break; + default: + $model = null; + } + + return $model; + } + + /** + * Get image parent model from type + * + * @param string $parentType + * @param int $parentId + * + * @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 getParentImageModel($parentType, $parentId) + { + switch ($parentType) { + case ImagesCreateOrUpdateEvent::TYPE_PRODUCT: + $model = ProductQuery::create()->findPk($parentId); + break; + case ImagesCreateOrUpdateEvent::TYPE_CATEGORY: + $model = CategoryQuery::create()->findPk($parentId); + break; + case ImagesCreateOrUpdateEvent::TYPE_CONTENT: + $model = ContentQuery::create()->findPk($parentId); + break; + case ImagesCreateOrUpdateEvent::TYPE_FOLDER: + $model = FolderQuery::create()->findPk($parentId); + break; + default: + $model = null; + } + + return $model; + } + + /** + * Get image parent model from type + * + * @param string $parentType Parent type + * @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 ImagesCreateOrUpdateEvent::TYPE_PRODUCT: + $form = new ProductImageModification($request); + break; + case ImagesCreateOrUpdateEvent::TYPE_CATEGORY: + $form = new CategoryImageModification($request); + break; + case ImagesCreateOrUpdateEvent::TYPE_CONTENT: + $form = new ContentImageModification($request); + break; + case ImagesCreateOrUpdateEvent::TYPE_FOLDER: + $form = new FolderImageModification($request); + break; + default: + $model = null; + } + + return $form; + + } + + /** + * Get image upload dir + * + * @param string $parentType Parent type + * + * @return string Uri + */ + public function getUploadDir($parentType) + { + switch ($parentType) { + case ImagesCreateOrUpdateEvent::TYPE_PRODUCT: + $uri = THELIA_LOCAL_DIR . 'media/images/' . ImagesCreateOrUpdateEvent::TYPE_PRODUCT; + break; + case ImagesCreateOrUpdateEvent::TYPE_CATEGORY: + $uri = THELIA_LOCAL_DIR . 'media/images/' . ImagesCreateOrUpdateEvent::TYPE_CATEGORY; + break; + case ImagesCreateOrUpdateEvent::TYPE_CONTENT: + $uri = THELIA_LOCAL_DIR . 'media/images/' . ImagesCreateOrUpdateEvent::TYPE_CONTENT; + break; + case ImagesCreateOrUpdateEvent::TYPE_FOLDER: + $uri = THELIA_LOCAL_DIR . 'media/images/' . ImagesCreateOrUpdateEvent::TYPE_FOLDER; + break; + default: + $uri = null; + } + + return $uri; + + } + + /** + * Deduce image redirecting URL from parent type + * + * @param string $parentType Parent type + * @param int $parentId Parent id + * @return string + */ + public function getRedirectionUrl($parentType, $parentId) + { + switch ($parentType) { + case ImagesCreateOrUpdateEvent::TYPE_PRODUCT: + $uri = '/admin/products/update?product_id=' . $parentId; + break; + case ImagesCreateOrUpdateEvent::TYPE_CATEGORY: + $uri = '/admin/categories/update?category_id=' . $parentId; + break; + case ImagesCreateOrUpdateEvent::TYPE_CONTENT: + $uri = '/admin/content/update/' . $parentId; + break; + case ImagesCreateOrUpdateEvent::TYPE_FOLDER: + $uri = '/admin/folders/update?folder_id=' . $parentId; + break; + default: + $uri = false; + } + + return $uri; + + } + + /** @var array Available image parent type */ + public static $availableType = array( + self::TYPE_PRODUCT, + self::TYPE_CATEGORY, + self::TYPE_CONTENT, + self::TYPE_FOLDER, + self::TYPE_MODULE + ); + + /** + * 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(); + $fileName = $this->sanitizeFileName( + str_replace('.' . $extension, '', $uploadedFile->getClientOriginalName()) . "-" . $modelId . "." . strtolower( + $extension + ) + ); + return $fileName; + } } \ No newline at end of file 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/assets/js/dropzone.js b/templates/admin/default/assets/js/dropzone.js new file mode 100644 index 000000000..3e68289ad --- /dev/null +++ b/templates/admin/default/assets/js/dropzone.js @@ -0,0 +1,1758 @@ +;(function(){ + + /** + * Require the given path. + * + * @param {String} path + * @return {Object} exports + * @api public + */ + + function require(path, parent, orig) { + var resolved = require.resolve(path); + + // lookup failed + if (null == resolved) { + orig = orig || path; + parent = parent || 'root'; + var err = new Error('Failed to require "' + orig + '" from "' + parent + '"'); + err.path = orig; + err.parent = parent; + err.require = true; + throw err; + } + + var module = require.modules[resolved]; + + // perform real require() + // by invoking the module's + // registered function + if (!module.exports) { + module.exports = {}; + module.client = module.component = true; + module.call(this, module.exports, require.relative(resolved), module); + } + + return module.exports; + } + + /** + * Registered modules. + */ + + require.modules = {}; + + /** + * Registered aliases. + */ + + require.aliases = {}; + + /** + * Resolve `path`. + * + * Lookup: + * + * - PATH/index.js + * - PATH.js + * - PATH + * + * @param {String} path + * @return {String} path or null + * @api private + */ + + require.resolve = function(path) { + if (path.charAt(0) === '/') path = path.slice(1); + + var paths = [ + path, + path + '.js', + path + '.json', + path + '/index.js', + path + '/index.json' + ]; + + for (var i = 0; i < paths.length; i++) { + var path = paths[i]; + if (require.modules.hasOwnProperty(path)) return path; + if (require.aliases.hasOwnProperty(path)) return require.aliases[path]; + } + }; + + /** + * Normalize `path` relative to the current path. + * + * @param {String} curr + * @param {String} path + * @return {String} + * @api private + */ + + require.normalize = function(curr, path) { + var segs = []; + + if ('.' != path.charAt(0)) return path; + + curr = curr.split('/'); + path = path.split('/'); + + for (var i = 0; i < path.length; ++i) { + if ('..' == path[i]) { + curr.pop(); + } else if ('.' != path[i] && '' != path[i]) { + segs.push(path[i]); + } + } + + return curr.concat(segs).join('/'); + }; + + /** + * Register module at `path` with callback `definition`. + * + * @param {String} path + * @param {Function} definition + * @api private + */ + + require.register = function(path, definition) { + require.modules[path] = definition; + }; + + /** + * Alias a module definition. + * + * @param {String} from + * @param {String} to + * @api private + */ + + require.alias = function(from, to) { + if (!require.modules.hasOwnProperty(from)) { + throw new Error('Failed to alias "' + from + '", it does not exist'); + } + require.aliases[to] = from; + }; + + /** + * Return a require function relative to the `parent` path. + * + * @param {String} parent + * @return {Function} + * @api private + */ + + require.relative = function(parent) { + var p = require.normalize(parent, '..'); + + /** + * lastIndexOf helper. + */ + + function lastIndexOf(arr, obj) { + var i = arr.length; + while (i--) { + if (arr[i] === obj) return i; + } + return -1; + } + + /** + * The relative require() itself. + */ + + function localRequire(path) { + var resolved = localRequire.resolve(path); + return require(resolved, parent, path); + } + + /** + * Resolve relative to the parent. + */ + + localRequire.resolve = function(path) { + var c = path.charAt(0); + if ('/' == c) return path.slice(1); + if ('.' == c) return require.normalize(p, path); + + // resolve deps by returning + // the dep in the nearest "deps" + // directory + var segs = parent.split('/'); + var i = lastIndexOf(segs, 'deps') + 1; + if (!i) i = 0; + path = segs.slice(0, i + 1).join('/') + '/deps/' + path; + return path; + }; + + /** + * Check if module is defined at `path`. + */ + + localRequire.exists = function(path) { + return require.modules.hasOwnProperty(localRequire.resolve(path)); + }; + + return localRequire; + }; + require.register("component-emitter/index.js", function(exports, require, module){ + + /** + * Expose `Emitter`. + */ + + module.exports = Emitter; + + /** + * Initialize a new `Emitter`. + * + * @api public + */ + + function Emitter(obj) { + if (obj) return mixin(obj); + }; + + /** + * Mixin the emitter properties. + * + * @param {Object} obj + * @return {Object} + * @api private + */ + + function mixin(obj) { + for (var key in Emitter.prototype) { + obj[key] = Emitter.prototype[key]; + } + return obj; + } + + /** + * Listen on the given `event` with `fn`. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.on = function(event, fn){ + this._callbacks = this._callbacks || {}; + (this._callbacks[event] = this._callbacks[event] || []) + .push(fn); + return this; + }; + + /** + * Adds an `event` listener that will be invoked a single + * time then automatically removed. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.once = function(event, fn){ + var self = this; + this._callbacks = this._callbacks || {}; + + function on() { + self.off(event, on); + fn.apply(this, arguments); + } + + fn._off = on; + this.on(event, on); + return this; + }; + + /** + * Remove the given callback for `event` or all + * registered callbacks. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.off = + Emitter.prototype.removeListener = + Emitter.prototype.removeAllListeners = function(event, fn){ + this._callbacks = this._callbacks || {}; + var callbacks = this._callbacks[event]; + if (!callbacks) return this; + + // remove all handlers + if (1 == arguments.length) { + delete this._callbacks[event]; + return this; + } + + // remove specific handler + var i = callbacks.indexOf(fn._off || fn); + if (~i) callbacks.splice(i, 1); + return this; + }; + + /** + * Emit `event` with the given args. + * + * @param {String} event + * @param {Mixed} ... + * @return {Emitter} + */ + + Emitter.prototype.emit = function(event){ + this._callbacks = this._callbacks || {}; + var args = [].slice.call(arguments, 1) + , callbacks = this._callbacks[event]; + + if (callbacks) { + callbacks = callbacks.slice(0); + for (var i = 0, len = callbacks.length; i < len; ++i) { + callbacks[i].apply(this, args); + } + } + + return this; + }; + + /** + * Return array of callbacks for `event`. + * + * @param {String} event + * @return {Array} + * @api public + */ + + Emitter.prototype.listeners = function(event){ + this._callbacks = this._callbacks || {}; + return this._callbacks[event] || []; + }; + + /** + * Check if this emitter has `event` handlers. + * + * @param {String} event + * @return {Boolean} + * @api public + */ + + Emitter.prototype.hasListeners = function(event){ + return !! this.listeners(event).length; + }; + + }); + require.register("dropzone/index.js", function(exports, require, module){ + + + /** + * Exposing dropzone + */ + module.exports = require("./lib/dropzone.js"); + + }); + require.register("dropzone/lib/dropzone.js", function(exports, require, module){ + /* + # + # More info at [www.dropzonejs.com](http://www.dropzonejs.com) + # + # Copyright (c) 2012, Matias Meno + # + # Permission is hereby granted, free of charge, to any person obtaining a copy + # of this software and associated documentation files (the "Software"), to deal + # in the Software without restriction, including without limitation the rights + # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + # copies of the Software, and to permit persons to whom the Software is + # furnished to do so, subject to the following conditions: + # + # The above copyright notice and this permission notice shall be included in + # all copies or substantial portions of the Software. + # + # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + # THE SOFTWARE. + # + */ + + + (function() { + var Dropzone, Em, camelize, contentLoaded, noop, without, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + __slice = [].slice; + + Em = typeof Emitter !== "undefined" && Emitter !== null ? Emitter : require("emitter"); + + noop = function() {}; + + Dropzone = (function(_super) { + var extend; + + __extends(Dropzone, _super); + + /* + This is a list of all available events you can register on a dropzone object. + + You can register an event handler like this: + + dropzone.on("dragEnter", function() { }); + */ + + + Dropzone.prototype.events = ["drop", "dragstart", "dragend", "dragenter", "dragover", "dragleave", "selectedfiles", "addedfile", "removedfile", "thumbnail", "error", "errormultiple", "processing", "processingmultiple", "uploadprogress", "totaluploadprogress", "sending", "sendingmultiple", "success", "successmultiple", "canceled", "canceledmultiple", "complete", "completemultiple", "reset", "maxfilesexceeded"]; + + Dropzone.prototype.defaultOptions = { + url: null, + method: "post", + withCredentials: false, + parallelUploads: 2, + uploadMultiple: false, + maxFilesize: 256, + paramName: "file", + createImageThumbnails: true, + maxThumbnailFilesize: 10, + thumbnailWidth: 100, + thumbnailHeight: 100, + maxFiles: null, + params: {}, + clickable: true, + ignoreHiddenFiles: true, + acceptedFiles: null, + acceptedMimeTypes: null, + autoProcessQueue: true, + addRemoveLinks: false, + previewsContainer: null, + dictDefaultMessage: "Drop files here to upload", + dictFallbackMessage: "Your browser does not support drag'n'drop file uploads.", + dictFallbackText: "Please use the fallback form below to upload your files like in the olden days.", + dictFileTooBig: "File is too big ({{filesize}}MB). Max filesize: {{maxFilesize}}MB.", + dictInvalidFileType: "You can't upload files of this type.", + dictResponseError: "Server responded with {{statusCode}} code.", + dictCancelUpload: "Cancel upload", + dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?", + dictRemoveFile: "Remove file", + dictRemoveFileConfirmation: null, + dictMaxFilesExceeded: "You can only upload {{maxFiles}} files.", + accept: function(file, done) { + return done(); + }, + init: function() { + return noop; + }, + forceFallback: false, + fallback: function() { + var child, messageElement, span, _i, _len, _ref; + this.element.className = "" + this.element.className + " dz-browser-not-supported"; + _ref = this.element.getElementsByTagName("div"); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + child = _ref[_i]; + if (/(^| )dz-message($| )/.test(child.className)) { + messageElement = child; + child.className = "dz-message"; + continue; + } + } + if (!messageElement) { + messageElement = Dropzone.createElement("
"); + this.element.appendChild(messageElement); + } + span = messageElement.getElementsByTagName("span")[0]; + if (span) { + span.textContent = this.options.dictFallbackMessage; + } + return this.element.appendChild(this.getFallbackForm()); + }, + resize: function(file) { + var info, srcRatio, trgRatio; + info = { + srcX: 0, + srcY: 0, + srcWidth: file.width, + srcHeight: file.height + }; + srcRatio = file.width / file.height; + trgRatio = this.options.thumbnailWidth / this.options.thumbnailHeight; + if (file.height < this.options.thumbnailHeight || file.width < this.options.thumbnailWidth) { + info.trgHeight = info.srcHeight; + info.trgWidth = info.srcWidth; + } else { + if (srcRatio > trgRatio) { + info.srcHeight = file.height; + info.srcWidth = info.srcHeight * trgRatio; + } else { + info.srcWidth = file.width; + info.srcHeight = info.srcWidth / trgRatio; + } + } + info.srcX = (file.width - info.srcWidth) / 2; + info.srcY = (file.height - info.srcHeight) / 2; + return info; + }, + /* + Those functions register themselves to the events on init and handle all + the user interface specific stuff. Overwriting them won't break the upload + but can break the way it's displayed. + You can overwrite them if you don't like the default behavior. If you just + want to add an additional event handler, register it on the dropzone object + and don't overwrite those options. + */ + + drop: function(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + dragstart: noop, + dragend: function(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + dragenter: function(e) { + return this.element.classList.add("dz-drag-hover"); + }, + dragover: function(e) { + return this.element.classList.add("dz-drag-hover"); + }, + dragleave: function(e) { + return this.element.classList.remove("dz-drag-hover"); + }, + selectedfiles: function(files) { + if (this.element === this.previewsContainer) { + return this.element.classList.add("dz-started"); + } + }, + reset: function() { + return this.element.classList.remove("dz-started"); + }, + addedfile: function(file) { + var _this = this; + file.previewElement = Dropzone.createElement(this.options.previewTemplate); + file.previewTemplate = file.previewElement; + this.previewsContainer.appendChild(file.previewElement); + file.previewElement.querySelector("[data-dz-name]").textContent = file.name; + file.previewElement.querySelector("[data-dz-size]").innerHTML = this.filesize(file.size); + if (this.options.addRemoveLinks) { + file._removeLink = Dropzone.createElement("" + this.options.dictRemoveFile + ""); + file._removeLink.addEventListener("click", function(e) { + e.preventDefault(); + e.stopPropagation(); + if (file.status === Dropzone.UPLOADING) { + return Dropzone.confirm(_this.options.dictCancelUploadConfirmation, function() { + return _this.removeFile(file); + }); + } else { + if (_this.options.dictRemoveFileConfirmation) { + return Dropzone.confirm(_this.options.dictRemoveFileConfirmation, function() { + return _this.removeFile(file); + }); + } else { + return _this.removeFile(file); + } + } + }); + file.previewElement.appendChild(file._removeLink); + } + return this._updateMaxFilesReachedClass(); + }, + removedfile: function(file) { + var _ref; + if ((_ref = file.previewElement) != null) { + _ref.parentNode.removeChild(file.previewElement); + } + return this._updateMaxFilesReachedClass(); + }, + thumbnail: function(file, dataUrl) { + var thumbnailElement; + file.previewElement.classList.remove("dz-file-preview"); + file.previewElement.classList.add("dz-image-preview"); + thumbnailElement = file.previewElement.querySelector("[data-dz-thumbnail]"); + thumbnailElement.alt = file.name; + return thumbnailElement.src = dataUrl; + }, + error: function(file, message) { + file.previewElement.classList.add("dz-error"); + return file.previewElement.querySelector("[data-dz-errormessage]").textContent = message; + }, + errormultiple: noop, + processing: function(file) { + file.previewElement.classList.add("dz-processing"); + if (file._removeLink) { + return file._removeLink.textContent = this.options.dictCancelUpload; + } + }, + processingmultiple: noop, + uploadprogress: function(file, progress, bytesSent) { + return file.previewElement.querySelector("[data-dz-uploadprogress]").style.width = "" + progress + "%"; + }, + totaluploadprogress: noop, + sending: noop, + sendingmultiple: noop, + success: function(file) { + return file.previewElement.classList.add("dz-success"); + }, + successmultiple: noop, + canceled: function(file) { + return this.emit("error", file, "Upload canceled."); + }, + canceledmultiple: noop, + complete: function(file) { + if (file._removeLink) { + return file._removeLink.textContent = this.options.dictRemoveFile; + } + }, + completemultiple: noop, + maxfilesexceeded: noop, + previewTemplate: "
\n
\n
\n
\n \n
\n
\n
✔
\n
✘
\n
\n
" + }; + + extend = function() { + var key, object, objects, target, val, _i, _len; + target = arguments[0], objects = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + for (_i = 0, _len = objects.length; _i < _len; _i++) { + object = objects[_i]; + for (key in object) { + val = object[key]; + target[key] = val; + } + } + return target; + }; + + function Dropzone(element, options) { + var elementOptions, fallback, _ref; + this.element = element; + this.version = Dropzone.version; + this.defaultOptions.previewTemplate = this.defaultOptions.previewTemplate.replace(/\n*/g, ""); + this.clickableElements = []; + this.listeners = []; + this.files = []; + if (typeof this.element === "string") { + this.element = document.querySelector(this.element); + } + if (!(this.element && (this.element.nodeType != null))) { + throw new Error("Invalid dropzone element."); + } + if (this.element.dropzone) { + throw new Error("Dropzone already attached."); + } + Dropzone.instances.push(this); + element.dropzone = this; + elementOptions = (_ref = Dropzone.optionsForElement(this.element)) != null ? _ref : {}; + this.options = extend({}, this.defaultOptions, elementOptions, options != null ? options : {}); + if (this.options.forceFallback || !Dropzone.isBrowserSupported()) { + return this.options.fallback.call(this); + } + if (this.options.url == null) { + this.options.url = this.element.getAttribute("action"); + } + if (!this.options.url) { + throw new Error("No URL provided."); + } + if (this.options.acceptedFiles && this.options.acceptedMimeTypes) { + throw new Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated."); + } + if (this.options.acceptedMimeTypes) { + this.options.acceptedFiles = this.options.acceptedMimeTypes; + delete this.options.acceptedMimeTypes; + } + this.options.method = this.options.method.toUpperCase(); + if ((fallback = this.getExistingFallback()) && fallback.parentNode) { + fallback.parentNode.removeChild(fallback); + } + if (this.options.previewsContainer) { + this.previewsContainer = Dropzone.getElement(this.options.previewsContainer, "previewsContainer"); + } else { + this.previewsContainer = this.element; + } + if (this.options.clickable) { + if (this.options.clickable === true) { + this.clickableElements = [this.element]; + } else { + this.clickableElements = Dropzone.getElements(this.options.clickable, "clickable"); + } + } + this.init(); + } + + Dropzone.prototype.getAcceptedFiles = function() { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.accepted) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.getRejectedFiles = function() { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (!file.accepted) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.getQueuedFiles = function() { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.status === Dropzone.QUEUED) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.getUploadingFiles = function() { + var file, _i, _len, _ref, _results; + _ref = this.files; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + if (file.status === Dropzone.UPLOADING) { + _results.push(file); + } + } + return _results; + }; + + Dropzone.prototype.init = function() { + var eventName, noPropagation, setupHiddenFileInput, _i, _len, _ref, _ref1, + _this = this; + if (this.element.tagName === "form") { + this.element.setAttribute("enctype", "multipart/form-data"); + } + if (this.element.classList.contains("dropzone") && !this.element.querySelector(".dz-message")) { + this.element.appendChild(Dropzone.createElement("
" + this.options.dictDefaultMessage + "
")); + } + if (this.clickableElements.length) { + setupHiddenFileInput = function() { + if (_this.hiddenFileInput) { + document.body.removeChild(_this.hiddenFileInput); + } + _this.hiddenFileInput = document.createElement("input"); + _this.hiddenFileInput.setAttribute("type", "file"); + _this.hiddenFileInput.setAttribute("multiple", "multiple"); + if (_this.options.acceptedFiles != null) { + _this.hiddenFileInput.setAttribute("accept", _this.options.acceptedFiles); + } + _this.hiddenFileInput.style.visibility = "hidden"; + _this.hiddenFileInput.style.position = "absolute"; + _this.hiddenFileInput.style.top = "0"; + _this.hiddenFileInput.style.left = "0"; + _this.hiddenFileInput.style.height = "0"; + _this.hiddenFileInput.style.width = "0"; + document.body.appendChild(_this.hiddenFileInput); + return _this.hiddenFileInput.addEventListener("change", function() { + var files; + files = _this.hiddenFileInput.files; + if (files.length) { + _this.emit("selectedfiles", files); + _this.handleFiles(files); + } + return setupHiddenFileInput(); + }); + }; + setupHiddenFileInput(); + } + this.URL = (_ref = window.URL) != null ? _ref : window.webkitURL; + _ref1 = this.events; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + eventName = _ref1[_i]; + this.on(eventName, this.options[eventName]); + } + this.on("uploadprogress", function() { + return _this.updateTotalUploadProgress(); + }); + this.on("removedfile", function() { + return _this.updateTotalUploadProgress(); + }); + this.on("canceled", function(file) { + return _this.emit("complete", file); + }); + noPropagation = function(e) { + e.stopPropagation(); + if (e.preventDefault) { + return e.preventDefault(); + } else { + return e.returnValue = false; + } + }; + this.listeners = [ + { + element: this.element, + events: { + "dragstart": function(e) { + return _this.emit("dragstart", e); + }, + "dragenter": function(e) { + noPropagation(e); + return _this.emit("dragenter", e); + }, + "dragover": function(e) { + noPropagation(e); + return _this.emit("dragover", e); + }, + "dragleave": function(e) { + return _this.emit("dragleave", e); + }, + "drop": function(e) { + noPropagation(e); + return _this.drop(e); + }, + "dragend": function(e) { + return _this.emit("dragend", e); + } + } + } + ]; + this.clickableElements.forEach(function(clickableElement) { + return _this.listeners.push({ + element: clickableElement, + events: { + "click": function(evt) { + if ((clickableElement !== _this.element) || (evt.target === _this.element || Dropzone.elementInside(evt.target, _this.element.querySelector(".dz-message")))) { + return _this.hiddenFileInput.click(); + } + } + } + }); + }); + this.enable(); + return this.options.init.call(this); + }; + + Dropzone.prototype.destroy = function() { + var _ref; + this.disable(); + this.removeAllFiles(true); + if ((_ref = this.hiddenFileInput) != null ? _ref.parentNode : void 0) { + this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput); + this.hiddenFileInput = null; + } + return delete this.element.dropzone; + }; + + Dropzone.prototype.updateTotalUploadProgress = function() { + var acceptedFiles, file, totalBytes, totalBytesSent, totalUploadProgress, _i, _len, _ref; + totalBytesSent = 0; + totalBytes = 0; + acceptedFiles = this.getAcceptedFiles(); + if (acceptedFiles.length) { + _ref = this.getAcceptedFiles(); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + file = _ref[_i]; + totalBytesSent += file.upload.bytesSent; + totalBytes += file.upload.total; + } + totalUploadProgress = 100 * totalBytesSent / totalBytes; + } else { + totalUploadProgress = 100; + } + return this.emit("totaluploadprogress", totalUploadProgress, totalBytes, totalBytesSent); + }; + + Dropzone.prototype.getFallbackForm = function() { + var existingFallback, fields, fieldsString, form; + if (existingFallback = this.getExistingFallback()) { + return existingFallback; + } + fieldsString = "
"; + if (this.options.dictFallbackText) { + fieldsString += "

" + this.options.dictFallbackText + "

"; + } + fieldsString += "
"; + 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 index 45e63ffbd..c4426e6de 100644 --- a/templates/admin/default/assets/js/image-upload.js +++ b/templates/admin/default/assets/js/image-upload.js @@ -1,28 +1,37 @@ -// Manage picture upload $(function($){ - + // Manage picture upload 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]); - } - }); + var imageDropzone = new Dropzone("#images-dropzone"); + Dropzone.options.imageDropzone = { + uploadMultiple: false + }; + imageDropzone.on("success", function(file) { + $(".image-manager .dz-file-preview").remove(); + imageDropzone.removeFile(file); + pictureUploadManager.updateImageListAjax(); + }); + + // Update picture list via AJAX call + pictureUploadManager.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 + ); + }); }; - pictureUploadManager.onChangePreviewPicture(); // Remove image on click pictureUploadManager.onClickDeleteImage = function() { @@ -52,16 +61,4 @@ $(function($){ }); }; 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 b315665b6..e9c3f72a1 100755 --- a/templates/admin/default/category-edit.html +++ b/templates/admin/default/category-edit.html @@ -256,7 +256,7 @@
- {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} + {include file='includes/image-upload-form.html' imageType='category' parentId=$category_id}
@@ -297,9 +297,14 @@ {/block} {block name="javascript-initialization"} -{javascripts file='assets/js/image-upload.js'} - -{/javascripts} + {javascripts file='assets/js/dropzone.js'} + + {/javascripts} + {javascripts file='assets/js/image-upload.js'} + + {/javascripts} + + + {/javascripts} +{/block} \ No newline at end of file diff --git a/templates/admin/default/includes/image-upload-form.html b/templates/admin/default/includes/image-upload-form.html index 64253c346..46c949841 100644 --- a/templates/admin/default/includes/image-upload-form.html +++ b/templates/admin/default/includes/image-upload-form.html @@ -1,80 +1,476 @@ +{* +A generic image upload form + +Parameters: + imageType = Image type (category, product, folder, content, module) + parentId = Image parent id, ex: category id + +*} +
-
{$imageMessage}
+
+
+ +
+
+
- {loop type="image" name="image_test" source="{$imageType}" source_id="{$parentId}" width="200" height="100" resize_mode="borders"} - - {/loop} + {include file='includes/image-upload-list-ajax.html'}
- {form name=$formName} -
-
flashMessage = {flashMessage key="imageMessage"}{$value}{/flashMessage}
- {form_hidden_fields form=$form} +{*{block name="javascript-initialization"}*} + {*{$smarty.block.parent}*} - {*{form_field form=$form field='locale'}*} - {**} - {*{/form_field}*} + {*{javascripts file='assets/js/image-upload.js'}*} + {**} + {*{/javascripts}*} + {*{javascripts file='assets/js/dropzone.js'}*} + {**} + {*{/javascripts}*} +{*{/block}*} - {form_field form=$form field='success_url'} - - {/form_field} + +{* +@todo refactor +see http://www.dropzonejs.com/ - {form_field form=$form field='pictures'} -
- {*$value={$value|var_dump}*} - {*$error={$error|var_dump}*} - {*$message={$message|var_dump}*} - {*img0={$value.0|var_dump}*} - {*img0={$value.0->getFile()}*} - {*img1={$value[1]->getFile()}*} -
- - {*{form_field form=$form field='pictures.0.title'}*} - {*$name={$name|var_dump}*} - - {*{/form_field}*} - {*{form_field form=$form field='pictures.0.file'}*} - {*$name={$name|var_dump}*} - - {*{/form_field}*} - Your image -
- {*
*} - {**} - {**} - {**} - {*Your image*} - {*
*} - {*
*} - {**} - {**} - {**} - {*Your image*} - {*
*} - {*
*} - {**} - {**} - {**} - {*Your image*} - {*
*} - {*
*} - {**} - {**} - {**} - {*Your image*} - {*
*} - {if $error}{$message}{/if} -
- {/form_field} +*} + \ No newline at end of file diff --git a/templates/admin/default/includes/image-upload-list-ajax.html b/templates/admin/default/includes/image-upload-list-ajax.html new file mode 100644 index 000000000..a61d1f08d --- /dev/null +++ b/templates/admin/default/includes/image-upload-list-ajax.html @@ -0,0 +1,23 @@ +{* + +A generic image upload form + +Parameters: + imageType = Image type (category, product, folder, content, module) + parentId = Image parent id, ex: category id + +*} + +{loop type="image" name="image" source="{$imageType}" source_id="{$parentId}" width="200" height="100" resize_mode="borders"} + +{/loop} + + diff --git a/web/test_to_remove/admin-stats.json b/web/test_to_remove/admin-stats.json old mode 100644 new mode 100755