diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml index d24c501152b78..7ca2c0a56f224 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml @@ -13,6 +13,7 @@ + diff --git a/app/code/Magento/Customer/Controller/Account/CreatePassword.php b/app/code/Magento/Customer/Controller/Account/CreatePassword.php index 124ac912a7ccf..d12ec57d3c339 100644 --- a/app/code/Magento/Customer/Controller/Account/CreatePassword.php +++ b/app/code/Magento/Customer/Controller/Account/CreatePassword.php @@ -3,13 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Customer\Controller\Account; use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Model\ForgotPasswordToken\ConfirmCustomerByToken; use Magento\Customer\Model\Session; use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\View\Result\PageFactory; use Magento\Framework\App\Action\Context; +use Magento\Framework\App\ObjectManager; /** * Class CreatePassword @@ -34,20 +38,30 @@ class CreatePassword extends \Magento\Customer\Controller\AbstractAccount implem protected $resultPageFactory; /** - * @param Context $context - * @param Session $customerSession - * @param PageFactory $resultPageFactory - * @param AccountManagementInterface $accountManagement + * @var \Magento\Customer\Model\ForgotPasswordToken\ConfirmCustomerByToken + */ + private $confirmByToken; + + /** + * @param \Magento\Framework\App\Action\Context $context + * @param \Magento\Customer\Model\Session $customerSession + * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory + * @param \Magento\Customer\Api\AccountManagementInterface $accountManagement + * @param \Magento\Customer\Model\ForgotPasswordToken\ConfirmCustomerByToken $confirmByToken */ public function __construct( Context $context, Session $customerSession, PageFactory $resultPageFactory, - AccountManagementInterface $accountManagement + AccountManagementInterface $accountManagement, + ConfirmCustomerByToken $confirmByToken = null ) { $this->session = $customerSession; $this->resultPageFactory = $resultPageFactory; $this->accountManagement = $accountManagement; + $this->confirmByToken = $confirmByToken + ?? ObjectManager::getInstance()->get(ConfirmCustomerByToken::class); + parent::__construct($context); } @@ -67,6 +81,8 @@ public function execute() try { $this->accountManagement->validateResetPasswordLinkToken(null, $resetPasswordToken); + $this->confirmByToken->execute($resetPasswordToken); + if ($isDirectLink) { $this->session->setRpToken($resetPasswordToken); $resultRedirect = $this->resultRedirectFactory->create(); @@ -77,16 +93,17 @@ public function execute() /** @var \Magento\Framework\View\Result\Page $resultPage */ $resultPage = $this->resultPageFactory->create(); $resultPage->getLayout() - ->getBlock('resetPassword') - ->setResetPasswordLinkToken($resetPasswordToken); + ->getBlock('resetPassword') + ->setResetPasswordLinkToken($resetPasswordToken); return $resultPage; } } catch (\Exception $exception) { - $this->messageManager->addError(__('Your password reset link has expired.')); + $this->messageManager->addErrorMessage(__('Your password reset link has expired.')); /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); $resultRedirect->setPath('*/*/forgotpassword'); + return $resultRedirect; } } diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index 8439a3f2308c2..15d98af86b72e 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -17,6 +17,7 @@ use Magento\Customer\Model\Config\Share as ConfigShare; use Magento\Customer\Model\Customer as CustomerModel; use Magento\Customer\Model\Customer\CredentialsValidator; +use Magento\Customer\Model\ForgotPasswordToken\GetCustomerByToken; use Magento\Customer\Model\Metadata\Validator; use Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory; use Magento\Directory\Model\AllowedCountries; @@ -44,7 +45,6 @@ use Magento\Framework\Intl\DateTimeFactory; use Magento\Framework\Mail\Template\TransportBuilder; use Magento\Framework\Math\Random; -use Magento\Framework\Phrase; use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Framework\Registry; use Magento\Framework\Session\SaveHandlerInterface; @@ -345,6 +345,11 @@ class AccountManagement implements AccountManagementInterface */ private $allowedCountriesReader; + /** + * @var GetCustomerByToken + */ + private $getByToken; + /** * @param CustomerFactory $customerFactory * @param ManagerInterface $eventManager @@ -377,10 +382,12 @@ class AccountManagement implements AccountManagementInterface * @param CollectionFactory|null $visitorCollectionFactory * @param SearchCriteriaBuilder|null $searchCriteriaBuilder * @param AddressRegistry|null $addressRegistry + * @param GetCustomerByToken|null $getByToken * @param AllowedCountries|null $allowedCountriesReader + * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.LongVariable) */ public function __construct( CustomerFactory $customerFactory, @@ -414,6 +421,7 @@ public function __construct( CollectionFactory $visitorCollectionFactory = null, SearchCriteriaBuilder $searchCriteriaBuilder = null, AddressRegistry $addressRegistry = null, + GetCustomerByToken $getByToken = null, AllowedCountries $allowedCountriesReader = null ) { $this->customerFactory = $customerFactory; @@ -439,23 +447,26 @@ public function __construct( $this->customerModel = $customerModel; $this->objectFactory = $objectFactory; $this->extensibleDataObjectConverter = $extensibleDataObjectConverter; + $objectManager = ObjectManager::getInstance(); $this->credentialsValidator = - $credentialsValidator ?: ObjectManager::getInstance()->get(CredentialsValidator::class); - $this->dateTimeFactory = $dateTimeFactory ?: ObjectManager::getInstance()->get(DateTimeFactory::class); - $this->accountConfirmation = $accountConfirmation ?: ObjectManager::getInstance() + $credentialsValidator ?: $objectManager->get(CredentialsValidator::class); + $this->dateTimeFactory = $dateTimeFactory ?: $objectManager->get(DateTimeFactory::class); + $this->accountConfirmation = $accountConfirmation ?: $objectManager ->get(AccountConfirmation::class); $this->sessionManager = $sessionManager - ?: ObjectManager::getInstance()->get(SessionManagerInterface::class); + ?: $objectManager->get(SessionManagerInterface::class); $this->saveHandler = $saveHandler - ?: ObjectManager::getInstance()->get(SaveHandlerInterface::class); + ?: $objectManager->get(SaveHandlerInterface::class); $this->visitorCollectionFactory = $visitorCollectionFactory - ?: ObjectManager::getInstance()->get(CollectionFactory::class); + ?: $objectManager->get(CollectionFactory::class); $this->searchCriteriaBuilder = $searchCriteriaBuilder - ?: ObjectManager::getInstance()->get(SearchCriteriaBuilder::class); + ?: $objectManager->get(SearchCriteriaBuilder::class); $this->addressRegistry = $addressRegistry - ?: ObjectManager::getInstance()->get(AddressRegistry::class); + ?: $objectManager->get(AddressRegistry::class); + $this->getByToken = $getByToken + ?: $objectManager->get(GetCustomerByToken::class); $this->allowedCountriesReader = $allowedCountriesReader - ?: ObjectManager::getInstance()->get(AllowedCountries::class); + ?: $objectManager->get(AllowedCountries::class); } /** @@ -521,8 +532,11 @@ public function activateById($customerId, $confirmationKey) * @param \Magento\Customer\Api\Data\CustomerInterface $customer * @param string $confirmationKey * @return \Magento\Customer\Api\Data\CustomerInterface - * @throws \Magento\Framework\Exception\State\InvalidTransitionException - * @throws \Magento\Framework\Exception\State\InputMismatchException + * @throws InputException + * @throws InputMismatchException + * @throws InvalidTransitionException + * @throws LocalizedException + * @throws NoSuchEntityException */ private function activateCustomer($customer, $confirmationKey) { @@ -630,42 +644,6 @@ public function initiatePasswordReset($email, $template, $websiteId = null) return false; } - /** - * Match a customer by their RP token. - * - * @param string $rpToken - * @throws ExpiredException - * @throws NoSuchEntityException - * @return CustomerInterface - * @throws LocalizedException - */ - private function matchCustomerByRpToken(string $rpToken): CustomerInterface - { - $this->searchCriteriaBuilder->addFilter( - 'rp_token', - $rpToken - ); - $this->searchCriteriaBuilder->setPageSize(1); - $found = $this->customerRepository->getList( - $this->searchCriteriaBuilder->create() - ); - if ($found->getTotalCount() > 1) { - //Failed to generated unique RP token - throw new ExpiredException( - new Phrase('Reset password token expired.') - ); - } - if ($found->getTotalCount() === 0) { - //Customer with such token not found. - throw NoSuchEntityException::singleField( - 'rp_token', - $rpToken - ); - } - //Unique customer found. - return $found->getItems()[0]; - } - /** * Handle not supported template * @@ -674,15 +652,17 @@ private function matchCustomerByRpToken(string $rpToken): CustomerInterface */ private function handleUnknownTemplate($template) { - throw new InputException(__( - 'Invalid value of "%value" provided for the %fieldName field. Possible values: %template1 or %template2.', - [ - 'value' => $template, - 'fieldName' => 'template', - 'template1' => AccountManagement::EMAIL_REMINDER, - 'template2' => AccountManagement::EMAIL_RESET - ] - )); + throw new InputException( + __( + 'Invalid value of "%value" provided for the %fieldName field. Possible values: %template1 or %template2.', + [ + 'value' => $template, + 'fieldName' => 'template', + 'template1' => AccountManagement::EMAIL_REMINDER, + 'template2' => AccountManagement::EMAIL_RESET + ] + ) + ); } /** @@ -691,7 +671,7 @@ private function handleUnknownTemplate($template) public function resetPassword($email, $resetToken, $newPassword) { if (!$email) { - $customer = $this->matchCustomerByRpToken($resetToken); + $customer = $this->getByToken->execute($resetToken); $email = $customer->getEmail(); } else { $customer = $this->customerRepository->get($email); @@ -830,6 +810,8 @@ public function getConfirmationStatus($customerId) /** * @inheritdoc + * + * @throws LocalizedException */ public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') { @@ -852,6 +834,8 @@ public function createAccount(CustomerInterface $customer, $password = null, $re /** * @inheritdoc + * + * @throws InputMismatchException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -987,6 +971,8 @@ protected function sendEmailConfirmation(CustomerInterface $customer, $redirectU /** * @inheritdoc + * + * @throws InvalidEmailOrPasswordException */ public function changePassword($email, $currentPassword, $newPassword) { @@ -1000,6 +986,8 @@ public function changePassword($email, $currentPassword, $newPassword) /** * @inheritdoc + * + * @throws InvalidEmailOrPasswordException */ public function changePasswordById($customerId, $currentPassword, $newPassword) { @@ -1137,12 +1125,14 @@ public function isCustomerInStore($customerWebsiteId, $storeId) * * @param int $customerId * @param string $resetPasswordLinkToken + * * @return bool - * @throws \Magento\Framework\Exception\State\InputMismatchException If token is mismatched - * @throws \Magento\Framework\Exception\State\ExpiredException If token is expired - * @throws \Magento\Framework\Exception\InputException If token or customer id is invalid - * @throws \Magento\Framework\Exception\NoSuchEntityException If customer doesn't exist + * @throws ExpiredException If token is expired + * @throws InputException If token or customer id is invalid + * @throws InputMismatchException If token is mismatched * @throws LocalizedException + * @throws NoSuchEntityException If customer doesn't exist + * @SuppressWarnings(PHPMD.LongVariable) */ private function validateResetPasswordToken($customerId, $resetPasswordLinkToken) { @@ -1157,7 +1147,8 @@ private function validateResetPasswordToken($customerId, $resetPasswordLinkToken if ($customerId === null) { //Looking for the customer. - $customerId = $this->matchCustomerByRpToken($resetPasswordLinkToken) + $customerId = $this->getByToken + ->execute($resetPasswordLinkToken) ->getId(); } if (!is_string($resetPasswordLinkToken) || empty($resetPasswordLinkToken)) { @@ -1325,13 +1316,20 @@ protected function sendEmailTemplate( } $transport = $this->transportBuilder->setTemplateIdentifier($templateId) - ->setTemplateOptions(['area' => Area::AREA_FRONTEND, 'store' => $storeId]) + ->setTemplateOptions( + [ + 'area' => Area::AREA_FRONTEND, + 'store' => $storeId + ] + ) ->setTemplateVars($templateParams) - ->setFrom($this->scopeConfig->getValue( - $sender, - ScopeInterface::SCOPE_STORE, - $storeId - )) + ->setFrom( + $this->scopeConfig->getValue( + $sender, + ScopeInterface::SCOPE_STORE, + $storeId + ) + ) ->addTo($email, $this->customerViewHelper->getCustomerName($customer)) ->getTransport(); diff --git a/app/code/Magento/Customer/Model/ForgotPasswordToken/ConfirmCustomerByToken.php b/app/code/Magento/Customer/Model/ForgotPasswordToken/ConfirmCustomerByToken.php new file mode 100644 index 0000000000000..6aadc814a4b9b --- /dev/null +++ b/app/code/Magento/Customer/Model/ForgotPasswordToken/ConfirmCustomerByToken.php @@ -0,0 +1,58 @@ +getByToken = $getByToken; + $this->customerRepository = $customerRepository; + } + + /** + * Confirm customer account my rp_token + * + * @param string $resetPasswordToken + * + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function execute(string $resetPasswordToken): void + { + $customer = $this->getByToken->execute($resetPasswordToken); + if ($customer->getConfirmation()) { + $this->customerRepository->save( + $customer->setConfirmation(null) + ); + } + } +} diff --git a/app/code/Magento/Customer/Model/ForgotPasswordToken/GetCustomerByToken.php b/app/code/Magento/Customer/Model/ForgotPasswordToken/GetCustomerByToken.php new file mode 100644 index 0000000000000..09af4e296bd92 --- /dev/null +++ b/app/code/Magento/Customer/Model/ForgotPasswordToken/GetCustomerByToken.php @@ -0,0 +1,89 @@ +customerRepository = $customerRepository; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + } + + /** + * Get customer by rp_token + * + * @param string $resetPasswordToken + * + * @return \Magento\Customer\Api\Data\CustomerInterface + * @throws ExpiredException + * @throws NoSuchEntityException + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function execute(string $resetPasswordToken):CustomerInterface + { + $this->searchCriteriaBuilder->addFilter( + 'rp_token', + $resetPasswordToken + ); + $this->searchCriteriaBuilder->setPageSize(1); + $found = $this->customerRepository->getList( + $this->searchCriteriaBuilder->create() + ); + + if ($found->getTotalCount() > 1) { + //Failed to generated unique RP token + throw new ExpiredException( + new Phrase('Reset password token expired.') + ); + } + if ($found->getTotalCount() === 0) { + //Customer with such token not found. + new NoSuchEntityException( + new Phrase( + 'No such entity with rp_token = %value', + [ + 'value' => $resetPasswordToken + ] + ) + ); + } + + //Unique customer found. + return $found->getItems()[0]; + } +} diff --git a/app/code/Magento/Customer/Model/ResourceModel/Customer.php b/app/code/Magento/Customer/Model/ResourceModel/Customer.php index 2eb1ef897e70e..94196df6fe093 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/Customer.php +++ b/app/code/Magento/Customer/Model/ResourceModel/Customer.php @@ -95,9 +95,12 @@ protected function _getDefaultAttributes() /** * Check customer scope, email and confirmation key before saving * - * @param \Magento\Framework\DataObject $customer + * @param \Magento\Framework\DataObject|\Magento\Customer\Api\Data\CustomerInterface $customer + * * @return $this - * @throws \Magento\Framework\Exception\LocalizedException + * @throws AlreadyExistsException + * @throws ValidatorException + * @throws \Magento\Framework\Exception\NoSuchEntityException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -141,9 +144,7 @@ protected function _beforeSave(\Magento\Framework\DataObject $customer) } // set confirmation key logic - if ($customer->getForceConfirmed() || $customer->getPasswordHash() == '') { - $customer->setConfirmation(null); - } elseif (!$customer->getId() && $customer->isConfirmationRequired()) { + if (!$customer->getId() && $customer->isConfirmationRequired()) { $customer->setConfirmation($customer->getRandomConfirmationKey()); } // remove customer confirmation key from database, if empty @@ -163,7 +164,7 @@ protected function _beforeSave(\Magento\Framework\DataObject $customer) * * @param \Magento\Customer\Model\Customer $customer * @return void - * @throws \Magento\Framework\Validator\Exception + * @throws ValidatorException */ protected function _validate($customer) { diff --git a/app/code/Magento/Email/Model/AbstractTemplate.php b/app/code/Magento/Email/Model/AbstractTemplate.php index a6ecdaf24ebbb..5eae1d1462184 100644 --- a/app/code/Magento/Email/Model/AbstractTemplate.php +++ b/app/code/Magento/Email/Model/AbstractTemplate.php @@ -14,10 +14,12 @@ use Magento\Store\Model\Information as StoreInformation; use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\Store; +use Magento\MediaStorage\Helper\File\Storage\Database; /** - * Template model class + * Template model class. * + * phpcs:disable Magento2.Classes.AbstractApi * @author Magento Core Team * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) @@ -163,6 +165,11 @@ abstract class AbstractTemplate extends AbstractModel implements TemplateTypesIn */ private $urlModel; + /** + * @var Database + */ + private $fileStorageDatabase; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\View\DesignInterface $design @@ -177,6 +184,7 @@ abstract class AbstractTemplate extends AbstractModel implements TemplateTypesIn * @param \Magento\Framework\Filter\FilterManager $filterManager * @param \Magento\Framework\UrlInterface $urlModel * @param array $data + * @param Database $fileStorageDatabase * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -193,7 +201,8 @@ public function __construct( \Magento\Email\Model\TemplateFactory $templateFactory, \Magento\Framework\Filter\FilterManager $filterManager, \Magento\Framework\UrlInterface $urlModel, - array $data = [] + array $data = [], + Database $fileStorageDatabase = null ) { $this->design = $design; $this->area = isset($data['area']) ? $data['area'] : null; @@ -207,6 +216,8 @@ public function __construct( $this->templateFactory = $templateFactory; $this->filterManager = $filterManager; $this->urlModel = $urlModel; + $this->fileStorageDatabase = $fileStorageDatabase ?: + \Magento\Framework\App\ObjectManager::getInstance()->get(Database::class); parent::__construct($context, $registry, null, null, $data); } @@ -394,6 +405,11 @@ protected function getLogoUrl($store) if ($fileName) { $uploadDir = \Magento\Email\Model\Design\Backend\Logo::UPLOAD_DIR; $mediaDirectory = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA); + if ($this->fileStorageDatabase->checkDbUsage() && + !$mediaDirectory->isFile($uploadDir . '/' . $fileName) + ) { + $this->fileStorageDatabase->saveFileToFilesystem($uploadDir . '/' . $fileName); + } if ($mediaDirectory->isFile($uploadDir . '/' . $fileName)) { return $this->storeManager->getStore()->getBaseUrl( \Magento\Framework\UrlInterface::URL_TYPE_MEDIA @@ -490,7 +506,6 @@ protected function addEmailVariables($variables, $storeId) /** * Apply design config so that emails are processed within the context of the appropriate area/store/theme. - * Can be called multiple times without issue. * * @return bool */ @@ -664,8 +679,7 @@ public function getTemplateFilter() } /** - * Save current design config and replace with design config from specified store - * Event is not dispatched. + * Save current design config and replace with design config from specified store. Event is not dispatched. * * @param null|bool|int|string $storeId * @param string $area diff --git a/app/code/Magento/Email/Test/Unit/Model/BackendTemplateTest.php b/app/code/Magento/Email/Test/Unit/Model/BackendTemplateTest.php index 31a04b0b2bbd0..1ceccd4414cc0 100644 --- a/app/code/Magento/Email/Test/Unit/Model/BackendTemplateTest.php +++ b/app/code/Magento/Email/Test/Unit/Model/BackendTemplateTest.php @@ -12,6 +12,9 @@ use Magento\Email\Model\BackendTemplate; use Magento\Framework\ObjectManagerInterface; +/** + * Tests for adminhtml email template model. + */ class BackendTemplateTest extends \PHPUnit\Framework\TestCase { /** @@ -46,6 +49,11 @@ class BackendTemplateTest extends \PHPUnit\Framework\TestCase */ private $serializerMock; + /** + * @var \Magento\MediaStorage\Helper\File\Storage\Database|\PHPUnit_Framework_MockObject_MockObject + */ + private $databaseHelperMock; + protected function setUp() { $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -56,6 +64,7 @@ protected function setUp() $this->structureMock = $this->createMock(\Magento\Config\Model\Config\Structure::class); $this->structureMock->expects($this->any())->method('getFieldPathsByAttribute')->willReturn(['path' => 'test']); + $this->databaseHelperMock = $this->createMock(\Magento\MediaStorage\Helper\File\Storage\Database::class); $this->resourceModelMock = $this->createMock(\Magento\Email\Model\ResourceModel\Template::class); $this->resourceModelMock->expects($this->any()) ->method('getSystemConfigByPathsAndTemplateId') @@ -64,8 +73,18 @@ protected function setUp() $objectManagerMock = $this->createMock(\Magento\Framework\ObjectManagerInterface::class); $objectManagerMock->expects($this->any()) ->method('get') - ->with(\Magento\Email\Model\ResourceModel\Template::class) - ->will($this->returnValue($this->resourceModelMock)); + ->willReturnCallback( + function ($value) { + switch ($value) { + case \Magento\MediaStorage\Helper\File\Storage\Database::class: + return ($this->databaseHelperMock); + case \Magento\Email\Model\ResourceModel\Template::class: + return ($this->resourceModelMock); + default: + return(null); + } + } + ); \Magento\Framework\App\ObjectManager::setInstance($objectManagerMock); diff --git a/app/code/Magento/Email/composer.json b/app/code/Magento/Email/composer.json index 1011b16f8537d..e887adef1fbc9 100644 --- a/app/code/Magento/Email/composer.json +++ b/app/code/Magento/Email/composer.json @@ -12,6 +12,7 @@ "magento/module-config": "*", "magento/module-store": "*", "magento/module-theme": "*", + "magento/module-media-storage": "*", "magento/module-variable": "*" }, "suggest": { diff --git a/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php b/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php index 6143b8e659059..0848f566f67bb 100644 --- a/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php +++ b/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php @@ -182,6 +182,9 @@ public function getJsonSwatchConfig() $attributeDataArray ); } + if (isset($attributeDataArray['additional_data'])) { + $config[$attributeId]['additional_data'] = $attributeDataArray['additional_data']; + } } return $this->jsonEncoder->encode($config); @@ -189,6 +192,7 @@ public function getJsonSwatchConfig() /** * Get number of swatches from config to show on product listing. + * * Other swatches can be shown after click button 'Show more' * * @return string @@ -228,6 +232,8 @@ public function getProduct() } /** + * Get swatch attributes data. + * * @return array */ protected function getSwatchAttributesData() @@ -236,6 +242,8 @@ protected function getSwatchAttributesData() } /** + * Init isProductHasSwatchAttribute. + * * @deprecated 100.1.5 Method isProductHasSwatchAttribute() is used instead of this. * * @codeCoverageIgnore @@ -364,6 +372,8 @@ protected function getVariationMedia($attributeCode, $optionId) } /** + * Get swatch product image. + * * @param Product $childProduct * @param string $imageType * @return string @@ -384,6 +394,8 @@ protected function getSwatchProductImage(Product $childProduct, $imageType) } /** + * Check if product have image. + * * @param Product $product * @param string $imageType * @return bool @@ -394,6 +406,8 @@ protected function isProductHasImage(Product $product, $imageType) } /** + * Get configurable options ids. + * * @param array $attributeData * @return array * @since 100.0.3 @@ -453,8 +467,8 @@ protected function getRendererTemplate() } /** + * @inheritDoc * @deprecated 100.1.5 Now is used _toHtml() directly - * @return string */ protected function getHtmlOutput() { @@ -462,6 +476,8 @@ protected function getHtmlOutput() } /** + * Get media callback url. + * * @return string */ public function getMediaCallback() diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml index 5347a1a1f870f..b1ae06428c0ab 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml @@ -41,6 +41,9 @@ + + + diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index b0688f33aaef7..6e028ec53c122 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -713,7 +713,8 @@ define([ $wrapper = $this.parents('.' + $widget.options.classes.attributeOptionsWrapper), $label = $parent.find('.' + $widget.options.classes.attributeSelectedOptionLabelClass), attributeId = $parent.attr('attribute-id'), - $input = $parent.find('.' + $widget.options.classes.attributeInput); + $input = $parent.find('.' + $widget.options.classes.attributeInput), + checkAdditionalData = JSON.parse(this.options.jsonSwatchConfig[attributeId]['additional_data']); if ($widget.inProductList) { $input = $widget.productForm.find( @@ -753,7 +754,10 @@ define([ $widget.options.jsonConfig.optionPrices ]); - $widget._loadMedia(); + if (checkAdditionalData['update_product_preview_image'] === '1') { + $widget._loadMedia(); + } + $input.trigger('change'); }, diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php index 10b632c002475..898d3ff400b38 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php @@ -15,17 +15,17 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Config\Value; use Magento\Framework\App\Http; +use Magento\Framework\App\Request\Http as HttpRequest; use Magento\Framework\Data\Form\FormKey; use Magento\Framework\Message\MessageInterface; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Stdlib\CookieManagerInterface; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; use Magento\TestFramework\Request; use Magento\TestFramework\Response; -use Zend\Stdlib\Parameters; -use Magento\Framework\App\Request\Http as HttpRequest; -use Magento\TestFramework\Mail\Template\TransportBuilderMock; -use Magento\Framework\Serialize\Serializer\Json; -use Magento\Framework\Stdlib\CookieManagerInterface; use Magento\Theme\Controller\Result\MessagePlugin; +use Zend\Stdlib\Parameters; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -169,6 +169,7 @@ public function testCreatepasswordActionWithDirectLink() $token = Bootstrap::getObjectManager()->get(\Magento\Framework\Math\Random::class) ->getUniqueHash(); $customer->changeResetPasswordLinkToken($token); + $customer->setData('confirmation', 'confirmation'); $customer->save(); $this->getRequest()->setParam('token', $token); @@ -187,6 +188,7 @@ public function testCreatepasswordActionWithDirectLink() $session = Bootstrap::getObjectManager()->get(Session::class); $this->assertEquals($token, $session->getRpToken()); $this->assertNotContains($token, $response->getHeader('Location')->getFieldValue()); + $this->assertCustomerConfirmationEquals(1, null); } /** @@ -201,6 +203,7 @@ public function testCreatepasswordActionWithSession() $token = Bootstrap::getObjectManager()->get(\Magento\Framework\Math\Random::class) ->getUniqueHash(); $customer->changeResetPasswordLinkToken($token); + $customer->setData('confirmation', 'confirmation'); $customer->save(); /** @var \Magento\Customer\Model\Session $customer */ @@ -213,6 +216,7 @@ public function testCreatepasswordActionWithSession() $response = $this->getResponse(); $text = $response->getBody(); $this->assertTrue((bool)preg_match('/' . $token . '/m', $text)); + $this->assertCustomerConfirmationEquals(1, null); } /** @@ -227,6 +231,7 @@ public function testCreatepasswordActionInvalidToken() $token = Bootstrap::getObjectManager()->get(\Magento\Framework\Math\Random::class) ->getUniqueHash(); $customer->changeResetPasswordLinkToken($token); + $customer->setData('confirmation', 'confirmation'); $customer->save(); $this->getRequest()->setParam('token', 'INVALIDTOKEN'); @@ -238,6 +243,19 @@ public function testCreatepasswordActionInvalidToken() $response = $this->getResponse(); $this->assertEquals(302, $response->getHttpResponseCode()); $this->assertContains('customer/account/forgotpassword', $response->getHeader('Location')->getFieldValue()); + $this->assertCustomerConfirmationEquals(1, 'confirmation'); + } + + /** + * @param int $customerId + * @param string|null $confirmation + */ + private function assertCustomerConfirmationEquals(int $customerId, string $confirmation = null) + { + /** @var \Magento\Customer\Model\Customer $customer */ + $customer = Bootstrap::getObjectManager() + ->create(\Magento\Customer\Model\Customer::class)->load($customerId); + $this->assertEquals($confirmation, $customer->getConfirmation()); } /** @@ -297,11 +315,13 @@ public function testWithConfirmCreatePostAction() $this->dispatch('customer/account/createPost'); $this->assertRedirect($this->stringContains('customer/account/index/')); $this->assertSessionMessages( - $this->equalTo([ - 'You must confirm your account. Please check your email for the confirmation link or ' + $this->equalTo( + [ + 'You must confirm your account. Please check your email for the confirmation link or ' . 'click here for a new link.' - ]), + ] + ), MessageInterface::TYPE_SUCCESS ); } @@ -315,10 +335,14 @@ public function testExistingEmailCreatePostAction() $this->dispatch('customer/account/createPost'); $this->assertRedirect($this->stringContains('customer/account/create/')); $this->assertSessionMessages( - $this->equalTo(['There is already an account with this email address. ' . - 'If you are sure that it is your email address, ' . - 'click here' . - ' to get your password and access your account.', ]), + $this->equalTo( + [ + 'There is already an account with this email address. ' . + 'If you are sure that it is your email address, ' . + 'click here' . + ' to get your password and access your account.', + ] + ), MessageInterface::TYPE_ERROR ); } @@ -330,7 +354,11 @@ public function testInactiveUserConfirmationAction() { $this->getRequest() ->setMethod('POST') - ->setPostValue(['email' => 'customer@needAconfirmation.com']); + ->setPostValue( + [ + 'email' => 'customer@needAconfirmation.com', + ] + ); $this->dispatch('customer/account/confirmation'); $this->assertRedirect($this->stringContains('customer/account/index')); @@ -347,14 +375,20 @@ public function testActiveUserConfirmationAction() { $this->getRequest() ->setMethod('POST') - ->setPostValue([ - 'email' => 'customer@example.com', - ]); + ->setPostValue( + [ + 'email' => 'customer@example.com', + ] + ); $this->dispatch('customer/account/confirmation'); $this->assertRedirect($this->stringContains('customer/account/index')); $this->assertSessionMessages( - $this->equalTo(['This email does not require confirmation.']), + $this->equalTo( + [ + 'This email does not require confirmation.', + ] + ), MessageInterface::TYPE_SUCCESS ); } @@ -395,9 +429,11 @@ public function testForgotPasswordPostWithBadEmailAction() { $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest() - ->setPostValue([ - 'email' => 'bad@email', - ]); + ->setPostValue( + [ + 'email' => 'bad@email', + ] + ); $this->dispatch('customer/account/forgotPasswordPost'); $this->assertRedirect($this->stringContains('customer/account/forgotpassword')); @@ -416,10 +452,12 @@ public function testResetPasswordPostNoTokenAction() ->setParam('id', 1) ->setParam('token', '8ed8677e6c79e68b94e61658bd756ea5') ->setMethod('POST') - ->setPostValue([ - 'password' => 'new-password', - 'password_confirmation' => 'new-password', - ]); + ->setPostValue( + [ + 'password' => 'new-password', + 'password_confirmation' => 'new-password', + ] + ); $this->dispatch('customer/account/resetPasswordPost'); $this->assertRedirect($this->stringContains('customer/account/')); @@ -439,10 +477,12 @@ public function testResetPasswordPostAction() ->setQueryValue('id', 1) ->setQueryValue('token', '8ed8677e6c79e68b94e61658bd756ea5') ->setMethod('POST') - ->setPostValue([ - 'password' => 'new-Password1', - 'password_confirmation' => 'new-Password1', - ]); + ->setPostValue( + [ + 'password' => 'new-Password1', + 'password_confirmation' => 'new-Password1', + ] + ); $this->dispatch('customer/account/resetPasswordPost'); $this->assertRedirect($this->stringContains('customer/account/login')); @@ -465,8 +505,11 @@ public function testEditAction() $this->assertEquals(200, $this->getResponse()->getHttpResponseCode(), $body); $this->assertContains('
', $body); // Verify the password check box is not checked - $this->assertContains('', $body); + $this->assertContains( + '', + $body + ); } /** @@ -510,14 +553,16 @@ public function testEditPostAction() $this->login(1); $this->getRequest() ->setMethod('POST') - ->setPostValue([ - 'form_key' => $this->_objectManager->get(FormKey::class)->getFormKey(), - 'firstname' => 'John', - 'lastname' => 'Doe', - 'email' => 'johndoe@email.com', - 'change_email' => 1, - 'current_password' => 'password' - ]); + ->setPostValue( + [ + 'form_key' => $this->_objectManager->get(FormKey::class)->getFormKey(), + 'firstname' => 'John', + 'lastname' => 'Doe', + 'email' => 'johndoe@email.com', + 'change_email' => 1, + 'current_password' => 'password' + ] + ); $this->dispatch('customer/account/editPost'); @@ -648,16 +693,18 @@ public function testWrongConfirmationEditPostAction() $this->login(1); $this->getRequest() ->setMethod('POST') - ->setPostValue([ - 'form_key' => $this->_objectManager->get(FormKey::class)->getFormKey(), - 'firstname' => 'John', - 'lastname' => 'Doe', - 'email' => 'johndoe@email.com', - 'change_password' => 1, - 'current_password' => 'password', - 'password' => 'new-password', - 'password_confirmation' => 'new-password-no-match', - ]); + ->setPostValue( + [ + 'form_key' => $this->_objectManager->get(FormKey::class)->getFormKey(), + 'firstname' => 'John', + 'lastname' => 'Doe', + 'email' => 'johndoe@email.com', + 'change_password' => 1, + 'current_password' => 'password', + 'password' => 'new-password', + 'password_confirmation' => 'new-password-no-match', + ] + ); $this->dispatch('customer/account/editPost'); @@ -841,13 +888,15 @@ private function getCustomerByEmail($email) */ private function prepareRequest() { - $post = new Parameters([ - 'form_key' => $this->_objectManager->get(FormKey::class)->getFormKey(), - 'login' => [ - 'username' => 'customer@example.com', - 'password' => 'password' + $post = new Parameters( + [ + 'form_key' => $this->_objectManager->get(FormKey::class)->getFormKey(), + 'login' => [ + 'username' => 'customer@example.com', + 'password' => 'password' + ] ] - ]); + ); $request = $this->getRequest(); $formKey = $this->_objectManager->get(FormKey::class); $request->setParam('form_key', $formKey->getFormKey()); @@ -913,6 +962,7 @@ private function getConfirmationUrlFromMessageContent(string $content): string if (preg_match('.*?)".*>', $content, $matches)) { $confirmationUrl = $matches['url']; $confirmationUrl = str_replace('http://localhost/index.php/', '', $confirmationUrl); + // phpcs:ignore Magento2.Functions.DiscouragedFunction $confirmationUrl = html_entity_decode($confirmationUrl); } diff --git a/lib/internal/Magento/Framework/App/Cache/FlushCacheByTags.php b/lib/internal/Magento/Framework/App/Cache/FlushCacheByTags.php index e2392b198492b..8f8dfd3baf1b6 100644 --- a/lib/internal/Magento/Framework/App/Cache/FlushCacheByTags.php +++ b/lib/internal/Magento/Framework/App/Cache/FlushCacheByTags.php @@ -1,18 +1,24 @@ cachePool = $cachePool; $this->cacheState = $cacheState; @@ -54,17 +58,14 @@ public function __construct( /** * Clean cache on save object * - * @param \Magento\Framework\Model\ResourceModel\AbstractResource $subject + * @param AbstractResource $subject * @param \Closure $proceed - * @param \Magento\Framework\Model\AbstractModel $object - * @return \Magento\Framework\Model\ResourceModel\AbstractResource + * @param AbstractModel $object + * @return AbstractResource * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function aroundSave( - \Magento\Framework\Model\ResourceModel\AbstractResource $subject, - \Closure $proceed, - \Magento\Framework\Model\AbstractModel $object - ) { + public function aroundSave(AbstractResource $subject, \Closure $proceed, AbstractModel $object): AbstractResource + { $result = $proceed($object); $tags = $this->tagResolver->getTags($object); $this->cleanCacheByTags($tags); @@ -75,39 +76,37 @@ public function aroundSave( /** * Clean cache on delete object * - * @param \Magento\Framework\Model\ResourceModel\AbstractResource $subject + * @param AbstractResource $subject * @param \Closure $proceed - * @param \Magento\Framework\Model\AbstractModel $object - * @return \Magento\Framework\Model\ResourceModel\AbstractResource + * @param AbstractModel $object + * @return AbstractResource * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function aroundDelete( - \Magento\Framework\Model\ResourceModel\AbstractResource $subject, - \Closure $proceed, - \Magento\Framework\Model\AbstractModel $object - ) { + public function aroundDelete(AbstractResource $subject, \Closure $proceed, AbstractModel $object): AbstractResource + { $tags = $this->tagResolver->getTags($object); $result = $proceed($object); $this->cleanCacheByTags($tags); + return $result; } /** * Clean cache by tags * - * @param string[] $tags + * @param string[] $tags * @return void */ - private function cleanCacheByTags($tags) + private function cleanCacheByTags(array $tags): void { - if (empty($tags)) { + if (!$tags) { return; } foreach ($this->cacheList as $cacheType) { if ($this->cacheState->isEnabled($cacheType)) { $this->cachePool->get($cacheType)->clean( - \Zend_Cache::CLEANING_MODE_MATCHING_TAG, - array_unique($tags) + \Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, + \array_unique($tags) ); } } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/Cache/FlushCacheByTagsTest.php b/lib/internal/Magento/Framework/App/Test/Unit/Cache/FlushCacheByTagsTest.php index e05399cd0bfcb..60dba582177eb 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/Cache/FlushCacheByTagsTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/Cache/FlushCacheByTagsTest.php @@ -3,9 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Framework\App\Test\Unit\Cache; +use Magento\Framework\App\Cache\FlushCacheByTags; +use Magento\Framework\App\Cache\StateInterface; +use Magento\Framework\App\Cache\Tag\Resolver; +use Magento\Framework\App\Cache\Type\FrontendPool; +use Magento\Framework\Model\AbstractModel; +use Magento\Framework\Model\ResourceModel\AbstractResource; + +/** + * Unit tests for the \Magento\Framework\App\Cache\FlushCacheByTags class. + */ class FlushCacheByTagsTest extends \PHPUnit\Framework\TestCase { /** @@ -28,13 +39,16 @@ class FlushCacheByTagsTest extends \PHPUnit\Framework\TestCase */ private $plugin; + /** + * @inheritdoc + */ protected function setUp() { - $this->cacheState = $this->getMockForAbstractClass(\Magento\Framework\App\Cache\StateInterface::class); - $this->frontendPool = $this->createMock(\Magento\Framework\App\Cache\Type\FrontendPool::class); - $this->tagResolver = $this->createMock(\Magento\Framework\App\Cache\Tag\Resolver::class); + $this->cacheState = $this->getMockForAbstractClass(StateInterface::class); + $this->frontendPool = $this->createMock(FrontendPool::class); + $this->tagResolver = $this->createMock(Resolver::class); - $this->plugin = new \Magento\Framework\App\Cache\FlushCacheByTags( + $this->plugin = new FlushCacheByTags( $this->frontendPool, $this->cacheState, ['test'], @@ -42,14 +56,19 @@ protected function setUp() ); } - public function testAroundSave() + /** + * @return void + */ + public function testAroundSave(): void { - $resource = $this->getMockBuilder(\Magento\Framework\Model\ResourceModel\AbstractResource::class) + $resource = $this->getMockBuilder(AbstractResource::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $model = $this->getMockBuilder(\Magento\Framework\Model\AbstractModel::class) + $model = $this->getMockBuilder(AbstractModel::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); + $this->tagResolver->expects($this->atLeastOnce())->method('getTags')->with($model)->willReturn([]); + $result = $this->plugin->aroundSave( $resource, function () use ($resource) { @@ -57,17 +76,23 @@ function () use ($resource) { }, $model ); + $this->assertSame($resource, $result); } - public function testAroundDelete() + /** + * @return void + */ + public function testAroundDelete(): void { - $resource = $this->getMockBuilder(\Magento\Framework\Model\ResourceModel\AbstractResource::class) + $resource = $this->getMockBuilder(AbstractResource::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $model = $this->getMockBuilder(\Magento\Framework\Model\AbstractModel::class) + $model = $this->getMockBuilder(AbstractModel::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); + $this->tagResolver->expects($this->atLeastOnce())->method('getTags')->with($model)->willReturn([]); + $result = $this->plugin->aroundDelete( $resource, function () use ($resource) { @@ -75,25 +100,7 @@ function () use ($resource) { }, $model ); - $this->assertSame($resource, $result); - } - public function testAroundSaveWithInterface() - { - $resource = $this->getMockBuilder(\Magento\Framework\Model\ResourceModel\AbstractResource::class) - - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $model = $this->getMockBuilder(\Magento\Framework\Model\AbstractModel::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $result = $this->plugin->aroundSave( - $resource, - function () use ($resource) { - return $resource; - }, - $model - ); $this->assertSame($resource, $result); } }