diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml index b9b31b780c8f7..afc332cc28378 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml @@ -191,6 +191,18 @@ + + + + + + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceConfigData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceConfigData.xml new file mode 100644 index 0000000000000..50ce7f2da18c7 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceConfigData.xml @@ -0,0 +1,19 @@ + + + + + + catalog/price/scope + 1 + + + catalog/price/scope + 0 + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml index bc552721e6ab8..af7e2786f6e8d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml @@ -19,5 +19,6 @@ + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml index 98b23a4669b72..7388ebc8408dd 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml @@ -8,6 +8,7 @@
+ diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Modifier/Attributes.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Modifier/Attributes.php new file mode 100644 index 0000000000000..01aaa6f8e0629 --- /dev/null +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Modifier/Attributes.php @@ -0,0 +1,64 @@ +escaper = $escaper; + $this->escapeAttributes = $escapeAttributes; + } + + /** + * @inheritdoc + */ + public function modifyData(array $data) + { + if (!empty($data) && !empty($this->escapeAttributes)) { + foreach ($data['items'] as &$item) { + foreach ($this->escapeAttributes as $escapeAttribute) { + if (isset($item[$escapeAttribute])) { + $item[$escapeAttribute] = $this->escaper->escapeHtml($item[$escapeAttribute]); + } + } + } + } + return $data; + } + + /** + * @inheritdoc + */ + public function modifyMeta(array $meta) + { + return $meta; + } +} diff --git a/app/code/Magento/Catalog/etc/adminhtml/di.xml b/app/code/Magento/Catalog/etc/adminhtml/di.xml index c04cfb2dce00a..b3c9230b3cfc6 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/di.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/di.xml @@ -166,7 +166,24 @@ Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool - + + + + + Magento\Catalog\Ui\DataProvider\Product\Modifier\Attributes + 10 + + + + + + + + name + sku + + + product_form.product_form diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml index 336a26db6b2b8..09053b5ad14a3 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml @@ -48,7 +48,6 @@ - @@ -57,8 +56,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml index e1c168b4a025f..c736dd8dde2cb 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml @@ -47,6 +47,7 @@
+ diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php index 7f987124040fd..54ef7102fcc47 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogUrlRewrite\Observer; use Magento\Catalog\Model\Category; @@ -18,6 +20,14 @@ */ class CategoryUrlPathAutogeneratorObserver implements ObserverInterface { + + /** + * Reserved endpoint names. + * + * @var string[] + */ + private $invalidValues = []; + /** * @var \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator */ @@ -38,22 +48,34 @@ class CategoryUrlPathAutogeneratorObserver implements ObserverInterface */ private $categoryRepository; + /** + * @var \Magento\Backend\App\Area\FrontNameResolver + */ + private $frontNameResolver; + /** * @param CategoryUrlPathGenerator $categoryUrlPathGenerator * @param ChildrenCategoriesProvider $childrenCategoriesProvider * @param \Magento\CatalogUrlRewrite\Service\V1\StoreViewService $storeViewService * @param CategoryRepositoryInterface $categoryRepository + * @param \Magento\Backend\App\Area\FrontNameResolver $frontNameResolver + * @param string[] $invalidValues */ public function __construct( CategoryUrlPathGenerator $categoryUrlPathGenerator, ChildrenCategoriesProvider $childrenCategoriesProvider, StoreViewService $storeViewService, - CategoryRepositoryInterface $categoryRepository + CategoryRepositoryInterface $categoryRepository, + \Magento\Backend\App\Area\FrontNameResolver $frontNameResolver = null, + array $invalidValues = [] ) { $this->categoryUrlPathGenerator = $categoryUrlPathGenerator; $this->childrenCategoriesProvider = $childrenCategoriesProvider; $this->storeViewService = $storeViewService; $this->categoryRepository = $categoryRepository; + $this->frontNameResolver = $frontNameResolver ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Backend\App\Area\FrontNameResolver::class); + $this->invalidValues = $invalidValues; } /** @@ -93,6 +115,17 @@ private function updateUrlKey($category, $urlKey) if (empty($urlKey)) { throw new \Magento\Framework\Exception\LocalizedException(__('Invalid URL key')); } + + if (in_array($urlKey, $this->getInvalidValues())) { + throw new \Magento\Framework\Exception\LocalizedException( + __( + 'URL key "%1" matches a reserved endpoint name (%2). Use another URL key.', + $urlKey, + implode(', ', $this->getInvalidValues()) + ) + ); + } + $category->setUrlKey($urlKey) ->setUrlPath($this->categoryUrlPathGenerator->getUrlPath($category)); if (!$category->isObjectNew()) { @@ -103,6 +136,16 @@ private function updateUrlKey($category, $urlKey) } } + /** + * Get reserved endpoint names. + * + * @return array + */ + private function getInvalidValues() + { + return array_unique(array_merge($this->invalidValues, [$this->frontNameResolver->getFrontName()])); + } + /** * Update url path for children category. * diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/AdminCategoryRestrictedUrlMessageData.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/AdminCategoryRestrictedUrlMessageData.xml new file mode 100644 index 0000000000000..b463b0524d5ff --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/AdminCategoryRestrictedUrlMessageData.xml @@ -0,0 +1,17 @@ + + + + + + URL key "admin" matches a reserved endpoint name (admin, soap, rest, graphql, standard). Use another URL key. + URL key "soap" matches a reserved endpoint name (admin, soap, rest, graphql, standard). Use another URL key. + URL key "rest" matches a reserved endpoint name (admin, soap, rest, graphql, standard). Use another URL key. + URL key "graphql" matches a reserved endpoint name (admin, soap, rest, graphql, standard). Use another URL key. + + diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminCategoryWithRestrictedUrlKeyNotCreatedTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminCategoryWithRestrictedUrlKeyNotCreatedTest.xml new file mode 100644 index 0000000000000..6538ec6e935df --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminCategoryWithRestrictedUrlKeyNotCreatedTest.xml @@ -0,0 +1,126 @@ + + + + + + + + + + <description value="Category with restricted Url Key cannot be created"/> + <severity value="MAJOR"/> + <testCaseId value="MC-17515"/> + <useCaseId value="MAGETWO-69825"/> + <group value="CatalogUrlRewrite"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <!--Delete created categories--> + <comment userInput="Delete created categories" stepKey="commentDeleteCreatedCategories"/> + <actionGroup ref="AdminDeleteCategoryByName" stepKey="deleteAdminCategory"> + <argument name="categoryName" value="admin"/> + </actionGroup> + <actionGroup ref="AdminDeleteCategoryByName" stepKey="deleteSoapCategory"> + <argument name="categoryName" value="soap"/> + </actionGroup> + <actionGroup ref="AdminDeleteCategoryByName" stepKey="deleteRestCategory"> + <argument name="categoryName" value="rest"/> + </actionGroup> + <actionGroup ref="AdminDeleteCategoryByName" stepKey="deleteGraphQlCategory"> + <argument name="categoryName" value="graphql"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Check category creation with restricted url key 'admin'--> + <comment userInput="Check category creation with restricted url key 'admin'" stepKey="commentCheckAdminCategoryCreation"/> + <actionGroup ref="goToCreateCategoryPage" stepKey="goToCreateAdminCategoryPage"/> + <actionGroup ref="FillCategoryNameAndUrlKeyAndSave" stepKey="fillAdminFirstCategoryForm"> + <argument name="categoryName" value="admin"/> + <argument name="categoryUrlKey" value=""/> + </actionGroup> + <see selector="{{AdminMessagesSection.errorMessage}}" userInput='{{AdminCategoryRestrictedUrlErrorMessage.urlAdmin}}' stepKey="seeAdminFirstErrorMessage"/> + <actionGroup ref="FillCategoryNameAndUrlKeyAndSave" stepKey="fillAdminSecondCategoryForm"> + <argument name="categoryName" value="{{SimpleSubCategory.name}}"/> + <argument name="categoryUrlKey" value="admin"/> + </actionGroup> + <see selector="{{AdminMessagesSection.errorMessage}}" userInput='{{AdminCategoryRestrictedUrlErrorMessage.urlAdmin}}' stepKey="seeAdminSecondErrorMessage"/> + <!--Create category with 'admin' name--> + <comment userInput="Create category with 'admin' name" stepKey="commentAdminCategoryCreation"/> + <actionGroup ref="FillCategoryNameAndUrlKeyAndSave" stepKey="fillAdminThirdCategoryForm"> + <argument name="categoryName" value="admin"/> + <argument name="categoryUrlKey" value="{{SimpleSubCategory.name}}"/> + </actionGroup> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="You saved the category." stepKey="seeAdminSuccessMessage"/> + <seeElement selector="{{AdminCategorySidebarTreeSection.categoryByName('admin')}}" stepKey="seeAdminCategoryInTree"/> + <!--Check category creation with restricted url key 'soap'--> + <comment userInput="Check category creation with restricted url key 'soap'" stepKey="commentCheckSoapCategoryCreation"/> + <actionGroup ref="goToCreateCategoryPage" stepKey="goToCreateSoapCategoryPage"/> + <actionGroup ref="FillCategoryNameAndUrlKeyAndSave" stepKey="fillSoapFirstCategoryForm"> + <argument name="categoryName" value="soap"/> + <argument name="categoryUrlKey" value=""/> + </actionGroup> + <see selector="{{AdminMessagesSection.errorMessage}}" userInput='{{AdminCategoryRestrictedUrlErrorMessage.urlSoap}}' stepKey="seeSoapFirstErrorMessage"/> + <actionGroup ref="FillCategoryNameAndUrlKeyAndSave" stepKey="fillSoapSecondCategoryForm"> + <argument name="categoryName" value="{{ApiCategory.name}}"/> + <argument name="categoryUrlKey" value="soap"/> + </actionGroup> + <see selector="{{AdminMessagesSection.errorMessage}}" userInput='{{AdminCategoryRestrictedUrlErrorMessage.urlSoap}}' stepKey="seeSoapSecondErrorMessage"/> + <!--Create category with 'soap' name--> + <comment userInput="Create category with 'soap' name" stepKey="commentSoapCategoryCreation"/> + <actionGroup ref="FillCategoryNameAndUrlKeyAndSave" stepKey="fillSoapThirdCategoryForm"> + <argument name="categoryName" value="soap"/> + <argument name="categoryUrlKey" value="{{ApiCategory.name}}"/> + </actionGroup> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="You saved the category." stepKey="seeSoapSuccessMessage"/> + <seeElement selector="{{AdminCategorySidebarTreeSection.categoryByName('soap')}}" stepKey="seeSoapCategoryInTree"/> + <!--Check category creation with restricted url key 'rest'--> + <comment userInput="Check category creation with restricted url key 'rest'" stepKey="commentCheckRestCategoryCreation"/> + <actionGroup ref="goToCreateCategoryPage" stepKey="goToCreateRestCategoryPage"/> + <actionGroup ref="FillCategoryNameAndUrlKeyAndSave" stepKey="fillRestFirstCategoryForm"> + <argument name="categoryName" value="rest"/> + <argument name="categoryUrlKey" value=""/> + </actionGroup> + <see selector="{{AdminMessagesSection.errorMessage}}" userInput='{{AdminCategoryRestrictedUrlErrorMessage.urlRest}}' stepKey="seeRestFirstErrorMessage"/> + <actionGroup ref="FillCategoryNameAndUrlKeyAndSave" stepKey="fillRestSecondCategoryForm"> + <argument name="categoryName" value="{{SubCategoryWithParent.name}}"/> + <argument name="categoryUrlKey" value="rest"/> + </actionGroup> + <see selector="{{AdminMessagesSection.errorMessage}}" userInput='{{AdminCategoryRestrictedUrlErrorMessage.urlRest}}' stepKey="seeRestSecondErrorMessage"/> + <!--Create category with 'rest' name--> + <comment userInput="Create category with 'rest' name" stepKey="commentRestCategoryCreation"/> + <actionGroup ref="FillCategoryNameAndUrlKeyAndSave" stepKey="fillRestThirdCategoryForm"> + <argument name="categoryName" value="rest"/> + <argument name="categoryUrlKey" value="{{SubCategoryWithParent.name}}"/> + </actionGroup> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="You saved the category." stepKey="seeRestSuccessMesdgssage"/> + <seeElement selector="{{AdminCategorySidebarTreeSection.categoryByName('rest')}}" stepKey="seeRestCategoryInTree"/> + <!--Check category creation with restricted url key 'graphql'--> + <comment userInput="Check category creation with restricted url key 'graphql'" stepKey="commentCheckGraphQlCategoryCreation"/> + <actionGroup ref="goToCreateCategoryPage" stepKey="goToCreateCategoryPage"/> + <actionGroup ref="FillCategoryNameAndUrlKeyAndSave" stepKey="fillGraphQlFirstCategoryForm"> + <argument name="categoryName" value="graphql"/> + <argument name="categoryUrlKey" value=""/> + </actionGroup> + <see selector="{{AdminMessagesSection.errorMessage}}" userInput='{{AdminCategoryRestrictedUrlErrorMessage.urlGraphql}}' stepKey="seeGraphQlFirstErrorMessage"/> + <actionGroup ref="FillCategoryNameAndUrlKeyAndSave" stepKey="fillGraphQlSecondCategoryForm"> + <argument name="categoryName" value="{{NewSubCategoryWithParent.name}}"/> + <argument name="categoryUrlKey" value="graphql"/> + </actionGroup> + <see selector="{{AdminMessagesSection.errorMessage}}" userInput='{{AdminCategoryRestrictedUrlErrorMessage.urlGraphql}}' stepKey="seeGraphQlSecondErrorMessage"/> + <!--Create category with 'graphql' name--> + <comment userInput="Create category with 'graphql' name" stepKey="commentGraphQlCategoryCreation"/> + <actionGroup ref="FillCategoryNameAndUrlKeyAndSave" stepKey="fillGraphQlThirdCategoryForm"> + <argument name="categoryName" value="graphql"/> + <argument name="categoryUrlKey" value="{{NewSubCategoryWithParent.name}}"/> + </actionGroup> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="You saved the category." stepKey="seeGraphQlSuccessMessage"/> + <seeElement selector="{{AdminCategorySidebarTreeSection.categoryByName('graphql')}}" stepKey="seeGraphQlCategoryInTree"/> + </test> +</tests> diff --git a/app/code/Magento/CatalogUrlRewrite/etc/di.xml b/app/code/Magento/CatalogUrlRewrite/etc/di.xml index e6fbcaefd0768..2a74b5cd92b28 100644 --- a/app/code/Magento/CatalogUrlRewrite/etc/di.xml +++ b/app/code/Magento/CatalogUrlRewrite/etc/di.xml @@ -45,4 +45,15 @@ </argument> </arguments> </type> + <type name="Magento\CatalogUrlRewrite\Observer\CategoryUrlPathAutogeneratorObserver"> + <arguments> + <argument name="invalidValues" xsi:type="array"> + <item name="0" xsi:type="string">admin</item> + <item name="1" xsi:type="string">soap</item> + <item name="2" xsi:type="string">rest</item> + <item name="3" xsi:type="string">graphql</item> + <item name="4" xsi:type="string">standard</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/CatalogUrlRewrite/i18n/en_US.csv b/app/code/Magento/CatalogUrlRewrite/i18n/en_US.csv index b3335dc3523ca..0f21e8ddf9fc9 100644 --- a/app/code/Magento/CatalogUrlRewrite/i18n/en_US.csv +++ b/app/code/Magento/CatalogUrlRewrite/i18n/en_US.csv @@ -5,4 +5,5 @@ "Product URL Suffix","Product URL Suffix" "Use Categories Path for Product URLs","Use Categories Path for Product URLs" "Create Permanent Redirect for URLs if URL Key Changed","Create Permanent Redirect for URLs if URL Key Changed" -"Generate "category/product" URL Rewrites","Generate "category/product" URL Rewrites" \ No newline at end of file +"Generate "category/product" URL Rewrites","Generate "category/product" URL Rewrites" +"URL key ""%1"" matches a reserved endpoint name (%2). Use another URL key.","URL key ""%1"" matches a reserved endpoint name (%2). Use another URL key." diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/ActionGroup/AdminCurrencyRatesActionGroup.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/ActionGroup/AdminCurrencyRatesActionGroup.xml new file mode 100644 index 0000000000000..6b8a93ef3542d --- /dev/null +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/ActionGroup/AdminCurrencyRatesActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSetCurrencyRatesActionGroup"> + <arguments> + <argument name="firstCurrency" type="string" defaultValue="USD"/> + <argument name="secondCurrency" type="string" defaultValue="EUR"/> + <argument name="rate" type="string" defaultValue="0.5"/> + </arguments> + <fillField selector="{{AdminCurrencyRatesSection.currencyRate(firstCurrency, secondCurrency)}}" userInput="{{rate}}" stepKey="setCurrencyRate"/> + <click selector="{{AdminCurrencyRatesSection.saveCurrencyRates}}" stepKey="clickSaveCurrencyRates"/> + <waitForPageLoad stepKey="waitForSave"/> + <see selector="{{AdminMessagesSection.success}}" userInput="{{AdminSaveCurrencyRatesMessageData.success}}" stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/ActionGroup/StorefrontCurrencyRatesActionGroup.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/ActionGroup/StorefrontCurrencyRatesActionGroup.xml new file mode 100644 index 0000000000000..61a6123b1a7af --- /dev/null +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/ActionGroup/StorefrontCurrencyRatesActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontSwitchCurrency"> + <arguments> + <argument name="currency" type="string" defaultValue="EUR"/> + </arguments> + <click selector="{{StorefrontSwitchCurrencyRatesSection.currencyTrigger}}" stepKey="openTrigger"/> + <waitForElementVisible selector="{{StorefrontSwitchCurrencyRatesSection.currency(currency)}}" stepKey="waitForCurrency"/> + <click selector="{{StorefrontSwitchCurrencyRatesSection.currency(currency)}}" stepKey="chooseCurrency"/> + <see selector="{{StorefrontSwitchCurrencyRatesSection.selectedCurrency}}" userInput="{{currency}}" stepKey="seeSelectedCurrency"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Data/AdminCurrencyRatesMessageData.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Data/AdminCurrencyRatesMessageData.xml new file mode 100644 index 0000000000000..90d22b06fcb80 --- /dev/null +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Data/AdminCurrencyRatesMessageData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminSaveCurrencyRatesMessageData"> + <data key="success">All valid rates have been saved.</data> + </entity> +</entities> diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Data/CurrencyRatesConfigData.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Data/CurrencyRatesConfigData.xml new file mode 100644 index 0000000000000..6194287dd058b --- /dev/null +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Data/CurrencyRatesConfigData.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="SetCurrencyUSDBaseConfig"> + <data key="path">currency/options/base</data> + <data key="value">USD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetCurrencyEURBaseConfig"> + <data key="path">currency/options/base</data> + <data key="value">EUR</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetAllowedCurrenciesConfigForUSD"> + <data key="path">currency/options/allow</data> + <data key="value">USD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetAllowedCurrenciesConfigForEUR"> + <data key="path">currency/options/allow</data> + <data key="value">EUR</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetAllowedCurrenciesConfigForRUB"> + <data key="path">currency/options/allow</data> + <data key="value">RUB</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetDefaultCurrencyEURConfig"> + <data key="path">currency/options/default</data> + <data key="value">EUR</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> + <entity name="SetDefaultCurrencyUSDConfig"> + <data key="path">currency/options/default</data> + <data key="value">USD</data> + <data key="scope">websites</data> + <data key="scope_code">base</data> + </entity> +</entities> diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Page/AdminCurrencyRatesPage.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Page/AdminCurrencyRatesPage.xml new file mode 100644 index 0000000000000..d31dd71d474bb --- /dev/null +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Page/AdminCurrencyRatesPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminCurrencyRatesPage" url="admin/system_currency/" area="admin" module="CurrencySymbol"> + <section name="AdminCurrencyRatesSection"/> + </page> +</pages> diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Section/AdminCurrencyRatesSection.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Section/AdminCurrencyRatesSection.xml new file mode 100644 index 0000000000000..a5799356eb459 --- /dev/null +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Section/AdminCurrencyRatesSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCurrencyRatesSection"> + <element name="import" type="button" selector="//button[@title='Import']"/> + <element name="saveCurrencyRates" type="button" selector="//button[@title='Save Currency Rates']"/> + <element name="oldRate" type="text" selector="//div[contains(@class, 'admin__field-note') and contains(text(), 'Old rate:')]/strong"/> + <element name="currencyRate" type="input" selector="input[name='rate[{{fistCurrency}}][{{secondCurrency}}]']" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Section/StorefrontSwitchCurrencyRatesSection.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Section/StorefrontSwitchCurrencyRatesSection.xml new file mode 100644 index 0000000000000..e69823ad68e0e --- /dev/null +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Section/StorefrontSwitchCurrencyRatesSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontSwitchCurrencyRatesSection"> + <element name="currencyTrigger" type="select" selector="#switcher-currency-trigger" timeout="30"/> + <element name="currency" type="button" selector="//div[@id='switcher-currency-trigger']/following-sibling::ul//a[contains(text(), '{{currency}}')]" parameterized="true" timeout="10"/> + <element name="selectedCurrency" type="text" selector="#switcher-currency-trigger span"/> + </section> +</sections> diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayWhenChooseThreeAllowedCurrenciesTest.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayWhenChooseThreeAllowedCurrenciesTest.xml new file mode 100644 index 0000000000000..26fbfd394be68 --- /dev/null +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayWhenChooseThreeAllowedCurrenciesTest.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminOrderRateDisplayWhenChooseThreeAllowedCurrenciesTest" extends="AdminOrderRateDisplayedInOneLineTest"> + <annotations> + <features value="CurrencySymbol"/> + <stories value="Currency rates order page"/> + <title value="Order rate converting currency for 'Base Currency' and 'Default Display Currency' displayed correct"/> + <description value="Order rate converting currency for 'Base Currency' and 'Default Display Currency' displayed correct"/> + <severity value="MAJOR"/> + <testCaseId value="MC-17255" /> + <useCaseId value="MAGETWO-67450"/> + <group value="currency"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create product--> + <createData entity="SimpleProduct2" stepKey="createNewProduct"/> + <!--Set Currency options for Website--> + <magentoCLI command="config:set --scope={{SetCurrencyUSDBaseConfig.scope}} --scope-code={{SetCurrencyUSDBaseConfig.scope_code}} {{SetCurrencyUSDBaseConfig.path}} {{SetCurrencyUSDBaseConfig.value}}" stepKey="setCurrencyBaseUSDWebsites"/> + <magentoCLI command="config:set --scope={{SetAllowedCurrenciesConfigForUSD.scope}} --scope-code={{SetAllowedCurrenciesConfigForUSD.scope_code}} {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForEUR.value}},{{SetAllowedCurrenciesConfigForRUB.value}}" stepKey="setAllowedCurrencyWebsitesEURandRUBandUSD"/> + <magentoCLI command="config:set --scope={{SetDefaultCurrencyEURConfig.scope}} --scope-code={{SetDefaultCurrencyEURConfig.scope_code}} {{SetDefaultCurrencyEURConfig.path}} {{SetDefaultCurrencyEURConfig.value}}" stepKey="setCurrencyDefaultEURWebsites"/> + </before> + <after> + <!--Delete created product--> + <comment userInput="Delete created product" stepKey="commentDeleteCreatedProduct"/> + <deleteData createDataKey="createNewProduct" stepKey="deleteNewProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Set currency rates--> + <amOnPage url="{{AdminCurrencyRatesPage.url}}" stepKey="gotToCurrencyRatesPageSecondTime"/> + <waitForPageLoad stepKey="waitForLoadRatesPageSecondTime"/> + <actionGroup ref="AdminSetCurrencyRatesActionGroup" stepKey="setCurrencyRates"> + <argument name="firstCurrency" value="USD"/> + <argument name="secondCurrency" value="RUB"/> + <argument name="rate" value="0.8"/> + </actionGroup> + <!--Open created product on Storefront and place for order--> + <amOnPage url="{{StorefrontProductPage.url($$createNewProduct.custom_attributes[url_key]$$)}}" stepKey="goToNewProductPage"/> + <waitForPageLoad stepKey="waitForNewProductPagePageLoad"/> + <actionGroup ref="StorefrontSwitchCurrency" stepKey="switchCurrency"> + <argument name="currency" value="RUB"/> + </actionGroup> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontNewProductPage"> + <argument name="productName" value="$$createNewProduct.name$$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="guestGoToCheckoutNewProductFromMinicart" /> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutNewFillingShippingSection"> + </actionGroup> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="guestSelectNewCheckMoneyOrderPayment" /> + <actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="guestPlaceNewOrder"> + <argument name="orderNumberMessage" value="CONST.successGuestCheckoutOrderNumberMessage" /> + <argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage" /> + </actionGroup> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabNewOrderNumber"/> + <!--Open order and check rates display in one line--> + <actionGroup ref="OpenOrderById" stepKey="openNewOrderById"> + <argument name="orderId" value="$grabNewOrderNumber"/> + </actionGroup> + <see selector="{{AdminOrderDetailsInformationSection.orderInformationTable}}" userInput="EUR / USD rate" stepKey="seeUSDandEURRate"/> + <see selector="{{AdminOrderDetailsInformationSection.orderInformationTable}}" userInput="RUB / USD rate:" stepKey="seeRUBandEURRate"/> + <grabMultiple selector="{{AdminOrderDetailsInformationSection.rate}}" stepKey="grabRates" /> + <assertEquals stepKey="assertRates"> + <actualResult type="variable">grabRates</actualResult> + <expectedResult type="array">['EUR / USD rate:', 'RUB / USD rate:']</expectedResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayedInOneLineTest.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayedInOneLineTest.xml new file mode 100644 index 0000000000000..dc6bdf3db542e --- /dev/null +++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayedInOneLineTest.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminOrderRateDisplayedInOneLineTest"> + <annotations> + <features value="CurrencySymbol"/> + <stories value="Currency rates order page"/> + <title value="Order rate converting currency for 'Base Currency' and 'Default Display Currency' displayed correct once"/> + <description value="Order rate converting currency for 'Base Currency' and 'Default Display Currency' displayed correct once"/> + <severity value="MAJOR"/> + <testCaseId value="MC-17255" /> + <useCaseId value="MAGETWO-67450"/> + <group value="currency"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create product--> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + <!--Set price scope website--> + <magentoCLI command="config:set {{CatalogPriceScopeWebsiteConfigData.path}} {{CatalogPriceScopeWebsiteConfigData.value}}" stepKey="setCatalogPriceScopeWebsite"/> + <!--Set Currency options for Default Config--> + <magentoCLI command="config:set {{SetCurrencyEURBaseConfig.path}} {{SetCurrencyEURBaseConfig.value}}" stepKey="setCurrencyBaseEUR"/> + <magentoCLI command="config:set {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForEUR.value}}" stepKey="setAllowedCurrencyEURandUSD"/> + <magentoCLI command="config:set {{SetDefaultCurrencyEURConfig.path}} {{SetDefaultCurrencyEURConfig.value}}" stepKey="setCurrencyDefaultEUR"/> + <!--Set Currency options for Website--> + <magentoCLI command="config:set --scope={{SetCurrencyUSDBaseConfig.scope}} --scope-code={{SetCurrencyUSDBaseConfig.scope_code}} {{SetCurrencyUSDBaseConfig.path}} {{SetCurrencyUSDBaseConfig.value}}" stepKey="setCurrencyBaseEURWebsites"/> + <magentoCLI command="config:set --scope={{SetAllowedCurrenciesConfigForUSD.scope}} --scope-code={{SetAllowedCurrenciesConfigForUSD.scope_code}} {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}},{{SetAllowedCurrenciesConfigForEUR.value}}" stepKey="setAllowedCurrencyWebsitesForEURandUSD"/> + <magentoCLI command="config:set --scope={{SetDefaultCurrencyEURConfig.scope}} --scope-code={{SetDefaultCurrencyEURConfig.scope_code}} {{SetDefaultCurrencyEURConfig.path}} {{SetDefaultCurrencyEURConfig.value}}" stepKey="setCurrencyDefaultEURWebsites"/> + </before> + <after> + <!--Delete created product--> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <!--Reset configurations--> + <magentoCLI command="config:set {{CatalogPriceScopeGlobalConfigData.path}} {{CatalogPriceScopeGlobalConfigData.value}}" stepKey="setCatalogPriceScopeGlobal"/> + <magentoCLI command="config:set {{SetCurrencyUSDBaseConfig.path}} {{SetCurrencyUSDBaseConfig.value}}" stepKey="setCurrencyBaseUSD"/> + <magentoCLI command="config:set {{SetDefaultCurrencyUSDConfig.path}} {{SetDefaultCurrencyUSDConfig.value}}" stepKey="setCurrencyDefaultUSD"/> + <magentoCLI command="config:set {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}}" stepKey="setAllowedCurrencyUSD"/> + <!--Set Currency options for Website--> + <magentoCLI command="config:set --scope={{SetCurrencyUSDBaseConfig.scope}} --scope-code={{SetCurrencyUSDBaseConfig.scope_code}} {{SetCurrencyUSDBaseConfig.path}} {{SetCurrencyUSDBaseConfig.value}}" stepKey="setCurrencyBaseUSDWebsites"/> + <magentoCLI command="config:set --scope={{SetDefaultCurrencyUSDConfig.scope}} --scope-code={{SetDefaultCurrencyUSDConfig.scope_code}} {{SetDefaultCurrencyUSDConfig.path}} {{SetDefaultCurrencyUSDConfig.value}}" stepKey="setCurrencyDefaultUSDWebsites"/> + <magentoCLI command="config:set --scope={{SetAllowedCurrenciesConfigForUSD.scope}} --scope-code={{SetAllowedCurrenciesConfigForUSD.scope_code}} {{SetAllowedCurrenciesConfigForUSD.path}} {{SetAllowedCurrenciesConfigForUSD.value}}" stepKey="setAllowedCurrencyUSDWebsites"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Open created product on Storefront and place for order--> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.custom_attributes[url_key]$$)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForProductPagePageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createProduct.name$$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="guestGoToCheckoutFromMinicart" /> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShippingSection"> + </actionGroup> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="guestSelectCheckMoneyOrderPayment" /> + <actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="guestPlaceOrder"> + <argument name="orderNumberMessage" value="CONST.successGuestCheckoutOrderNumberMessage" /> + <argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage" /> + </actionGroup> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> + <!--Open order and check rates display in one line--> + <actionGroup ref="OpenOrderById" stepKey="openOrderById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + <see selector="{{AdminOrderDetailsInformationSection.orderInformationTable}}" userInput="EUR / USD rate" stepKey="seeEURandUSDRate"/> + <grabMultiple selector="{{AdminOrderDetailsInformationSection.rate}}" stepKey="grabRate" /> + <assertEquals stepKey="assertSelectedCategories"> + <actualResult type="variable">grabRate</actualResult> + <expectedResult type="array">[EUR / USD rate:]</expectedResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Eav/Model/Validator/Attribute/Data.php b/app/code/Magento/Eav/Model/Validator/Attribute/Data.php index cd0d5141154c0..15dcea077c887 100644 --- a/app/code/Magento/Eav/Model/Validator/Attribute/Data.php +++ b/app/code/Magento/Eav/Model/Validator/Attribute/Data.php @@ -4,15 +4,15 @@ * See COPYING.txt for license details. */ +namespace Magento\Eav\Model\Validator\Attribute; + +use Magento\Eav\Model\Attribute; + /** * EAV attribute data validator * * @author Magento Core Team <core@magentocommerce.com> */ -namespace Magento\Eav\Model\Validator\Attribute; - -use Magento\Eav\Model\Attribute; - class Data extends \Magento\Framework\Validator\AbstractValidator { /** @@ -126,7 +126,7 @@ public function isValid($entity) $dataModel = $this->_attrDataFactory->create($attribute, $entity); $dataModel->setExtractedData($data); if (!isset($data[$attributeCode])) { - $data[$attributeCode] = null; + $data[$attributeCode] = ''; } $result = $dataModel->validateValue($data[$attributeCode]); if (true !== $result) { diff --git a/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/DataTest.php b/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/DataTest.php index 07ce6fbfc6a4c..acba37cc45788 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/DataTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/DataTest.php @@ -4,13 +4,54 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + +namespace Magento\Eav\Test\Unit\Model\Validator\Attribute; + /** * Test for \Magento\Eav\Model\Validator\Attribute\Data */ -namespace Magento\Eav\Test\Unit\Model\Validator\Attribute; - class DataTest extends \PHPUnit\Framework\TestCase { + /** + * @var \Magento\Eav\Model\AttributeDataFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $attrDataFactory; + + /** + * @var \Magento\Eav\Model\Validator\Attribute\Data + */ + private $model; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->attrDataFactory = $this->getMockBuilder(\Magento\Eav\Model\AttributeDataFactory::class) + ->setMethods(['create']) + ->setConstructorArgs( + [ + 'objectManager' => $this->createMock(\Magento\Framework\ObjectManagerInterface::class), + 'string' => $this->createMock(\Magento\Framework\Stdlib\StringUtils::class) + ] + ) + ->getMock(); + + $this->model = $this->objectManager->getObject( + \Magento\Eav\Model\Validator\Attribute\Data::class, + [ + '_attrDataFactory' => $this->attrDataFactory + ] + ); + } + /** * Testing \Magento\Eav\Model\Validator\Attribute\Data::isValid * @@ -381,13 +422,15 @@ public function testAddErrorMessages() protected function _getAttributeMock($attributeData) { $attribute = $this->getMockBuilder(\Magento\Eav\Model\Attribute::class) - ->setMethods([ - 'getAttributeCode', - 'getDataModel', - 'getFrontendInput', - '__wakeup', - 'getIsVisible', - ]) + ->setMethods( + [ + 'getAttributeCode', + 'getDataModel', + 'getFrontendInput', + '__wakeup', + 'getIsVisible', + ] + ) ->disableOriginalConstructor() ->getMock(); @@ -436,7 +479,7 @@ protected function _getDataModelMock($returnValue, $argument = null) $dataModel = $this->getMockBuilder( \Magento\Eav\Model\Attribute\Data\AbstractData::class )->disableOriginalConstructor()->setMethods( - ['validateValue'] + ['setExtractedData', 'validateValue'] )->getMockForAbstractClass(); if ($argument) { $dataModel->expects( @@ -466,4 +509,24 @@ protected function _getEntityMock() )->disableOriginalConstructor()->getMock(); return $entity; } + + /** + * Test for isValid() without data for attribute. + * + * @return void + */ + public function testIsValidWithoutData() : void + { + $attributeData = ['attribute_code' => 'attribute', 'frontend_input' => 'text', 'is_visible' => true]; + $entity = $this->_getEntityMock(); + $attribute = $this->_getAttributeMock($attributeData); + $dataModel = $this->_getDataModelMock(true, $this->logicalAnd($this->isEmpty(), $this->isType('string'))); + $dataModel->expects($this->once())->method('setExtractedData')->with([])->willReturnSelf(); + $this->attrDataFactory->expects($this->once()) + ->method('create') + ->with($attribute, $entity) + ->willReturn($dataModel); + $this->model->setAttributes([$attribute])->setData([]); + $this->assertTrue($this->model->isValid($entity)); + } } diff --git a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php index 6729fe722de56..d58af06da94cf 100644 --- a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php +++ b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Rule\Model\Condition; @@ -17,6 +18,7 @@ * @method setFormName() * @SuppressWarnings(PHPMD.ExcessivePublicCount) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * phpcs:disable Magento2.Classes.AbstractApi * @api * @since 100.0.2 */ @@ -390,7 +392,7 @@ public function getValueParsed() $value = reset($value); } if (!is_array($value) && $this->isArrayOperatorType() && $value) { - $value = preg_split('#\s*[,;]\s*#', $value, null, PREG_SPLIT_NO_EMPTY); + $value = preg_split('#\s*[,;]\s*#', (string) $value, -1, PREG_SPLIT_NO_EMPTY); } $this->setValueParsed($value); } @@ -419,8 +421,11 @@ public function getValue() { if ($this->getInputType() == 'date' && !$this->getIsValueParsed()) { // date format intentionally hard-coded + $date = $this->getData('value'); + $date = (\is_numeric($date) ? '@' : '') . $date; $this->setValue( - (new \DateTime($this->getData('value')))->format('Y-m-d H:i:s') + (new \DateTime($date, new \DateTimeZone((string) $this->_localeDate->getConfigTimezone()))) + ->format('Y-m-d H:i:s') ); $this->setIsValueParsed(true); } @@ -432,6 +437,7 @@ public function getValue() * * @return array|string * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * phpcs:disable Generic.Metrics.NestingLevel */ public function getValueName() { @@ -469,6 +475,7 @@ public function getValueName() } return $value; } + //phpcs:enable Generic.Metrics.NestingLevel /** * Get inherited conditions selectors @@ -674,6 +681,9 @@ public function getValueElement() $elementParams['placeholder'] = \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT; $elementParams['autocomplete'] = 'off'; $elementParams['readonly'] = 'true'; + $elementParams['value_name'] = + (new \DateTime($elementParams['value'], new \DateTimeZone($this->_localeDate->getConfigTimezone()))) + ->format('Y-m-d'); } return $this->getForm()->addField( $this->getPrefix() . '__' . $this->getId() . '__value', @@ -879,7 +889,7 @@ protected function _compareValues($validatedValue, $value, $strict = true) return $validatedValue == $value; } - $validatePattern = preg_quote($validatedValue, '~'); + $validatePattern = preg_quote((string) $validatedValue, '~'); if ($strict) { $validatePattern = '^' . $validatePattern . '$'; } diff --git a/app/code/Magento/Rule/Test/Unit/Model/Condition/AbstractConditionTest.php b/app/code/Magento/Rule/Test/Unit/Model/Condition/AbstractConditionTest.php index 52653197e3981..0ba41af04a1b3 100644 --- a/app/code/Magento/Rule/Test/Unit/Model/Condition/AbstractConditionTest.php +++ b/app/code/Magento/Rule/Test/Unit/Model/Condition/AbstractConditionTest.php @@ -55,14 +55,14 @@ public function validateAttributeDataProvider() ['0', '==', 1, false], ['1', '==', 1, true], ['x', '==', 'x', true], - ['x', '==', 0, false], + ['x', '==', '0', false], [1, '!=', 1, false], [0, '!=', 1, true], ['0', '!=', 1, true], ['1', '!=', 1, false], ['x', '!=', 'x', false], - ['x', '!=', 0, true], + ['x', '!=', '0', true], [1, '==', [1], true], [1, '!=', [1], false], @@ -164,15 +164,15 @@ public function validateAttributeArrayInputTypeDataProvider() [[1, 2, 3], '{}', '1', true, 'grid'], [[1, 2, 3], '{}', '8', false, 'grid'], - [[1, 2, 3], '{}', 5, false, 'grid'], + [[1, 2, 3], '{}', '5', false, 'grid'], [[1, 2, 3], '{}', [2, 3, 4], true, 'grid'], [[1, 2, 3], '{}', [4], false, 'grid'], [[3], '{}', [], false, 'grid'], [1, '{}', 1, false, 'grid'], [1, '!{}', [1, 2, 3], false, 'grid'], [[1], '{}', null, false, 'grid'], - [null, '{}', null, true, 'input'], - [null, '!{}', null, false, 'input'], + ['null', '{}', 'null', true, 'input'], + ['null', '!{}', 'null', false, 'input'], [null, '{}', [1], false, 'input'], [[1, 2, 3], '()', 1, true, 'select'], diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml index 71a96dc109385..653b1d48686e3 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml @@ -13,6 +13,8 @@ <element name="orderStatus" type="text" selector=".order-information table.order-information-table #order_status"/> <element name="purchasedFrom" type="text" selector=".order-information table.order-information-table tr:last-of-type > td"/> <element name="accountInformation" type="text" selector=".order-account-information-table"/> + <element name="orderInformationTable" type="block" selector=".order-information-table"/> + <element name="rate" type="text" selector="//table[contains(@class, 'order-information-table')]//th[contains(text(), 'rate:')]"/> <element name="customerName" type="text" selector=".order-account-information table tr:first-of-type > td span"/> <element name="customerEmail" type="text" selector=".order-account-information table tr:nth-of-type(2) > td a"/> <element name="customerGroup" type="text" selector=".order-account-information table tr:nth-of-type(3) > td"/> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml index ab5cd49449ece..825ac8205772e 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml @@ -9,6 +9,10 @@ */ $order = $block->getOrder(); +$baseCurrencyCode = (string)$order->getBaseCurrencyCode(); +$globalCurrencyCode = (string)$order->getGlobalCurrencyCode(); +$orderCurrencyCode = (string)$order->getOrderCurrencyCode(); + $orderAdminDate = $block->formatDate( $block->getOrderAdminDate($order->getCreatedAt()), \IntlDateFormatter::MEDIUM, @@ -23,6 +27,7 @@ $orderStoreDate = $block->formatDate( ); $customerUrl = $block->getCustomerViewUrl(); + $allowedAddressHtmlTags = ['b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'sub', 'sup', 'ul']; ?> @@ -93,15 +98,15 @@ $allowedAddressHtmlTags = ['b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'sub <td><?= $block->escapeHtml($order->getRemoteIp()); ?><?= $order->getXForwardedFor() ? ' (' . $block->escapeHtml($order->getXForwardedFor()) . ')' : ''; ?></td> </tr> <?php endif; ?> - <?php if ($order->getGlobalCurrencyCode() != $order->getBaseCurrencyCode()) : ?> + <?php if ($globalCurrencyCode !== $baseCurrencyCode) : ?> <tr> - <th><?= $block->escapeHtml(__('%1 / %2 rate:', $order->getGlobalCurrencyCode(), $order->getBaseCurrencyCode())) ?></th> + <th><?= $block->escapeHtml(__('%1 / %2 rate:', $globalCurrencyCode, $baseCurrencyCode)) ?></th> <td><?= $block->escapeHtml($order->getBaseToGlobalRate()) ?></td> </tr> <?php endif; ?> - <?php if ($order->getBaseCurrencyCode() != $order->getOrderCurrencyCode()) : ?> + <?php if ($baseCurrencyCode !== $orderCurrencyCode && $globalCurrencyCode !== $orderCurrencyCode) : ?> <tr> - <th><?= $block->escapeHtml(__('%1 / %2 rate:', $order->getOrderCurrencyCode(), $order->getBaseCurrencyCode())) ?></th> + <th><?= $block->escapeHtml(__('%1 / %2 rate:', $orderCurrencyCode, $baseCurrencyCode)) ?></th> <td><?= $block->escapeHtml($order->getBaseToOrderRate()) ?></td> </tr> <?php endif; ?> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/PriceRuleConditionsSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/PriceRuleConditionsSection.xml index 9a74ced2a2c17..89398051fcf67 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/PriceRuleConditionsSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/PriceRuleConditionsSection.xml @@ -11,6 +11,7 @@ <element name="rulesDropdown" type="select" selector="select[data-form-part='sales_rule_form'][data-ui-id='newchild-0-select-rule-conditions-1-new-child']"/> <element name="addProductAttributesButton" type="text" selector="#conditions__1--1__children>li>span>a"/> <element name="productAttributesDropdown" type="select" selector="#conditions__1--1__new_child"/> + <element name="firstProductAttributeSelected" type="select" selector="#conditions__1__children .rule-param:nth-of-type(2) a:nth-child(1)"/> <element name="changeCategoriesButton" type="text" selector="#conditions__1--1__children>li>span.rule-param:nth-of-type(2)>a"/> <element name="categoriesChooser" type="text" selector="#conditions__1--1__children>li>span.rule-param:nth-of-type(2)>span>label>a"/> <element name="treeRoot" type="text" selector=".x-tree-root-ct.x-tree-lines"/> diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php index 8ca6b20db3b5a..da358372e0895 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php @@ -247,10 +247,10 @@ public function testValidateCategoriesIgnoresVisibility(): void * @param boolean $isValid * @param string $conditionValue * @param string $operator - * @param double $productPrice + * @param string $productPrice * @dataProvider localisationProvider */ - public function testQuoteLocaleFormatPrice($isValid, $conditionValue, $operator = '>=', $productPrice = 2000.00) + public function testQuoteLocaleFormatPrice($isValid, $conditionValue, $operator = '>=', $productPrice = '2000.00') { $attr = $this->getMockBuilder(\Magento\Framework\Model\ResourceModel\Db\AbstractDb::class) ->disableOriginalConstructor() diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php index 5e91d8b2092da..b324bb6f62952 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/CategoryTest.php @@ -3,17 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\Catalog\Controller\Adminhtml; -use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Backend\App\Area\FrontNameResolver; +use Magento\Catalog\Model\ResourceModel\Product; use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Message\MessageInterface; +use Magento\Catalog\Api\CategoryRepositoryInterface; use Magento\Framework\Exception\NoSuchEntityException; use Magento\TestFramework\Helper\Bootstrap; use Magento\Store\Model\Store; -use Magento\Catalog\Model\ResourceModel\Product; /** * Test for category backend actions @@ -43,7 +44,7 @@ protected function setUp() } /** - * Test save action + * Test save action. * * @magentoDataFixture Magento/Store/_files/core_fixturestore.php * @magentoDbIsolation enabled @@ -57,7 +58,7 @@ protected function setUp() public function testSaveAction($inputData, $defaultAttributes, $attributesSaved = [], $isSuccess = true) { /** @var $store \Magento\Store\Model\Store */ - $store = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Store\Model\Store::class); + $store = Bootstrap::getObjectManager()->create(\Magento\Store\Model\Store::class); $store->load('fixturestore', 'code'); $storeId = $store->getId(); @@ -70,14 +71,12 @@ public function testSaveAction($inputData, $defaultAttributes, $attributesSaved if ($isSuccess) { $this->assertSessionMessages( $this->equalTo(['You saved the category.']), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS + MessageInterface::TYPE_SUCCESS ); } /** @var $category \Magento\Catalog\Model\Category */ - $category = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\Category::class - ); + $category = Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Category::class); $category->setStoreId($storeId); $category->load(2); @@ -428,12 +427,12 @@ public function testSaveActionCategoryWithDangerRequest() $this->dispatch('backend/catalog/category/save'); $this->assertSessionMessages( $this->equalTo(['The "Name" attribute value is empty. Set the attribute and try again.']), - \Magento\Framework\Message\MessageInterface::TYPE_ERROR + MessageInterface::TYPE_ERROR ); } /** - * Test move Action + * Test move action. * * @magentoDataFixture Magento/Catalog/_files/category_tree.php * @dataProvider moveActionDataProvider @@ -488,7 +487,7 @@ public function moveActionDataProvider() } /** - * Test save action + * Test save category with product position. * * @magentoDataFixture Magento/Catalog/_files/products_in_different_stores.php * @magentoDbIsolation disabled @@ -600,7 +599,7 @@ public function saveActionWithDifferentWebsitesDataProvider() } /** - * Get items count from catalog_category_product + * Get items count from catalog_category_product. * * @return int */ @@ -614,4 +613,36 @@ private function getCategoryProductsCount(): int $this->productResource->getConnection()->fetchAll($oldCategoryProducts) ); } + + /** + * Verify that the category cannot be saved if the category url matches the admin url. + * + * @magentoConfigFixture admin/url/use_custom_path 1 + * @magentoConfigFixture admin/url/custom_path backend + */ + public function testSaveWithCustomBackendNameAction() + { + $frontNameResolver = Bootstrap::getObjectManager()->create(FrontNameResolver::class); + $urlKey = $frontNameResolver->getFrontName(); + $inputData = [ + 'id' => '2', + 'url_key' => $urlKey, + 'use_config' => [ + 'available_sort_by' => 1, + 'default_sort_by' => 1 + ] + ]; + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setPostValue($inputData); + $this->dispatch('backend/catalog/category/save'); + $this->assertSessionMessages( + $this->equalTo( + [ + 'URL key "backend" matches a reserved endpoint name ' + . '(admin, soap, rest, graphql, standard, backend). Use another URL key.' + ] + ), + MessageInterface::TYPE_ERROR + ); + } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/quotes.php b/dev/tests/integration/testsuite/Magento/Sales/_files/quotes.php index b916fc0240417..3c4c164f0a01f 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/quotes.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/quotes.php @@ -5,6 +5,7 @@ */ declare(strict_types=1); +use Magento\Store\Model\StoreRepository; use Magento\Quote\Model\QuoteFactory; use Magento\Quote\Model\QuoteRepository; use Magento\TestFramework\Helper\Bootstrap; @@ -18,13 +19,18 @@ $quoteFactory = $objectManager->get(QuoteFactory::class); /** @var QuoteRepository $quoteRepository */ $quoteRepository = $objectManager->get(QuoteRepository::class); +/** @var StoreRepository $storeRepository */ +$storeRepository = $objectManager->get(StoreRepository::class); + +$defaultStore = $storeRepository->getActiveStoreByCode('default'); +$secondStore = $storeRepository->getActiveStoreByCode('fixture_second_store'); $quotes = [ 'quote for first store' => [ - 'store' => 1, + 'store' => $defaultStore->getId(), ], 'quote for second store' => [ - 'store' => 2, + 'store' => $secondStore->getId(), ], ];