From ffbf8e6da2f597dbb6d8ad6cfbe628903721648d Mon Sep 17 00:00:00 2001 From: Alexey Yakimovich Date: Mon, 15 Oct 2018 15:55:13 +0300 Subject: [PATCH] MAGETWO-64838: Unable to create order from store front if customer address custom attribute is required. - Fixed an issue witn incorrent work of file/image custom attributes on checkout; --- .../Controller/Address/File/Upload.php | 146 ++++++ .../Magento/Customer/Model/FileProcessor.php | 5 +- .../base/web/js/form/element/file-uploader.js | 3 +- .../web/js/form/element/image-uploader.js | 3 +- .../form/element/uploader/uploader.html | 37 ++ .../css/source/module/checkout/_checkout.less | 2 + .../checkout/fields/_file-uploader.less | 450 ++++++++++++++++++ .../Magento/luma/web/css/source/_forms.less | 4 + .../CustomAttribute/PreprocessorInterface.php | 29 ++ .../Webapi/ServiceInputProcessor.php | 30 +- lib/web/css/source/lib/variables/_colors.less | 10 + 11 files changed, 712 insertions(+), 7 deletions(-) create mode 100644 app/code/Magento/Customer/Controller/Address/File/Upload.php create mode 100644 app/code/Magento/Ui/view/frontend/web/templates/form/element/uploader/uploader.html create mode 100644 app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/fields/_file-uploader.less create mode 100644 lib/internal/Magento/Framework/Webapi/CustomAttribute/PreprocessorInterface.php diff --git a/app/code/Magento/Customer/Controller/Address/File/Upload.php b/app/code/Magento/Customer/Controller/Address/File/Upload.php new file mode 100644 index 0000000000000..dbbea482b0e52 --- /dev/null +++ b/app/code/Magento/Customer/Controller/Address/File/Upload.php @@ -0,0 +1,146 @@ +fileUploaderFactory = $fileUploaderFactory; + $this->addressMetadataService = $addressMetadataService; + $this->logger = $logger; + $this->fileProcessorFactory = $fileProcessorFactory; + parent::__construct($context); + } + + /** + * @inheritDoc + */ + public function execute() + { + try { + $requestedFiles = $this->getRequest()->getFiles('custom_attributes'); + if (empty($requestedFiles)) { + $result = $this->processError(__('No files for upload.')); + } else { + $attributeCode = key($requestedFiles); + $attributeMetadata = $this->addressMetadataService->getAttributeMetadata($attributeCode); + + /** @var FileUploader $fileUploader */ + $fileUploader = $this->fileUploaderFactory->create([ + 'attributeMetadata' => $attributeMetadata, + 'entityTypeCode' => AddressMetadataInterface::ENTITY_TYPE_ADDRESS, + 'scope' => CustomAttributesDataInterface::CUSTOM_ATTRIBUTES, + ]); + + $errors = $fileUploader->validate(); + if (true !== $errors) { + $errorMessage = implode('
', $errors); + $result = $this->processError(($errorMessage)); + } else { + $result = $fileUploader->upload(); + } + } + } catch (LocalizedException $e) { + $result = $this->processError($e->getMessage(), $e->getCode()); + } catch (\Exception $e) { + $this->logger->critical($e); + $result = $this->processError($e->getMessage(), $e->getCode()); + } + + $this->moveTmpFileToSuitableFolder($result); + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON); + $resultJson->setData($result); + return $resultJson; + } + + /** + * Move file from temporary folder to the 'customer_address' media folder + * + * @param array $fileInfo + * @throws LocalizedException + */ + private function moveTmpFileToSuitableFolder(&$fileInfo) + { + $fileName = $fileInfo['file']; + $fileProcessor = $this->fileProcessorFactory + ->create(['entityTypeCode' => AddressMetadataInterface::ENTITY_TYPE_ADDRESS]); + + $newFilePath = $fileProcessor->moveTemporaryFile($fileName); + $fileInfo['file'] = $newFilePath; + $fileInfo['url'] = $fileProcessor->getViewUrl( + $newFilePath, + 'file' + ); + } + + /** + * Prepare result array for errors + * + * @param string $message + * @param int $code + * @return array + */ + private function processError($message, $code = 0) + { + $result = [ + 'error' => $message, + 'errorcode' => $code, + ]; + + return $result; + } +} diff --git a/app/code/Magento/Customer/Model/FileProcessor.php b/app/code/Magento/Customer/Model/FileProcessor.php index 6a8472758c169..e7e235b31fe49 100644 --- a/app/code/Magento/Customer/Model/FileProcessor.php +++ b/app/code/Magento/Customer/Model/FileProcessor.php @@ -5,6 +5,9 @@ */ namespace Magento\Customer\Model; +/** + * Processor class for work with uploaded files + */ class FileProcessor { /** @@ -232,7 +235,7 @@ public function moveTemporaryFile($fileName) ); } - $fileName = $dispersionPath . '/' . $fileName; + $fileName = $dispersionPath . '/' . $destinationFileName; return $fileName; } diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js index 2c5bc1159dd3a..f28569caa0053 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js @@ -16,7 +16,8 @@ define([ 'Magento_Ui/js/form/element/abstract', 'mage/backend/notification', 'mage/translate', - 'jquery/file-uploader' + 'jquery/file-uploader', + 'mage/adminhtml/tools' ], function ($, _, utils, uiAlert, validator, Element, notification, $t) { 'use strict'; diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js b/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js index 0ae09f14fa946..b490ac557e71b 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js @@ -11,8 +11,7 @@ define([ 'Magento_Ui/js/modal/alert', 'Magento_Ui/js/lib/validation/validator', 'Magento_Ui/js/form/element/file-uploader', - 'mage/adminhtml/browser', - 'mage/adminhtml/tools' + 'mage/adminhtml/browser' ], function ($, _, utils, uiAlert, validator, Element, browser) { 'use strict'; diff --git a/app/code/Magento/Ui/view/frontend/web/templates/form/element/uploader/uploader.html b/app/code/Magento/Ui/view/frontend/web/templates/form/element/uploader/uploader.html new file mode 100644 index 0000000000000..226ad2915bb61 --- /dev/null +++ b/app/code/Magento/Ui/view/frontend/web/templates/form/element/uploader/uploader.html @@ -0,0 +1,37 @@ + + +
+
+
+ +
+ + + +
+ +
+ + + +
+ +
+
+ +
diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less index 3ea1f5b7f6842..ac5ab0d87bf62 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less @@ -7,6 +7,8 @@ // Variables // _____________________________________________ +@import 'fields/_file-uploader.less'; + @checkout-wrapper__margin: @indent__base; @checkout-wrapper__columns: 16; diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/fields/_file-uploader.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/fields/_file-uploader.less new file mode 100644 index 0000000000000..7b06186ef9ad3 --- /dev/null +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/fields/_file-uploader.less @@ -0,0 +1,450 @@ +// /** +// * Copyright © Magento, Inc. All rights reserved. +// * See COPYING.txt for license details. +// */ + +// +// Components -> Single File Uploader +// _____________________________________________ + +// +// Variables +// --------------------------------------------- + +@icon-delete__content: '\e604'; +@icon-file__content: '\e626'; + + +@file-uploader-preview__border-color: @color-lighter-grayish; +@file-uploader-preview__background-color: @color-white; +@file-uploader-preview-focus__color: @color-blue2; + +@file-uploader-document-icon__color: @color-gray80; +@file-uploader-document-icon__size: 7rem; +@file-uploader-document-icon__z-index: @data-grid-file-uploader-image__z-index + 1; + +@file-uploader-video-icon__color: @color-gray80; +@file-uploader-video-icon__size: 4rem; +@file-uploader-video-icon__z-index: @data-grid-file-uploader-image__z-index + 1; + +@file-uploader-placeholder-icon__color: @color-gray80; +@file-uploader-placeholder-icon__z-index: @data-grid-file-uploader-image__z-index + 1; + +@file-uploader-delete-icon__color: @color-brownie; +@file-uploader-delete-icon__hover__color: @color-brownie-vanilla; +@file-uploader-delete-icon__font-size: 1.6rem; + +@file-uploader-muted-text__color: @color-gray62; + +@file-uploader-preview__width: 150px; +@file-uploader-preview__height: @file-uploader-preview__width; +@file-uploader-preview__opacity: .7; + +@file-uploader-spinner-dimensions: 15px; + +@file-uploader-dragover__background: @color-gray83; +@file-uploader-dragover-focus__color: @color-blue2; + +// Grid uploader + +@data-grid-file-uploader-image__size: 5rem; +@data-grid-file-uploader-image__z-index: 1; + +@data-grid-file-uploader-menu-button__width: 2rem; + +@data-grid-file-uploader-upload-icon__color: @color-darkie-gray; +@data-grid-file-uploader-upload-icon__hover__color: @color-very-dark-gray; +@data-grid-file-uploader-upload-icon__line-height: 48px; + +@data-grid-file-uploader-wrapper__size: @data-grid-file-uploader-image__size + 2rem; + +// +// Single file uploader +// --------------------------------------------- + +.file-uploader-area { + position: relative; + + input[type='file'] { + cursor: pointer; + opacity: 0; + overflow: hidden; + position: absolute; + visibility: hidden; + width: 0; + + &:focus { + + .file-uploader-button { + box-shadow: 0 0 0 1px @file-uploader-preview-focus__color; + } + } + + &:disabled { + + .file-uploader-button { + cursor: default; + opacity: .5; + pointer-events: none; + } + } + } +} + +.file-uploader-summary { + display: inline-block; + vertical-align: top; +} + +.file-uploader-button { + background: @color-gray-darken0; + border: 1px solid @color-gray_light; + box-sizing: border-box; + color: @color-black_dark; + cursor: pointer; + display: inline-block; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 1.4rem; + font-weight: 600; + line-height: 1.6rem; + margin: 0; + padding: 7px 15px; + vertical-align: middle; + + &._is-dragover { + background: @file-uploader-dragover__background; + border: 1px solid @file-uploader-preview-focus__color; + } +} + +.file-uploader-spinner { + background-image: url('@{baseDir}images/loader-1.gif'); + background-position: 50%; + background-repeat: no-repeat; + background-size: @file-uploader-spinner-dimensions; + display: none; + height: 30px; + margin-left: @indent__s; + vertical-align: top; + width: @file-uploader-spinner-dimensions; +} + +.file-uploader-preview { + .action-remove { + &:extend(.abs-action-reset all); + .lib-icon-font ( + @icon-delete__content, + @_icon-font: @icons__font-name, + @_icon-font-size: @file-uploader-delete-icon__font-size, + @_icon-font-color: @file-uploader-delete-icon__color, + @_icon-font-color-hover: @file-uploader-delete-icon__hover__color, + @_icon-font-text-hide: true, + @_icon-font-display: block + ); + bottom: 4px; + cursor: pointer; + display: block; + height: 27px; + left: 6px; + padding: 2px; + position: absolute; + text-decoration: none; + width: 25px; + z-index: 2; + } + + &:hover { + .preview-image img, + .preview-link:before { + opacity: @file-uploader-preview__opacity; + } + } + + .preview-link { + display: block; + height: 100%; + } + + .preview-image img { + bottom: 0; + left: 0; + margin: auto; + max-height: 100%; + max-width: 100%; + position: absolute; + right: 0; + top: 0; + z-index: 1; + } + + .preview-video { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: @file-uploader-video-icon__size, + @_icon-font-color: @file-uploader-video-icon__color, + @_icon-font-color-hover: @file-uploader-video-icon__color + ); + + &:before { + left: 0; + margin-top: -@file-uploader-video-icon__size / 2; + position: absolute; + right: 0; + top: 50%; + z-index: @file-uploader-video-icon__z-index; + } + } + + .preview-document { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: @file-uploader-document-icon__size, + @_icon-font-color: @file-uploader-document-icon__color, + @_icon-font-color-hover: @file-uploader-document-icon__color + ); + + &:before { + left: 0; + margin-top: -@file-uploader-document-icon__size / 2; + position: absolute; + right: 0; + top: 50%; + z-index: @file-uploader-document-icon__z-index; + } + } +} + +.file-uploader-preview, +.file-uploader-placeholder { + background: @file-uploader-preview__background-color; + border: 1px solid @file-uploader-preview__border-color; + box-sizing: border-box; + cursor: pointer; + display: block; + height: @file-uploader-preview__height; + line-height: 1; + margin: @indent__s @indent__m @indent__s 0; + overflow: hidden; + position: relative; + width: @file-uploader-preview__width; +} + +.file-uploader { + &._loading { + .file-uploader-spinner { + display: inline-block; + } + } + + .admin__field-note, + .admin__field-error { + margin-bottom: @indent__s; + } + + .file-uploader-filename { + .lib-text-overflow(); + max-width: @file-uploader-preview__width; + word-break: break-all; + + &:first-child { + margin-bottom: @indent__s; + } + } + + .file-uploader-meta { + color: @file-uploader-muted-text__color; + } + + .admin__field-fallback-reset { + margin-left: @indent__s; + } + + ._keyfocus & .action-remove { + &:focus { + box-shadow: 0 0 0 1px @file-uploader-preview-focus__color; + } + } +} + +// Placeholder for multiple uploader +.file-uploader-placeholder { + &.placeholder-document { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: 5rem, + @_icon-font-color: @file-uploader-placeholder-icon__color, + @_icon-font-color-hover: @file-uploader-placeholder-icon__color + ); + + &:before { + left: 0; + position: absolute; + right: 0; + top: 20px; + z-index: @file-uploader-placeholder-icon__z-index; + } + } + + &.placeholder-image { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: 5rem, + @_icon-font-color: @file-uploader-placeholder-icon__color, + @_icon-font-color-hover: @file-uploader-placeholder-icon__color + ); + + &:before { + left: 0; + position: absolute; + right: 0; + top: 20px; + z-index: @file-uploader-placeholder-icon__z-index; + } + } + + &.placeholder-video { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: 3rem, + @_icon-font-color: @file-uploader-placeholder-icon__color, + @_icon-font-color-hover: @file-uploader-placeholder-icon__color + ); + + &:before { + left: 0; + position: absolute; + right: 0; + top: 30px; + z-index: @file-uploader-placeholder-icon__z-index; + } + } +} + +.file-uploader-placeholder-text { + bottom: 0; + color: @color-blue-dodger; + font-size: 1.1rem; + left: 0; + line-height: @line-height__base; + margin-bottom: 15%; + padding: 0 @indent__base; + position: absolute; + right: 0; + text-align: center; +} + +// +// Grid image uploader +// --------------------------------------------- + +.data-grid-file-uploader { + min-width: @data-grid-file-uploader-wrapper__size; + + &._loading { + .file-uploader-spinner { + display: block; + } + + .file-uploader-button { + &:before { + display: none; + } + } + } + + .file-uploader-image { + background: transparent; + bottom: 0; + left: 0; + margin: auto; + max-height: 100%; + max-width: 100%; + position: absolute; + right: 0; + top: 0; + z-index: @data-grid-file-uploader-image__z-index; + + + .file-uploader-area { + .file-uploader-button { + &:before { + display: none; + } + } + } + } + + .file-uploader-area { + z-index: @data-grid-file-uploader-image__z-index + 1; + } + + .file-uploader-spinner { + height: 100%; + margin: 0; + position: absolute; + top: 0; + width: 100%; + } + + .file-uploader-button { + display: block; + height: @data-grid-file-uploader-upload-icon__line-height; + text-align: center; + + .lib-icon-font ( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: 1.3rem, + @_icon-font-line-height: @data-grid-file-uploader-upload-icon__line-height, + @_icon-font-color: @data-grid-file-uploader-upload-icon__color, + @_icon-font-color-hover: @data-grid-file-uploader-upload-icon__hover__color, + @_icon-font-text-hide: true, + @_icon-font-display: block + ); + } + + .action-select-wrap { + float: left; + + .action-select { + border: 1px solid @file-uploader-preview__border-color; + display: block; + height: @data-grid-file-uploader-image__size; + margin-left: -1px; + padding: 0; + width: @data-grid-file-uploader-menu-button__width; + + &:after { + border-color: @data-grid-file-uploader-upload-icon__color transparent transparent transparent; + left: 50%; + margin: 0 0 0 -5px; + } + + &:hover { + &:after { + border-color: @data-grid-file-uploader-upload-icon__hover__color transparent transparent transparent; + } + } + + > span { + display: none; + } + } + + .action-menu { + left: 4rem; + right: auto; + z-index: @data-grid-file-uploader-image__z-index + 1; + } + } +} + +.data-grid-file-uploader-inner { + border: 1px solid @file-uploader-preview__border-color; + float: left; + height: @data-grid-file-uploader-image__size; + position: relative; + width: @data-grid-file-uploader-image__size; +} diff --git a/app/design/frontend/Magento/luma/web/css/source/_forms.less b/app/design/frontend/Magento/luma/web/css/source/_forms.less index 98dd57dead74c..b92a2b5070b8f 100644 --- a/app/design/frontend/Magento/luma/web/css/source/_forms.less +++ b/app/design/frontend/Magento/luma/web/css/source/_forms.less @@ -104,6 +104,10 @@ .select-styling(); } + select.admin__control-multiselect { + height: auto; + } + .field-error, div.mage-error[generated] { margin-top: 7px; diff --git a/lib/internal/Magento/Framework/Webapi/CustomAttribute/PreprocessorInterface.php b/lib/internal/Magento/Framework/Webapi/CustomAttribute/PreprocessorInterface.php new file mode 100644 index 0000000000000..0f8743f057601 --- /dev/null +++ b/lib/internal/Magento/Framework/Webapi/CustomAttribute/PreprocessorInterface.php @@ -0,0 +1,29 @@ +typeProcessor = $typeProcessor; $this->objectManager = $objectManager; @@ -101,6 +109,7 @@ public function __construct( ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ServiceTypeToEntityTypeMap::class); $this->config = $config ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ConfigInterface::class); + $this->customAttributePreprocessors = $customAttributePreprocessors; } /** @@ -289,12 +298,11 @@ protected function convertCustomAttributeValue($customAttributesValueArray, $dat $dataObjectClassName = ltrim($dataObjectClassName, '\\'); foreach ($customAttributesValueArray as $key => $customAttribute) { + $this->runCustomAttributePreprocessors($key, $customAttribute); if (!is_array($customAttribute)) { $customAttribute = [AttributeValue::ATTRIBUTE_CODE => $key, AttributeValue::VALUE => $customAttribute]; } - list($customAttributeCode, $customAttributeValue) = $this->processCustomAttribute($customAttribute); - $entityType = $this->serviceTypeToEntityTypeMap->getEntityType($dataObjectClassName); if ($entityType) { $type = $this->customAttributeTypeLocator->getType( @@ -331,6 +339,22 @@ protected function convertCustomAttributeValue($customAttributesValueArray, $dat return $result; } + /** + * Prepare attribute value by loaded attribute preprocessors + * + * @param string $key + * @param mixed $customAttribute + * @return bool + */ + private function runCustomAttributePreprocessors($key, &$customAttribute) + { + foreach ($this->customAttributePreprocessors as $attributePreprocessor) { + if ($attributePreprocessor->shouldBePrepared($key, $customAttribute)) { + $attributePreprocessor->prepare($key, $customAttribute); + } + } + } + /** * Derive the custom attribute code and value. * diff --git a/lib/web/css/source/lib/variables/_colors.less b/lib/web/css/source/lib/variables/_colors.less index 9c694468e9f62..ffb0e8e797d81 100644 --- a/lib/web/css/source/lib/variables/_colors.less +++ b/lib/web/css/source/lib/variables/_colors.less @@ -7,9 +7,13 @@ // Color variables // _____________________________________________ +@color-blue-dodger: #008bdb; +@color-black_dark: #333333; + @color-white: #fff; @color-black: #000; +@color-darkie-gray: #8a837f; @color-gray19: #303030; @color-gray20: #333; @color-gray34: #575757; @@ -29,12 +33,16 @@ @color-gray79: #c9c9c9; @color-gray80: #ccc; @color-gray82: #d1d1d1; +@color-gray83: #d4d4d4; @color-gray89: #e3e3e3; @color-gray90: #e5e5e5; @color-gray91: #e8e8e8; @color-gray92: #ebebeb; @color-gray94: #f0f0f0; @color-gray95: #f2f2f2; +@color-gray_light: #cccccc; +@color-lighter-grayish: #cacaca; +@color-very-dark-gray: #666; @color-white-smoke: #f5f5f5; @color-white-dark-smoke: #efefef; @color-white-fog: #f8f8f8; @@ -80,6 +88,8 @@ @color-pink1: #fae5e5; @color-dark-pink1: #800080; // Legacy pink +@color-brownie: #514943; +@color-brownie-vanilla: #736963; @color-brownie1: #6f4400; @color-brownie-light1: #c07600;