diff --git a/Controller/MediaRestController.php b/Controller/MediaRestController.php new file mode 100644 index 00000000..587e5935 --- /dev/null +++ b/Controller/MediaRestController.php @@ -0,0 +1,97 @@ + + * @package Pim\Bundle\CustomEntityBundle\Controller + */ +class MediaRestController +{ + /** + * Validator interface + * + * @var ValidatorInterface + */ + protected $validator; + + /** + * Path generator + * + * @var PathGeneratorInterface + */ + protected $pathGenerator; + + /** + * Upload directory + * + * @var string + */ + protected $uploadDir; + + /** + * @param ValidatorInterface $validator + * @param PathGeneratorInterface $pathGenerator + * @param string $uploadDir + */ + public function __construct(ValidatorInterface $validator, PathGeneratorInterface $pathGenerator, $uploadDir) + { + $this->validator = $validator; + $this->pathGenerator = $pathGenerator; + $this->uploadDir = $uploadDir; + } + + /** + * Post a new media and return original filename and path + * + * @param Request $request + * + * @return JsonResponse + */ + public function postAction(Request $request) + { + /** @var \Symfony\Component\HttpFoundation\File\UploadedFile $file */ + $file = $request->files->get('file'); + $violations = $this->validator->validate($file); + + if (count($violations) > 0) { + $errors = []; + foreach ($violations as $violation) { + $errors[$violation->getPropertyPath()] = [ + 'message' => $violation->getMessage(), + 'invalid_value' => $violation->getInvalidValue(), + ]; + } + + return new JsonResponse($errors, 400); + } + $pathData = $this->pathGenerator->generate($file); + + try { + $movedFile = $file->move( + $this->uploadDir . DIRECTORY_SEPARATOR . $pathData['path'] . DIRECTORY_SEPARATOR . $pathData['uuid'], + $file->getClientOriginalName() + ); + } catch (FileException $e) { + return new JsonResponse("Unable to create target-directory, or moving file.", 400); + } + $filePath = $movedFile->getPathname(); + $filePathWithoutDirectory = str_replace($this->uploadDir . '/', '', $filePath); + + return new JsonResponse( + [ + 'originalFilename' => $file->getClientOriginalName(), + 'filePath' => $filePath, + 'shortFilePath' => $filePathWithoutDirectory + ] + ); + } +} diff --git a/Resources/config/controllers.yml b/Resources/config/controllers.yml index da7131bc..0959461f 100644 --- a/Resources/config/controllers.yml +++ b/Resources/config/controllers.yml @@ -1,7 +1,8 @@ parameters: - pim_ui.controller.ajax_option.class: Pim\Bundle\CustomEntityBundle\Controller\AjaxOptionController - pimee_ui.controller.ajax_option.class: Pim\Bundle\CustomEntityBundle\Controller\EnterpriseAjaxOptionController - pim_custom_entity.rest_controller.class: Pim\Bundle\CustomEntityBundle\Controller\RestController + pim_ui.controller.ajax_option.class: Pim\Bundle\CustomEntityBundle\Controller\AjaxOptionController + pimee_ui.controller.ajax_option.class: Pim\Bundle\CustomEntityBundle\Controller\EnterpriseAjaxOptionController + pim_custom_entity.rest_controller.class: Pim\Bundle\CustomEntityBundle\Controller\RestController + pim_custom_entity.media_rest_controller.class: Pim\Bundle\CustomEntityBundle\Controller\MediaRestController services: pim_custom_entity.controller: @@ -16,3 +17,10 @@ services: scope: request arguments: - '@pim_custom_entity.configuration.registry' + + pim_custom_entity.media_rest_controller: + class: '%pim_custom_entity.media_rest_controller.class%' + arguments: + - '@validator' + - '@akeneo_file_storage.file_storage.path_generator' + - '%catalog_storage_dir%' diff --git a/Resources/config/requirejs.yml b/Resources/config/requirejs.yml index 797ac5c1..3f77056d 100644 --- a/Resources/config/requirejs.yml +++ b/Resources/config/requirejs.yml @@ -8,10 +8,12 @@ config: custom_entity/form/common/save-form: pimcustomentity/js/form/common/save-form custom_entity/form/common/delete: pimcustomentity/js/form/common/delete custom_entity/form/common/label: pimcustomentity/js/form/common/label + custom_entity/form/common/fields/media: pimcustomentity/js/form/common/fields/media custom_entity/controller/list: pimcustomentity/js/controller/custom_entity-list custom_entity/controller/edit: pimcustomentity/js/controller/custom_entity-edit custom_entity/fetcher: pimcustomentity/js/fetcher/custom_entity-fetcher custom_entity/remover/reference-data: pimcustomentity/js/remover/reference-data-remover + custom_entity/template/form/common/fields/media: pimcustomentity/templates/form/common/fields/media.html config: pim/fetcher-registry: diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml index 64c761c9..16502956 100644 --- a/Resources/config/routing.yml +++ b/Resources/config/routing.yml @@ -66,3 +66,8 @@ pim_customentity_history: actionType: history requirements: id: \d+ + +pim_customentity_media_rest_post: + path: '/rest/customentity_media' + defaults: { _controller: pim_custom_entity.media_rest_controller:postAction } + methods: [POST] diff --git a/Resources/public/js/form/common/fields/media.js b/Resources/public/js/form/common/fields/media.js new file mode 100644 index 00000000..4aa75f8b --- /dev/null +++ b/Resources/public/js/form/common/fields/media.js @@ -0,0 +1,96 @@ +'use strict'; + +define([ + 'jquery', + 'underscore', + 'pim/form/common/fields/field', + 'pim/media-url-generator', + 'routing', + 'custom_entity/template/form/common/fields/media' + ], + function ($, + _, + BaseField, + MediaUrlGenerator, + Routing, + template) { + return BaseField.extend({ + template: _.template(template), + + events: { + 'change input[type="file"]': function (event) { + this.errors = []; + this.updateModel(this.getFieldValue(event.target)); + this.render(); + }, + 'click .clear-field': 'clearField' + }, + + /** + * {@inheritdoc} + */ + renderInput: function (templateContext) { + var modelValue = this.getModelValue(); + var originalFilename = null; + var filePath = null; + var shortFilePath = null; + if (modelValue != null) { + var modelValueAsJson = JSON.parse(modelValue); + originalFilename = modelValueAsJson.originalFilename; + filePath = modelValueAsJson.filePath; + shortFilePath = modelValueAsJson.shortFilePath; + } + + return this.template(_.extend(templateContext, { + value: modelValue, + originalFilename: originalFilename, + filePath: filePath, + shortFilePath: shortFilePath, + mediaUrlGenerator: MediaUrlGenerator + })); + }, + + /** + * {@inheritdoc} + */ + getFieldValue: function (field) { + var self = this; + var fieldValue = null; + + var input = this.$(field).get(0); + if (!input || 0 === input.files.length) { + return null; + } + var formData = new FormData(); + formData.append('file', input.files[0]); + + $.ajax({ + url: Routing.generate('pim_customentity_media_rest_post'), + type: 'POST', + data: formData, + contentType: false, + cache: false, + processData: false, + async: false + }).done(function (data) { + fieldValue = JSON.stringify(data); + }).fail(function () { + self.errors.push({ + code: self.fieldName, + message: _.__('pim_enrich.entity.product.error.upload'), + global: false + }); + }); + + return fieldValue; + }, + + /** + * Clear media field + */ + clearField: function () { + this.updateModel(null); + this.render(); + } + }); + }); diff --git a/Resources/public/templates/form/common/fields/media.html b/Resources/public/templates/form/common/fields/media.html new file mode 100644 index 00000000..1d80b5eb --- /dev/null +++ b/Resources/public/templates/form/common/fields/media.html @@ -0,0 +1,22 @@ +
+ <% if (!filePath) { %> + +
+ + <%- _.__('pim_enrich.entity.product.media.upload')%> +
+ <% } else { %> +
+ <% mediaThumbnailUrl = mediaUrlGenerator.getMediaShowUrl(filePath, 'thumbnail_small') %> + <% mediaDownloadUrl = mediaUrlGenerator.getMediaDownloadUrl(shortFilePath) %> +
+
+
<%- originalFilename %>
+
+ + +
+
+
+ <% } %> +
diff --git a/docs/examples/CustomBundle/Entity/Brand.php b/docs/examples/CustomBundle/Entity/Brand.php index 521ab463..dfaae608 100644 --- a/docs/examples/CustomBundle/Entity/Brand.php +++ b/docs/examples/CustomBundle/Entity/Brand.php @@ -9,11 +9,36 @@ */ class Brand extends AbstractCustomEntity { + /** + * @var string + */ + protected $visual; + /** * @var Fabric */ protected $fabric; + /** + * @return string + */ + public function getVisual() + { + return $this->visual; + } + + /** + * @param string $visual + * + * @return Brand + */ + public function setVisual($visual) + { + $this->visual = $visual; + + return $this; + } + /** * @param Fabric|null $fabric * diff --git a/docs/examples/CustomBundle/Normalizer/Standard/BrandNormalizer.php b/docs/examples/CustomBundle/Normalizer/Standard/BrandNormalizer.php index a7191acf..1aebea55 100644 --- a/docs/examples/CustomBundle/Normalizer/Standard/BrandNormalizer.php +++ b/docs/examples/CustomBundle/Normalizer/Standard/BrandNormalizer.php @@ -37,8 +37,9 @@ public function __construct(FabricNormalizer $fabricNormalizer) public function normalize($entity, $format = null, array $context = []) { $normalizedBrand = [ - 'id' => $entity->getId(), - 'code' => $entity->getCode(), + 'id' => $entity->getId(), + 'code' => $entity->getCode(), + 'visual' => $entity->getVisual(), ]; $fabric = $entity->getFabric(); diff --git a/docs/examples/CustomBundle/Resources/config/doctrine/Brand.orm.yml b/docs/examples/CustomBundle/Resources/config/doctrine/Brand.orm.yml index 7fc9c8ba..e7a4236a 100644 --- a/docs/examples/CustomBundle/Resources/config/doctrine/Brand.orm.yml +++ b/docs/examples/CustomBundle/Resources/config/doctrine/Brand.orm.yml @@ -13,6 +13,9 @@ Acme\Bundle\CustomBundle\Entity\Brand: type: string length: 255 unique: true + visual: + type: text + nullable: true sortOrder: type: integer manyToOne: diff --git a/docs/examples/CustomBundle/Resources/config/form_extensions/brand/edit.yml b/docs/examples/CustomBundle/Resources/config/form_extensions/brand/edit.yml index 303c0b3d..e9c80e4c 100644 --- a/docs/examples/CustomBundle/Resources/config/form_extensions/brand/edit.yml +++ b/docs/examples/CustomBundle/Resources/config/form_extensions/brand/edit.yml @@ -140,11 +140,20 @@ extensions: required: true readOnly: true + pim-brand-edit-form-properties-visual: + module: custom_entity/form/common/fields/media + parent: pim-brand-edit-form-properties-common + targetZone: content + position: 100 + config: + fieldName: visual + label: acme_custom.brand.field.label.visual + pim-brand-edit-form-properties-fabric: module: custom_entity/field/custom-entity-select parent: pim-brand-edit-form-properties-common targetZone: content - position: 100 + position: 110 config: fieldName: fabric choiceNameField: code diff --git a/docs/examples/CustomBundle/Resources/translations/jsmessages.en.yml b/docs/examples/CustomBundle/Resources/translations/jsmessages.en.yml index 2a2a85c0..cde55f65 100644 --- a/docs/examples/CustomBundle/Resources/translations/jsmessages.en.yml +++ b/docs/examples/CustomBundle/Resources/translations/jsmessages.en.yml @@ -4,6 +4,7 @@ acme_custom: selected: brands selected field.label: code: Code + visual: Visual form: tab.properties.section.common: Common tab.properties.section.label_translations: Labels