diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminAttributeTextSwatchesCanBeFiledTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminAttributeTextSwatchesCanBeFiledTest.xml index 8ac7af096da0a..ef69764a87833 100644 --- a/app/code/Magento/Backend/Test/Mftf/Test/AdminAttributeTextSwatchesCanBeFiledTest.xml +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminAttributeTextSwatchesCanBeFiledTest.xml @@ -94,8 +94,7 @@ - - + diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php index f010536f06ee5..ef36af340994a 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php @@ -368,14 +368,20 @@ protected function _reindexRows($changedIds = []) $productsTypes = $this->getProductsTypes($changedIds); $parentProductsTypes = $this->getParentProductsTypes($changedIds); - $changedIds = array_merge($changedIds, ...array_values($parentProductsTypes)); + $changedIds = array_unique(array_merge($changedIds, ...array_values($parentProductsTypes))); $productsTypes = array_merge_recursive($productsTypes, $parentProductsTypes); if ($changedIds) { $this->deleteIndexData($changedIds); } - foreach ($productsTypes as $productType => $entityIds) { - $indexer = $this->_getIndexer($productType); + + $typeIndexers = $this->getTypeIndexers(); + foreach ($typeIndexers as $productType => $indexer) { + $entityIds = $productsTypes[$productType] ?? []; + if (empty($entityIds)) { + continue; + } + if ($indexer instanceof DimensionalIndexerInterface) { foreach ($this->dimensionCollectionFactory->create() as $dimensions) { $this->tableMaintainer->createMainTmpTable($dimensions); diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCategoryWithInactiveIncludeInMenuActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCategoryWithInactiveIncludeInMenuActionGroup.xml new file mode 100644 index 0000000000000..c407e9ba829d7 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCategoryWithInactiveIncludeInMenuActionGroup.xml @@ -0,0 +1,22 @@ + + + + + + + EXTENDS: CreateCategory. Add "disableIncludeInMenuOption" step. + + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateInactiveCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateInactiveCategoryActionGroup.xml new file mode 100644 index 0000000000000..b16ff59b91329 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateInactiveCategoryActionGroup.xml @@ -0,0 +1,22 @@ + + + + + + + EXTENDS: CreateCategory. Add "disableCategory" step. + + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminCategoryIsInactiveActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminCategoryIsInactiveActionGroup.xml new file mode 100644 index 0000000000000..cec6d42fc2dc4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminCategoryIsInactiveActionGroup.xml @@ -0,0 +1,18 @@ + + + + + + + Verify the category is disabled + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml index fafae5d535546..4b4aa20300d14 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml @@ -15,5 +15,6 @@ + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeGroupTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeGroupTest.xml new file mode 100644 index 0000000000000..04955807d7618 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeGroupTest.xml @@ -0,0 +1,85 @@ + + + + + + + <description value="Attribute value should be preserved after changing attribute group"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-35612"/> + <useCaseId value="MC-31892"/> + <group value="catalog"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <createData entity="productAttributeText" stepKey="createProductAttribute"/> + <createData entity="CatalogAttributeSet" stepKey="createAttributeSet"/> + <createData entity="CatalogAttributeSet" stepKey="createSecondAttributeSet"/> + + <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> + <amOnPage url="{{AdminProductAttributeSetEditPage.url}}/$createAttributeSet.attribute_set_id$/" + stepKey="onAttributeSetEdit"/> + <actionGroup ref="AssignAttributeToGroupActionGroup" stepKey="assignAttributeToGroup"> + <argument name="group" value="Product Details"/> + <argument name="attribute" value="$createProductAttribute.attribute_code$"/> + </actionGroup> + <actionGroup ref="SaveAttributeSetActionGroup" stepKey="saveAttributeSet"/> + <amOnPage url="{{AdminProductAttributeSetEditPage.url}}/$createSecondAttributeSet.attribute_set_id$/" + stepKey="onSecondAttributeSetEdit"/> + <actionGroup ref="AssignAttributeToGroupActionGroup" stepKey="assignAttributeToContentGroup"> + <argument name="group" value="Content"/> + <argument name="attribute" value="$createProductAttribute.attribute_code$"/> + </actionGroup> + <actionGroup ref="SaveAttributeSetActionGroup" stepKey="saveSecondAttributeSet"/> + </before> + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProductAttribute" stepKey="deleteProductAttribute"/> + <deleteData createDataKey="createAttributeSet" stepKey="deleteAttributeSet"/> + <deleteData createDataKey="createSecondAttributeSet" stepKey="deleteSecondAttributeSet"/> + <actionGroup ref="ClearProductsFilterActionGroup" stepKey="clearProductsFilter"/> + + <!-- Reindex invalidated indices after product attribute has been created/deleted --> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> + <argument name="product" value="$createSimpleProduct$"/> + </actionGroup> + + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct1"> + <argument name="product" value="$createSimpleProduct$"/> + </actionGroup> + + <actionGroup ref="AdminProductPageSelectAttributeSetActionGroup" stepKey="selectAttributeSet"> + <argument name="attributeSetName" value="$createAttributeSet.attribute_set_name$"/> + </actionGroup> + <waitForText userInput="$createProductAttribute.default_frontend_label$" stepKey="seeAttributeInForm"/> + <fillField selector="{{AdminProductFormSection.attributeRequiredInput($createProductAttribute.attribute_code$)}}" + userInput="test" + stepKey="fillProductAttributeValue"/> + <actionGroup ref="AdminProductPageSelectAttributeSetActionGroup" stepKey="selectSecondAttributeSet"> + <argument name="attributeSetName" value="$createSecondAttributeSet.attribute_set_name$"/> + </actionGroup> + <actionGroup ref="ExpandAdminProductSectionActionGroup" stepKey="expandContentSection"/> + <waitForText userInput="$createProductAttribute.default_frontend_label$" stepKey="seeAttributeInSection"/> + <grabValueFrom selector="{{AdminProductContentSection.attributeInput($createProductAttribute.attribute_code$)}}" + stepKey="attributeValue"/> + <assertEquals stepKey="assertAttributeValue"> + <expectedResult type="string">test</expectedResult> + <actualResult type="variable">attributeValue</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveCategoryTest.xml index a7dab57173377..6d7d56861b731 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveCategoryTest.xml @@ -26,20 +26,12 @@ </after> <!-- Create In active Category --> <actionGroup ref="AdminOpenCategoryPageActionGroup" stepKey="openAdminCategoryIndexPage"/> - <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton"/> - <click selector="{{AdminCategoryBasicFieldSection.enableCategoryLabel}}" stepKey="disableCategory"/> - <checkOption selector="{{AdminCategoryBasicFieldSection.IncludeInMenu}}" stepKey="enableIncludeInMenu"/> - <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{_defaultCategory.name}}" stepKey="fillCategoryName"/> - <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> - <waitForPageLoad stepKey="waitForCategorySaved"/> - <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage"/> - <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seePageTitle" /> - <dontSeeCheckboxIsChecked selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="dontCategoryIsChecked"/> - <!--Verify InActive Category is created--> - <seeElement selector="{{AdminCategoryContentSection.categoryInTree(_defaultCategory.name)}}" stepKey="seeCategoryInTree" /> + <actionGroup ref="AdminCreateInactiveCategoryActionGroup" stepKey="createInactiveCategory"/> + <actionGroup ref="AssertAdminCategoryIsInactiveActionGroup" stepKey="seeDisabledCategory"/> <!--Verify Category is not listed store front page--> - <amOnPage url="{{StorefrontCategoryPage.url(_defaultCategory.name)}}" stepKey="amOnCategoryPage"/> - <waitForPageLoad stepKey="waitForPageToBeLoaded"/> - <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="dontSeeCategoryOnStoreFrontPage"/> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="goToStoreFront"/> + <actionGroup ref="StorefrontAssertCategoryNameIsNotShownInMenuActionGroup" stepKey="doNotSeeCategoryNameInMenu"> + <argument name="categoryName" value="{{_defaultCategory.name}}"/> + </actionGroup> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveIncludeInMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveIncludeInMenuTest.xml index d3a766be2c99f..f60312f19a7e0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveIncludeInMenuTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveIncludeInMenuTest.xml @@ -26,21 +26,11 @@ </after> <!--Create Category with not included in menu Subcategory --> <actionGroup ref="AdminOpenCategoryPageActionGroup" stepKey="openAdminCategoryIndexPage"/> - <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton"/> - <checkOption selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="enableCategory"/> - <click selector="{{AdminCategoryBasicFieldSection.includeInMenuLabel}}" stepKey="disableIncludeInMenu"/> - <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{_defaultCategory.name}}" stepKey="fillCategoryName"/> - <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> - <waitForPageLoad stepKey="waitForCategorySaved"/> - <actionGroup ref="AssertAdminCategorySaveSuccessMessageActionGroup" stepKey="assertSuccessMessage"/> - <waitForPageLoad stepKey="waitForPageSaved"/> - <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seePageTitle" /> - <!--Verify Category is created/>--> - <seeElement selector="{{AdminCategoryContentSection.activeCategoryInTree(_defaultCategory.name)}}" stepKey="seeCategoryInTree" /> + <actionGroup ref="AdminCreateCategoryWithInactiveIncludeInMenuActionGroup" stepKey="createNotIncludedInMenuCategory"/> <!--Verify Category in store front page menu/>--> - <amOnPage url="{{StorefrontCategoryPage.url(_defaultCategory.name)}}" stepKey="amOnCategoryPage"/> - <waitForPageLoad stepKey="waitForPageToBeLoaded"/> - <see selector="{{StorefrontCategoryMainSection.CategoryTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seeCategoryPageTitle"/> - <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="dontSeeCategoryOnNavigation"/> + <actionGroup ref="CheckCategoryOnStorefrontActionGroup" stepKey="CheckCategoryOnStorefront"/> + <actionGroup ref="StorefrontAssertCategoryNameIsNotShownInMenuActionGroup" stepKey="doNotSeeCategoryOnNavigation"> + <argument name="categoryName" value="{{_defaultCategory.name}}"/> + </actionGroup> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeTest.xml index 88b1c874caadc..b8fec6d5bc001 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeTest.xml @@ -27,8 +27,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{ProductAttributePage.url}}" stepKey="navigateToNewProductAttributePage"/> - <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="AdminNavigateToNewProductAttributePageActionGroup" stepKey="navigateToNewProductAttributePage"/> <!-- Set attribute properties --> <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" diff --git a/app/code/Magento/Customer/Setup/Patch/Data/AddCustomerUpdatedAtAttribute.php b/app/code/Magento/Customer/Setup/Patch/Data/AddCustomerUpdatedAtAttribute.php index bad5735bc3e3a..1a8cdb8987db7 100644 --- a/app/code/Magento/Customer/Setup/Patch/Data/AddCustomerUpdatedAtAttribute.php +++ b/app/code/Magento/Customer/Setup/Patch/Data/AddCustomerUpdatedAtAttribute.php @@ -3,19 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Setup\Patch\Data; use Magento\Customer\Model\Customer; use Magento\Customer\Setup\CustomerSetupFactory; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** - * Class AddCustomerUpdatedAtAttribute - * @package Magento\Customer\Setup\Patch + * Class add customer updated attribute to customer */ class AddCustomerUpdatedAtAttribute implements DataPatchInterface, PatchVersionInterface { @@ -30,7 +29,6 @@ class AddCustomerUpdatedAtAttribute implements DataPatchInterface, PatchVersionI private $customerSetupFactory; /** - * AddCustomerUpdatedAtAttribute constructor. * @param ModuleDataSetupInterface $moduleDataSetup * @param CustomerSetupFactory $customerSetupFactory */ @@ -43,7 +41,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -61,10 +59,12 @@ public function apply() 'system' => false, ] ); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -74,7 +74,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -82,7 +82,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Customer/Setup/Patch/Data/AddNonSpecifiedGenderAttributeOption.php b/app/code/Magento/Customer/Setup/Patch/Data/AddNonSpecifiedGenderAttributeOption.php index ba50f6e17dd87..36611afc6a2aa 100644 --- a/app/code/Magento/Customer/Setup/Patch/Data/AddNonSpecifiedGenderAttributeOption.php +++ b/app/code/Magento/Customer/Setup/Patch/Data/AddNonSpecifiedGenderAttributeOption.php @@ -3,30 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Setup\Patch\Data; use Magento\Customer\Model\Customer; use Magento\Customer\Setup\CustomerSetupFactory; -use Magento\Directory\Model\AllowedCountries; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\Encryption\Encryptor; -use Magento\Framework\Indexer\IndexerRegistry; -use Magento\Framework\Setup\SetupInterface; -use Magento\Framework\Setup\UpgradeDataInterface; -use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; -use Magento\Store\Model\ScopeInterface; -use Magento\Store\Model\StoreManagerInterface; -use Magento\Framework\DB\FieldDataConverterFactory; -use Magento\Framework\DB\DataConverter\SerializedToJson; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** - * Class AddNonSpecifiedGenderAttributeOption - * @package Magento\Customer\Setup\Patch + * Class add non specified gender attribute option to customer */ class AddNonSpecifiedGenderAttributeOption implements DataPatchInterface, PatchVersionInterface { @@ -41,7 +29,6 @@ class AddNonSpecifiedGenderAttributeOption implements DataPatchInterface, PatchV private $customerSetupFactory; /** - * AddNonSpecifiedGenderAttributeOption constructor. * @param ModuleDataSetupInterface $moduleDataSetup * @param CustomerSetupFactory $customerSetupFactory */ @@ -54,7 +41,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -64,10 +51,12 @@ public function apply() $option = ['attribute_id' => $attributeId, 'values' => [3 => 'Not Specified']]; $customerSetup->addAttributeOption($option); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -77,7 +66,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -85,7 +74,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Customer/Setup/Patch/Data/AddSecurityTrackingAttributes.php b/app/code/Magento/Customer/Setup/Patch/Data/AddSecurityTrackingAttributes.php index b066d14a3c63e..09611ac1ccca3 100644 --- a/app/code/Magento/Customer/Setup/Patch/Data/AddSecurityTrackingAttributes.php +++ b/app/code/Magento/Customer/Setup/Patch/Data/AddSecurityTrackingAttributes.php @@ -3,19 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Setup\Patch\Data; use Magento\Customer\Model\Customer; use Magento\Customer\Setup\CustomerSetupFactory; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** - * Class AddSecurityTrackingAttributes - * @package Magento\Customer\Setup\Patch + * Class add security tracking attributes to customer */ class AddSecurityTrackingAttributes implements DataPatchInterface, PatchVersionInterface { @@ -30,7 +29,6 @@ class AddSecurityTrackingAttributes implements DataPatchInterface, PatchVersionI private $customerSetupFactory; /** - * AddSecurityTrackingAttributes constructor. * @param ModuleDataSetupInterface $moduleDataSetup * @param CustomerSetupFactory $customerSetupFactory */ @@ -43,7 +41,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -94,12 +92,14 @@ public function apply() $this->moduleDataSetup->getConnection()->update( $configTable, ['value' => new \Zend_Db_Expr('value*24')], - ['path = ?' => \Magento\Customer\Model\Customer::XML_PATH_CUSTOMER_RESET_PASSWORD_LINK_EXPIRATION_PERIOD] + ['path = ?' => Customer::XML_PATH_CUSTOMER_RESET_PASSWORD_LINK_EXPIRATION_PERIOD] ); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -109,7 +109,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -117,7 +117,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Customer/Setup/Patch/Data/ConvertValidationRulesFromSerializedToJson.php b/app/code/Magento/Customer/Setup/Patch/Data/ConvertValidationRulesFromSerializedToJson.php index 83c5fe7ae6d1e..e25fe5803e46c 100644 --- a/app/code/Magento/Customer/Setup/Patch/Data/ConvertValidationRulesFromSerializedToJson.php +++ b/app/code/Magento/Customer/Setup/Patch/Data/ConvertValidationRulesFromSerializedToJson.php @@ -3,19 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Setup\Patch\Data; use Magento\Framework\DB\FieldDataConverterFactory; use Magento\Framework\DB\DataConverter\SerializedToJson; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** - * Class ConvertValidationRulesFromSerializedToJson - * @package Magento\Customer\Setup\Patch + * Class convert validation rules from serialized to json for customer */ class ConvertValidationRulesFromSerializedToJson implements DataPatchInterface, PatchVersionInterface { @@ -30,7 +29,6 @@ class ConvertValidationRulesFromSerializedToJson implements DataPatchInterface, private $fieldDataConverterFactory; /** - * ConvertValidationRulesFromSerializedToJson constructor. * @param ModuleDataSetupInterface $moduleDataSetup * @param FieldDataConverterFactory $fieldDataConverterFactory */ @@ -43,7 +41,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -54,10 +52,12 @@ public function apply() 'attribute_id', 'validate_rules' ); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -67,7 +67,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -75,7 +75,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Customer/Setup/Patch/Data/DefaultCustomerGroupsAndAttributes.php b/app/code/Magento/Customer/Setup/Patch/Data/DefaultCustomerGroupsAndAttributes.php index 6e61b66f3c9db..fccda2ee9dac1 100644 --- a/app/code/Magento/Customer/Setup/Patch/Data/DefaultCustomerGroupsAndAttributes.php +++ b/app/code/Magento/Customer/Setup/Patch/Data/DefaultCustomerGroupsAndAttributes.php @@ -4,19 +4,20 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Customer\Setup\Patch\Data; use Magento\Customer\Setup\CustomerSetup; use Magento\Customer\Setup\CustomerSetupFactory; +use Magento\Eav\Model\Entity\Attribute\Backend\DefaultBackend; use Magento\Framework\Module\Setup\Migration; use Magento\Framework\Setup\ModuleDataSetupInterface; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** - * Class DefaultCustomerGroupsAndAttributes - * @package Magento\Customer\Setup\Patch + * Class default groups and attributes for customer */ class DefaultCustomerGroupsAndAttributes implements DataPatchInterface, PatchVersionInterface { @@ -31,20 +32,20 @@ class DefaultCustomerGroupsAndAttributes implements DataPatchInterface, PatchVer private $moduleDataSetup; /** - * DefaultCustomerGroupsAndAttributes constructor. * @param CustomerSetupFactory $customerSetupFactory * @param ModuleDataSetupInterface $moduleDataSetup */ public function __construct( CustomerSetupFactory $customerSetupFactory, - \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup + ModuleDataSetupInterface $moduleDataSetup ) { $this->customerSetupFactory = $customerSetupFactory; $this->moduleDataSetup = $moduleDataSetup; } /** - * {@inheritdoc} + * @inheritdoc + * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function apply() @@ -133,7 +134,7 @@ public function apply() 'customer_address', 'street', 'backend_model', - \Magento\Eav\Model\Entity\Attribute\Backend\DefaultBackend::class + DefaultBackend::class ); $migrationSetup = $this->moduleDataSetup->createMigrationSetup(); @@ -146,10 +147,12 @@ public function apply() ['attribute_id'] ); $migrationSetup->doUpdateClassAliases(); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -157,7 +160,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -165,7 +168,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Customer/Setup/Patch/Data/MigrateStoresAllowedCountriesToWebsite.php b/app/code/Magento/Customer/Setup/Patch/Data/MigrateStoresAllowedCountriesToWebsite.php index e4978070f53ad..1f21c7d4e83ba 100644 --- a/app/code/Magento/Customer/Setup/Patch/Data/MigrateStoresAllowedCountriesToWebsite.php +++ b/app/code/Magento/Customer/Setup/Patch/Data/MigrateStoresAllowedCountriesToWebsite.php @@ -3,9 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Setup\Patch\Data; +use Exception; use Magento\Directory\Model\AllowedCountries; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Store\Model\ScopeInterface; @@ -34,15 +36,14 @@ class MigrateStoresAllowedCountriesToWebsite implements DataPatchInterface, Patc private $allowedCountries; /** - * MigrateStoresAllowedCountriesToWebsite constructor. * @param ModuleDataSetupInterface $moduleDataSetup * @param StoreManagerInterface $storeManager * @param AllowedCountries $allowedCountries */ public function __construct( ModuleDataSetupInterface $moduleDataSetup, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Directory\Model\AllowedCountries $allowedCountries + StoreManagerInterface $storeManager, + AllowedCountries $allowedCountries ) { $this->moduleDataSetup = $moduleDataSetup; $this->storeManager = $storeManager; @@ -51,6 +52,8 @@ public function __construct( /** * @inheritdoc + * + * @throws Exception */ public function apply() { @@ -60,10 +63,12 @@ public function apply() try { $this->migrateStoresAllowedCountriesToWebsite(); $this->moduleDataSetup->getConnection()->commit(); - } catch (\Exception $e) { + } catch (Exception $e) { $this->moduleDataSetup->getConnection()->rollBack(); throw $e; } + + return $this; } /** diff --git a/app/code/Magento/Customer/Setup/Patch/Data/RemoveCheckoutRegisterAndUpdateAttributes.php b/app/code/Magento/Customer/Setup/Patch/Data/RemoveCheckoutRegisterAndUpdateAttributes.php index 51f54dc4a432c..5dfcf2bf9bf0d 100644 --- a/app/code/Magento/Customer/Setup/Patch/Data/RemoveCheckoutRegisterAndUpdateAttributes.php +++ b/app/code/Magento/Customer/Setup/Patch/Data/RemoveCheckoutRegisterAndUpdateAttributes.php @@ -3,30 +3,23 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Setup\Patch\Data; use Magento\Customer\Model\Customer; +use Magento\Customer\Model\ResourceModel\Address; +use Magento\Customer\Model\ResourceModel\Address\Attribute\Backend\Region; +use Magento\Customer\Model\ResourceModel\Address\Attribute\Source\Country; +use Magento\Customer\Model\ResourceModel\Attribute\Collection; use Magento\Customer\Setup\CustomerSetupFactory; -use Magento\Directory\Model\AllowedCountries; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\Encryption\Encryptor; -use Magento\Framework\Indexer\IndexerRegistry; -use Magento\Framework\Setup\SetupInterface; -use Magento\Framework\Setup\UpgradeDataInterface; -use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Eav\Model\Entity\Increment\NumericValue; use Magento\Framework\Setup\ModuleDataSetupInterface; -use Magento\Store\Model\ScopeInterface; -use Magento\Store\Model\StoreManagerInterface; -use Magento\Framework\DB\FieldDataConverterFactory; -use Magento\Framework\DB\DataConverter\SerializedToJson; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** - * Class RemoveCheckoutRegisterAndUpdateAttributes - * @package Magento\Customer\Setup\Patch + * Remove register and update attributes for checkout */ class RemoveCheckoutRegisterAndUpdateAttributes implements DataPatchInterface, PatchVersionInterface { @@ -41,7 +34,6 @@ class RemoveCheckoutRegisterAndUpdateAttributes implements DataPatchInterface, P private $customerSetupFactory; /** - * RemoveCheckoutRegisterAndUpdateAttributes constructor. * @param ModuleDataSetupInterface $moduleDataSetup * @param CustomerSetupFactory $customerSetupFactory */ @@ -54,7 +46,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -64,52 +56,54 @@ public function apply() ); $customerSetup = $this->customerSetupFactory->create(['setup' => $this->moduleDataSetup]); $customerSetup->updateEntityType( - \Magento\Customer\Model\Customer::ENTITY, + Customer::ENTITY, 'entity_model', \Magento\Customer\Model\ResourceModel\Customer::class ); $customerSetup->updateEntityType( - \Magento\Customer\Model\Customer::ENTITY, + Customer::ENTITY, 'increment_model', - \Magento\Eav\Model\Entity\Increment\NumericValue::class + NumericValue::class ); $customerSetup->updateEntityType( - \Magento\Customer\Model\Customer::ENTITY, + Customer::ENTITY, 'entity_attribute_collection', - \Magento\Customer\Model\ResourceModel\Attribute\Collection::class + Collection::class ); $customerSetup->updateEntityType( 'customer_address', 'entity_model', - \Magento\Customer\Model\ResourceModel\Address::class + Address::class ); $customerSetup->updateEntityType( 'customer_address', 'entity_attribute_collection', - \Magento\Customer\Model\ResourceModel\Address\Attribute\Collection::class + Address\Attribute\Collection::class ); $customerSetup->updateAttribute( 'customer_address', 'country_id', 'source_model', - \Magento\Customer\Model\ResourceModel\Address\Attribute\Source\Country::class + Country::class ); $customerSetup->updateAttribute( 'customer_address', 'region', 'backend_model', - \Magento\Customer\Model\ResourceModel\Address\Attribute\Backend\Region::class + Region::class ); $customerSetup->updateAttribute( 'customer_address', 'region_id', 'source_model', - \Magento\Customer\Model\ResourceModel\Address\Attribute\Source\Region::class + Address\Attribute\Source\Region::class ); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -119,7 +113,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -127,7 +121,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Customer/Setup/Patch/Data/UpdateAutocompleteOnStorefrontConfigPath.php b/app/code/Magento/Customer/Setup/Patch/Data/UpdateAutocompleteOnStorefrontConfigPath.php index 30435ace54d46..8b8092cbb22c6 100644 --- a/app/code/Magento/Customer/Setup/Patch/Data/UpdateAutocompleteOnStorefrontConfigPath.php +++ b/app/code/Magento/Customer/Setup/Patch/Data/UpdateAutocompleteOnStorefrontConfigPath.php @@ -3,17 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Setup\Patch\Data; -use Magento\Framework\App\ResourceConnection; +use Magento\Customer\Model\Form; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** - * Class UpdateAutocompleteOnStorefrontCOnfigPath - * @package Magento\Customer\Setup\Patch + * Update storefront's autocomplete of config path */ class UpdateAutocompleteOnStorefrontConfigPath implements DataPatchInterface, PatchVersionInterface { @@ -23,7 +23,6 @@ class UpdateAutocompleteOnStorefrontConfigPath implements DataPatchInterface, Pa private $moduleDataSetup; /** - * UpdateAutocompleteOnStorefrontCOnfigPath constructor. * @param ModuleDataSetupInterface $moduleDataSetup */ public function __construct( @@ -33,19 +32,21 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { $this->moduleDataSetup->getConnection()->update( $this->moduleDataSetup->getTable('core_config_data'), - ['path' => \Magento\Customer\Model\Form::XML_PATH_ENABLE_AUTOCOMPLETE], + ['path' => Form::XML_PATH_ENABLE_AUTOCOMPLETE], ['path = ?' => 'general/restriction/autocomplete_on_storefront'] ); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -55,7 +56,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -63,7 +64,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Customer/Setup/Patch/Data/UpdateCustomerAttributeInputFilters.php b/app/code/Magento/Customer/Setup/Patch/Data/UpdateCustomerAttributeInputFilters.php index 938cd3cd52e73..ff6decb1d2123 100644 --- a/app/code/Magento/Customer/Setup/Patch/Data/UpdateCustomerAttributeInputFilters.php +++ b/app/code/Magento/Customer/Setup/Patch/Data/UpdateCustomerAttributeInputFilters.php @@ -3,18 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Setup\Patch\Data; use Magento\Customer\Setup\CustomerSetupFactory; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** - * Class UpdateCustomerAttributeInputFilters - * @package Magento\Customer\Setup\Patch + * Update attribute input filters for customer */ class UpdateCustomerAttributeInputFilters implements DataPatchInterface, PatchVersionInterface { @@ -29,7 +28,6 @@ class UpdateCustomerAttributeInputFilters implements DataPatchInterface, PatchVe private $customerSetupFactory; /** - * UpdateCustomerAttributeInputFilters constructor. * @param ModuleDataSetupInterface $moduleDataSetup * @param CustomerSetupFactory $customerSetupFactory */ @@ -42,7 +40,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -72,10 +70,12 @@ public function apply() ], ]; $customerSetup->upgradeAttributes($entityAttributes); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -85,7 +85,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -93,7 +93,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Customer/Setup/Patch/Data/UpdateIdentifierCustomerAttributesVisibility.php b/app/code/Magento/Customer/Setup/Patch/Data/UpdateIdentifierCustomerAttributesVisibility.php index 7d0cad768d6b0..8519fab81efc5 100644 --- a/app/code/Magento/Customer/Setup/Patch/Data/UpdateIdentifierCustomerAttributesVisibility.php +++ b/app/code/Magento/Customer/Setup/Patch/Data/UpdateIdentifierCustomerAttributesVisibility.php @@ -3,18 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Setup\Patch\Data; use Magento\Customer\Setup\CustomerSetupFactory; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** - * Class UpdateIdentifierCustomerAttributesVisibility - * @package Magento\Customer\Setup\Patch + * Update identifier attributes visibility for customer */ class UpdateIdentifierCustomerAttributesVisibility implements DataPatchInterface, PatchVersionInterface { @@ -29,7 +28,6 @@ class UpdateIdentifierCustomerAttributesVisibility implements DataPatchInterface private $customerSetupFactory; /** - * UpdateIdentifierCustomerAttributesVisibility constructor. * @param ModuleDataSetupInterface $moduleDataSetup * @param CustomerSetupFactory $customerSetupFactory */ @@ -42,7 +40,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -70,10 +68,12 @@ public function apply() ], ]; $customerSetup->upgradeAttributes($entityAttributes); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -83,7 +83,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -91,7 +91,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Customer/Setup/Patch/Data/UpdateVATNumber.php b/app/code/Magento/Customer/Setup/Patch/Data/UpdateVATNumber.php index d31301eedf4b1..ea3207c7ccb85 100644 --- a/app/code/Magento/Customer/Setup/Patch/Data/UpdateVATNumber.php +++ b/app/code/Magento/Customer/Setup/Patch/Data/UpdateVATNumber.php @@ -3,27 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Setup\Patch\Data; -use Magento\Customer\Model\Customer; use Magento\Customer\Setup\CustomerSetupFactory; -use Magento\Directory\Model\AllowedCountries; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\Encryption\Encryptor; -use Magento\Framework\Indexer\IndexerRegistry; -use Magento\Framework\Setup\SetupInterface; -use Magento\Framework\Setup\UpgradeDataInterface; -use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; -use Magento\Store\Model\ScopeInterface; -use Magento\Store\Model\StoreManagerInterface; -use Magento\Framework\DB\FieldDataConverterFactory; -use Magento\Framework\DB\DataConverter\SerializedToJson; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; +/** + * Upgrade vat number + */ class UpdateVATNumber implements DataPatchInterface, PatchVersionInterface { /** @@ -37,7 +28,6 @@ class UpdateVATNumber implements DataPatchInterface, PatchVersionInterface private $customerSetupFactory; /** - * UpdateVATNumber constructor. * @param ModuleDataSetupInterface $moduleDataSetup * @param CustomerSetupFactory $customerSetupFactory */ @@ -50,16 +40,23 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { $customerSetup = $this->customerSetupFactory->create(['resourceConnection' => $this->moduleDataSetup]); - $customerSetup->updateAttribute('customer_address', 'vat_id', 'frontend_label', 'VAT Number'); + $customerSetup->updateAttribute( + 'customer_address', + 'vat_id', + 'frontend_label', + 'VAT Number' + ); + + return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -69,7 +66,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -77,7 +74,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Customer/Setup/Patch/Data/UpgradePasswordHashAndAddress.php b/app/code/Magento/Customer/Setup/Patch/Data/UpgradePasswordHashAndAddress.php index 3b8f96a037343..5d6e490bead22 100644 --- a/app/code/Magento/Customer/Setup/Patch/Data/UpgradePasswordHashAndAddress.php +++ b/app/code/Magento/Customer/Setup/Patch/Data/UpgradePasswordHashAndAddress.php @@ -3,19 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Setup\Patch\Data; use Magento\Customer\Setup\CustomerSetupFactory; use Magento\Framework\Encryption\Encryptor; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** - * Class UpgradePasswordHashAndAddress - * @package Magento\Customer\Setup\Patch + * Update passwordHash and address */ class UpgradePasswordHashAndAddress implements DataPatchInterface, PatchVersionInterface { @@ -30,7 +29,6 @@ class UpgradePasswordHashAndAddress implements DataPatchInterface, PatchVersionI private $customerSetupFactory; /** - * UpgradePasswordHashAndAddress constructor. * @param ModuleDataSetupInterface $moduleDataSetup * @param CustomerSetupFactory $customerSetupFactory */ @@ -43,7 +41,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -58,9 +56,13 @@ public function apply() ]; $customerSetup = $this->customerSetupFactory->create(['setup' => $this->moduleDataSetup]); $customerSetup->upgradeAttributes($entityAttributes); + + return $this; } /** + * Password hash upgrade + * * @return void */ private function upgradeHash() @@ -93,7 +95,7 @@ private function upgradeHash() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -103,7 +105,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -111,7 +113,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminMarketingDeleteNewsletterSubscriberFromGridActionGroup.xml b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminMarketingDeleteNewsletterSubscriberFromGridActionGroup.xml new file mode 100644 index 0000000000000..ed60c1509e453 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminMarketingDeleteNewsletterSubscriberFromGridActionGroup.xml @@ -0,0 +1,19 @@ +<?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="AdminMarketingDeleteNewsletterSubscriberFromGridActionGroup"> + <click selector="{{AdminNewsletterSubscriberGridSection.rowCheckbox('1')}}" stepKey="clickOnFirstCheckbox"/> + <selectOption selector="{{AdminNewsletterSubscriberGridSection.actionsDropdown}}" userInput="Delete" stepKey="selectDelete"/> + <click selector="{{AdminNewsletterSubscriberGridSection.submit}}" stepKey="clickSubmitBtn"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{AdminNewsletterSubscriberGridSection.okButton}}" stepKey="clickOkButton"/> + <waitForPageLoad stepKey="waitForResultsLoading"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminMarketingFindNewsletterSubscribersInGridActionGroup.xml b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminMarketingFindNewsletterSubscribersInGridActionGroup.xml new file mode 100644 index 0000000000000..9f9231397a1f8 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AdminMarketingFindNewsletterSubscribersInGridActionGroup.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="AdminMarketingFindNewsletterSubscribersInGridActionGroup"> + <arguments> + <argument name="email" type="string"/> + </arguments> + + <click stepKey="resetFilter" selector="{{AdminNewsletterSubscriberGridSection.resetFilter}}"/> + <waitForPageLoad stepKey="waitForPageLoading"/> + <fillField stepKey="fillEmailField" selector="{{AdminNewsletterSubscriberGridSection.emailField}}" userInput="{{email}}"/> + <click stepKey="clickSearchButton" selector="{{AdminNewsletterSubscriberGridSection.searchButton}}"/> + <waitForPageLoad stepKey="waitForResultsLoading"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AssertAdminDeletedNewsletterSubscriberIsNotInGridActionGroup.xml b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AssertAdminDeletedNewsletterSubscriberIsNotInGridActionGroup.xml new file mode 100644 index 0000000000000..e114a4e640fa2 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/AssertAdminDeletedNewsletterSubscriberIsNotInGridActionGroup.xml @@ -0,0 +1,17 @@ +<?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="AssertAdminDeletedNewsletterSubscriberIsNotInGridActionGroup"> + <arguments> + <argument name="email" type="string"/> + </arguments> + <dontSee selector="{{AdminNewsletterSubscriberGridSection.email('1')}}" userInput="{{email}}" stepKey="dontSeeSubscriber"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Section/AdminNewsletterSubscriberGridSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/AdminNewsletterSubscriberGridSection.xml index 3332041817150..26512a28c9f3d 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Section/AdminNewsletterSubscriberGridSection.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Section/AdminNewsletterSubscriberGridSection.xml @@ -12,5 +12,12 @@ <element name="type" type="text" selector="//table[contains(@class, 'data-grid')]/tbody/tr[{{row}}][@data-role='row']/td[@data-column='type']" parameterized="true"/> <element name="firstName" type="text" selector="//table[contains(@class, 'data-grid')]/tbody/tr[{{row}}][@data-role='row']/td[@data-column='firstname']" parameterized="true"/> <element name="lastName" type="text" selector="//table[contains(@class, 'data-grid')]/tbody/tr[{{row}}][@data-role='row']/td[@data-column='lastname']" parameterized="true"/> + <element name="resetFilter" type="button" selector=".action-default.scalable.action-reset.action-tertiary"/> + <element name="emailField" type="input" selector=".col-email #subscriberGrid_filter_email"/> + <element name="searchButton" type="button" selector="//*[@class='admin__filter-actions']//*[text()='Search']"/> + <element name="rowCheckbox" type="checkbox" selector="table.data-grid tbody > tr:nth-of-type({{row}}) td.data-grid-checkbox-cell input" parameterized="true"/> + <element name="actionsDropdown" type="select" selector=".admin__grid-massaction-form #subscriberGrid_massaction-select"/> + <element name="submit" type="button" selector="//*[@class='admin__grid-massaction-form']//*[text()='Submit']"/> + <element name="okButton" type="button" selector="//footer[@class='modal-footer']/button[contains(@class, 'action-accept')]" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingDeleteNewsletterSubscriberTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingDeleteNewsletterSubscriberTest.xml new file mode 100644 index 0000000000000..eea77a6be0784 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminMarketingDeleteNewsletterSubscriberTest.xml @@ -0,0 +1,50 @@ +<?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="AdminMarketingDeleteNewsletterSubscriberTest"> + <annotations> + <features value="Newsletter"/> + <stories value="Subscribers Deleting"/> + <title value="Admin deletes newsletter subscribers"/> + <description value="Admin should be able delete newsletter subscribers"/> + <group value="newsletter"/> + </annotations> + <before> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="deleteCreatedCustomer"/> + </after> + + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <actionGroup ref="StorefrontCustomerNavigateToNewsletterPageActionGroup" stepKey="navigateToNewsletterPage"/> + <actionGroup ref="StorefrontCustomerUpdateGeneralSubscriptionActionGroup" stepKey="subscribeToNewsletter"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAsAdmin"/> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToNewsletterSubscribersPage"> + <argument name="menuUiId" value="{{AdminMenuMarketing.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuMarketingCommunicationsNewsletterSubscribers.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminMarketingFindNewsletterSubscribersInGridActionGroup" stepKey="findSubscriber"> + <argument name="email" value="{{Simple_US_Customer.email}}"/> + </actionGroup> + <actionGroup ref="AdminMarketingDeleteNewsletterSubscriberFromGridActionGroup" stepKey="deleteSubscriber"/> + <actionGroup ref="AssertMessageInAdminPanelActionGroup" stepKey="assertSuccessMessage"> + <argument name="message" value="Total of 1 record(s) were deleted."/> + </actionGroup> + <actionGroup ref="AdminMarketingFindNewsletterSubscribersInGridActionGroup" stepKey="findDeletedSubscriber"> + <argument name="email" value="{{Simple_US_Customer.email}}"/> + </actionGroup> + <actionGroup ref="AssertAdminDeletedNewsletterSubscriberIsNotInGridActionGroup" stepKey="dontSeeSubscriber"> + <argument name="email" value="{{Simple_US_Customer.email}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Address/Collection.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Address/Collection.php index 8af6c03b44275..f2a28b613cfea 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Order/Address/Collection.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Address/Collection.php @@ -6,12 +6,19 @@ namespace Magento\Sales\Model\ResourceModel\Order\Address; use Magento\Sales\Api\Data\OrderAddressSearchResultInterface; -use \Magento\Sales\Model\ResourceModel\Order\Collection\AbstractCollection; +use Magento\Sales\Model\ResourceModel\Order\Collection\AbstractCollection; +use Magento\Framework\Locale\ResolverInterface; +use Magento\Framework\Data\Collection\EntityFactoryInterface; +use Magento\Framework\Data\Collection\Db\FetchStrategyInterface; +use Magento\Framework\Event\ManagerInterface; +use Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\Framework\App\ObjectManager; +use Psr\Log\LoggerInterface; /** - * Flat sales order payment collection - * - * @author Magento Core Team <core@magentocommerce.com> + * Order addresses collection */ class Collection extends AbstractCollection implements OrderAddressSearchResultInterface { @@ -29,6 +36,44 @@ class Collection extends AbstractCollection implements OrderAddressSearchResultI */ protected $_eventObject = 'order_address_collection'; + /** + * @var ResolverInterface + */ + private $localeResolver; + + /** + * @param EntityFactoryInterface $entityFactory + * @param LoggerInterface $logger + * @param FetchStrategyInterface $fetchStrategy + * @param ManagerInterface $eventManager + * @param Snapshot $entitySnapshot + * @param AdapterInterface|null $connection + * @param AbstractDb|null $resource + * @param ResolverInterface|null $localeResolver + */ + public function __construct( + EntityFactoryInterface $entityFactory, + LoggerInterface $logger, + FetchStrategyInterface $fetchStrategy, + ManagerInterface $eventManager, + Snapshot $entitySnapshot, + AdapterInterface $connection = null, + AbstractDb $resource = null, + ResolverInterface $localeResolver = null + ) { + $this->localeResolver = $localeResolver ?: ObjectManager::getInstance() + ->get(ResolverInterface::class); + parent::__construct( + $entityFactory, + $logger, + $fetchStrategy, + $eventManager, + $entitySnapshot, + $connection, + $resource + ); + } + /** * Model initialization * @@ -42,6 +87,16 @@ protected function _construct() ); } + /** + * @inheritdoc + */ + protected function _initSelect() + { + parent::_initSelect(); + $this->joinRegions(); + return $this; + } + /** * Redeclare after load method for dispatch event * @@ -55,4 +110,31 @@ protected function _afterLoad() return $this; } + + /** + * Join region name table with current locale + * + * @return $this + */ + private function joinRegions() + { + $locale = $this->localeResolver->getLocale(); + $connection = $this->getConnection(); + + $defaultNameExpr = $connection->getIfNullSql( + $connection->quoteIdentifier('rct.default_name'), + $connection->quoteIdentifier('main_table.region') + ); + $expression = $connection->getIfNullSql($connection->quoteIdentifier('rnt.name'), $defaultNameExpr); + + $regionId = $connection->quoteIdentifier('main_table.region_id'); + $condition = $connection->quoteInto("rnt.locale=?", $locale); + $rctTable = $this->getTable('directory_country_region'); + $rntTable = $this->getTable('directory_country_region_name'); + + $this->getSelect() + ->joinLeft(['rct' => $rctTable], "rct.region_id={$regionId}", []) + ->joinLeft(['rnt' => $rntTable], "rnt.region_id={$regionId} AND {$condition}", ['region' => $expression]); + return $this; + } } diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/Address.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/Address.php index 0fec004a25fae..274132a7fea50 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/Address.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/Address.php @@ -9,9 +9,6 @@ use Magento\Sales\Model\Order; use Magento\Sales\Model\ResourceModel\Attribute; -/** - * Class Address - */ class Address { /** @@ -69,7 +66,7 @@ public function process(Order $order) $attributesForSave[] = 'billing_address_id'; } $shippingAddress = $order->getShippingAddress(); - if ($shippingAddress && $order->getShippigAddressId() != $shippingAddress->getId()) { + if ($shippingAddress && $order->getShippingAddressId() != $shippingAddress->getId()) { $order->setShippingAddressId($shippingAddress->getId()); $attributesForSave[] = 'shipping_address_id'; } diff --git a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/AddressTest.php b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/AddressTest.php index 5267686a447cc..0978dda09f7a7 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/AddressTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/AddressTest.php @@ -133,6 +133,66 @@ public function testProcessShippingAddress() $this->assertEquals($this->address, $this->address->process($this->orderMock)); } + /** + * Test processing of the shipping address when shipping address id was not changed. + * setShippingAddressId and saveAttribute methods must not be executed. + */ + public function testProcessShippingAddressNotChanged() + { + $this->orderMock->expects($this->exactly(2)) + ->method('getAddresses') + ->willReturn([$this->addressMock]); + $this->addressMock->expects($this->once()) + ->method('save')->willReturnSelf(); + $this->orderMock->expects($this->once()) + ->method('getBillingAddress') + ->willReturn(null); + $this->orderMock->expects($this->once()) + ->method('getShippingAddress') + ->willReturn($this->addressMock); + $this->addressMock->expects($this->once()) + ->method('getId')->willReturn(1); + $this->orderMock->expects($this->once()) + ->method('getShippingAddressId') + ->willReturn(1); + $this->orderMock->expects($this->never()) + ->method('setShippingAddressId')->willReturnSelf(); + $this->attributeMock->expects($this->never()) + ->method('saveAttribute') + ->with($this->orderMock, ['shipping_address_id'])->willReturnSelf(); + $this->assertEquals($this->address, $this->address->process($this->orderMock)); + } + + /** + * Test processing of the billing address when billing address id was not changed. + * setBillingAddressId and saveAttribute methods must not be executed. + */ + public function testProcessBillingAddressNotChanged() + { + $this->orderMock->expects($this->exactly(2)) + ->method('getAddresses') + ->willReturn([$this->addressMock]); + $this->addressMock->expects($this->once()) + ->method('save')->willReturnSelf(); + $this->orderMock->expects($this->once()) + ->method('getBillingAddress') + ->willReturn($this->addressMock); + $this->orderMock->expects($this->once()) + ->method('getShippingAddress') + ->willReturn(null); + $this->addressMock->expects($this->once()) + ->method('getId')->willReturn(1); + $this->orderMock->expects($this->once()) + ->method('getBillingAddressId') + ->willReturn(1); + $this->orderMock->expects($this->never()) + ->method('setBillingAddressId')->willReturnSelf(); + $this->attributeMock->expects($this->never()) + ->method('saveAttribute') + ->with($this->orderMock, ['billing_address_id'])->willReturnSelf(); + $this->assertEquals($this->address, $this->address->process($this->orderMock)); + } + /** * Test method removeEmptyAddresses */ diff --git a/app/code/Magento/Search/ViewModel/ConfigProvider.php b/app/code/Magento/Search/ViewModel/ConfigProvider.php new file mode 100644 index 0000000000000..be3366e62e965 --- /dev/null +++ b/app/code/Magento/Search/ViewModel/ConfigProvider.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Search\ViewModel; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\View\Element\Block\ArgumentInterface; +use Magento\Store\Model\ScopeInterface; + +/** + * View model for search + */ +class ConfigProvider implements ArgumentInterface +{ + /** + * Suggestions settings config paths + */ + private const SEARCH_SUGGESTION_ENABLED = 'catalog/search/search_suggestion_enabled'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct( + ScopeConfigInterface $scopeConfig + ) { + $this->scopeConfig = $scopeConfig; + } + + /** + * Is Search Suggestions Allowed + * + * @return bool + */ + public function isSuggestionsAllowed(): bool + { + return $this->scopeConfig->isSetFlag( + self::SEARCH_SUGGESTION_ENABLED, + ScopeInterface::SCOPE_STORE + ); + } +} diff --git a/app/code/Magento/Search/view/frontend/layout/default.xml b/app/code/Magento/Search/view/frontend/layout/default.xml index 0cb18adedd952..69c99f979d51b 100644 --- a/app/code/Magento/Search/view/frontend/layout/default.xml +++ b/app/code/Magento/Search/view/frontend/layout/default.xml @@ -8,7 +8,11 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="header-wrapper"> - <block class="Magento\Framework\View\Element\Template" name="top.search" as="topSearch" template="Magento_Search::form.mini.phtml" /> + <block class="Magento\Framework\View\Element\Template" name="top.search" as="topSearch" template="Magento_Search::form.mini.phtml"> + <arguments> + <argument name="configProvider" xsi:type="object">Magento\Search\ViewModel\ConfigProvider</argument> + </arguments> + </block> </referenceContainer> <referenceBlock name="footer_links"> <block class="Magento\Framework\View\Element\Html\Link\Current" ifconfig="catalog/seo/search_terms" name="search-term-popular-link"> diff --git a/app/code/Magento/Search/view/frontend/templates/form.mini.phtml b/app/code/Magento/Search/view/frontend/templates/form.mini.phtml index 35f3876599731..80e720e2c2fe2 100644 --- a/app/code/Magento/Search/view/frontend/templates/form.mini.phtml +++ b/app/code/Magento/Search/view/frontend/templates/form.mini.phtml @@ -9,7 +9,9 @@ <?php /** @var $block \Magento\Framework\View\Element\Template */ /** @var $helper \Magento\Search\Helper\Data */ +/** @var $configProvider \Magento\Search\ViewModel\ConfigProvider */ $helper = $this->helper(\Magento\Search\Helper\Data::class); +$configProvider = $block->getData('configProvider'); ?> <div class="block block-search"> <div class="block block-title"><strong><?= $block->escapeHtml(__('Search')) ?></strong></div> @@ -22,12 +24,14 @@ $helper = $this->helper(\Magento\Search\Helper\Data::class); </label> <div class="control"> <input id="search" - data-mage-init='{"quickSearch":{ - "formSelector":"#search_mini_form", - "url":"<?= $block->escapeUrl($helper->getSuggestUrl())?>", - "destinationSelector":"#search_autocomplete", - "minSearchLength":"<?= $block->escapeHtml($helper->getMinQueryLength()) ?>"} - }' + <?php if ($configProvider->isSuggestionsAllowed()):?> + data-mage-init='{"quickSearch":{ + "formSelector":"#search_mini_form", + "url":"<?= $block->escapeUrl($helper->getSuggestUrl())?>", + "destinationSelector":"#search_autocomplete", + "minSearchLength":"<?= $block->escapeHtml($helper->getMinQueryLength()) ?>"} + }' + <?php endif;?> type="text" name="<?= $block->escapeHtmlAttr($helper->getQueryParamName()) ?>" value="<?= /* @noEscape */ $helper->getEscapedQueryText() ?>" diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCheckColorUploadChooserVisualSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCheckColorUploadChooserVisualSwatchTest.xml index a4fc0bdcfd1fb..9833ee79a297a 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCheckColorUploadChooserVisualSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCheckColorUploadChooserVisualSwatchTest.xml @@ -19,7 +19,7 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> - <amOnPage url="{{ProductAttributePage.url}}" stepKey="addNewProductAttribute"/> + <actionGroup ref="AdminNavigateToNewProductAttributePageActionGroup" stepKey="addNewProductAttribute"/> <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="{{visualSwatchAttribute.input_type}}" stepKey="fillInputType"/> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml index e67d0c763308c..a972456e22ac5 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml @@ -35,8 +35,7 @@ </after> <!-- Begin creating a new product attribute of type "Image Swatch" --> - <amOnPage url="{{ProductAttributePage.url}}" stepKey="goToNewProductAttributePage"/> - <waitForPageLoad stepKey="waitForNewProductAttributePage"/> + <actionGroup ref="AdminNavigateToNewProductAttributePageActionGroup" stepKey="goToNewProductAttributePage"/> <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="fillDefaultLabel"/> <!-- Select visual swatch --> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateTextSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateTextSwatchTest.xml index 6b2a29d8ec451..6e05e1a4e6c03 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateTextSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateTextSwatchTest.xml @@ -25,8 +25,7 @@ </after> <!-- Create a new product attribute of type "Text Swatch" --> - <amOnPage url="{{ProductAttributePage.url}}" stepKey="goToNewProductAttributePage"/> - <waitForPageLoad stepKey="waitForNewProductAttributePage"/> + <actionGroup ref="AdminNavigateToNewProductAttributePageActionGroup" stepKey="goToNewProductAttributePage"/> <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="fillDefaultLabel"/> <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="swatch_text" stepKey="selectInputType"/> <click selector="{{AdminManageSwatchSection.addSwatchText}}" stepKey="clickAddSwatch0"/> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchWithNonValidOptionsTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchWithNonValidOptionsTest.xml index e93a27d377a52..0d58ba8fc9917 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchWithNonValidOptionsTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchWithNonValidOptionsTest.xml @@ -27,8 +27,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{ProductAttributePage.url}}" stepKey="navigateToNewProductAttributePage"/> - <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="AdminNavigateToNewProductAttributePageActionGroup" stepKey="navigateToNewProductAttributePage"/> <!-- Set attribute properties --> <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml index 2ca26d84d45c7..f90190f9961de 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml @@ -34,8 +34,7 @@ </after> <!-- Begin creating a new product attribute --> - <amOnPage url="{{ProductAttributePage.url}}" stepKey="goToNewProductAttributePage"/> - <waitForPageLoad stepKey="waitForNewProductAttributePage"/> + <actionGroup ref="AdminNavigateToNewProductAttributePageActionGroup" stepKey="goToNewProductAttributePage"/> <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="fillDefaultLabel"/> <!-- Select visual swatch --> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByTextSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByTextSwatchTest.xml index 82dbff950d62f..7218d257ccf88 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByTextSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByTextSwatchTest.xml @@ -32,8 +32,7 @@ </after> <!-- Begin creating a new product attribute --> - <amOnPage url="{{ProductAttributePage.url}}" stepKey="goToNewProductAttributePage"/> - <waitForPageLoad stepKey="waitForNewProductAttributePage"/> + <actionGroup ref="AdminNavigateToNewProductAttributePageActionGroup" stepKey="goToNewProductAttributePage"/> <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="fillDefaultLabel"/> <!-- Select text swatch --> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByVisualSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByVisualSwatchTest.xml index bf820863cf9b6..84cdc6590b11f 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByVisualSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByVisualSwatchTest.xml @@ -34,8 +34,7 @@ </after> <!-- Begin creating a new product attribute --> - <amOnPage url="{{ProductAttributePage.url}}" stepKey="goToNewProductAttributePage"/> - <waitForPageLoad stepKey="waitForNewProductAttributePage"/> + <actionGroup ref="AdminNavigateToNewProductAttributePageActionGroup" stepKey="goToNewProductAttributePage"/> <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="fillDefaultLabel"/> <!-- Select visual swatch --> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSeeProductImagesMatchingProductSwatchesTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSeeProductImagesMatchingProductSwatchesTest.xml index 43944ceef33ef..27cbb01eafff0 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSeeProductImagesMatchingProductSwatchesTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSeeProductImagesMatchingProductSwatchesTest.xml @@ -38,7 +38,7 @@ </after> <!-- Begin creating a new product attribute --> - <amOnPage url="{{ProductAttributePage.url}}" stepKey="goToNewProductAttributePage"/> + <actionGroup ref="AdminNavigateToNewProductAttributePageActionGroup" stepKey="goToNewProductAttributePage"/> <actionGroup ref="AdminFillProductAttributePropertiesActionGroup" stepKey="fillProductAttributeProperties"> <argument name="attributeName" value="{{VisualSwatchProductAttribute.attribute_code}}"/> diff --git a/app/code/Magento/Ui/view/base/web/js/core/renderer/layout.js b/app/code/Magento/Ui/view/base/web/js/core/renderer/layout.js index ac1de4631e908..5240fe55f6a74 100644 --- a/app/code/Magento/Ui/view/base/web/js/core/renderer/layout.js +++ b/app/code/Magento/Ui/view/base/web/js/core/renderer/layout.js @@ -499,7 +499,7 @@ define([ component = registry.get(val.path); if (component) { - component.cleanData().destroy(); + component.destroy(); } }); diff --git a/app/etc/di.xml b/app/etc/di.xml index ba635d0662755..31cc5caf3ba67 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -209,6 +209,8 @@ <preference for="Magento\Framework\MessageQueue\QueueFactoryInterface" type="Magento\Framework\MessageQueue\QueueFactory" /> <preference for="Magento\Framework\Search\Request\IndexScopeResolverInterface" type="Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver"/> <preference for="Magento\Framework\HTTP\ClientInterface" type="Magento\Framework\HTTP\Client\Curl" /> + <preference for="Magento\Framework\Interception\ConfigLoaderInterface" type="Magento\Framework\Interception\PluginListGenerator" /> + <preference for="Magento\Framework\Interception\ConfigWriterInterface" type="Magento\Framework\Interception\PluginListGenerator" /> <type name="Magento\Framework\Model\ResourceModel\Db\TransactionManager" shared="false" /> <type name="Magento\Framework\Acl\Data\Cache"> <arguments> @@ -431,6 +433,16 @@ </argument> </arguments> </type> + <type name="Magento\Framework\Interception\PluginListGenerator"> + <arguments> + <argument name="reader" xsi:type="object">Magento\Framework\ObjectManager\Config\Reader\Dom\Proxy</argument> + <argument name="logger" xsi:type="object">\Psr\Log\LoggerInterface\Proxy</argument> + <argument name="scopePriorityScheme" xsi:type="array"> + <item name="primary" xsi:type="string">primary</item> + <item name="first" xsi:type="string">global</item> + </argument> + </arguments> + </type> <type name="Magento\Framework\App\ResourceConnection"> <arguments> <argument name="connectionFactory" xsi:type="object">Magento\Framework\App\ResourceConnection\ConnectionFactory</argument> diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderAddressUpdateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderAddressUpdateTest.php index 1096c0dca6530..c5b06285f1fe1 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderAddressUpdateTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderAddressUpdateTest.php @@ -3,13 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Sales\Service\V1; use Magento\Sales\Api\Data\OrderAddressInterface as OrderAddress; use Magento\TestFramework\TestCase\WebapiAbstract; /** - * Class OrderAddressUpdateTest + * Test for address update */ class OrderAddressUpdateTest extends WebapiAbstract { @@ -28,7 +29,7 @@ public function testOrderAddressUpdate() $order = $objectManager->get(\Magento\Sales\Model\Order::class)->loadByIncrementId('100000001'); $address = [ - OrderAddress::REGION => 'CA', + OrderAddress::REGION => 'California', OrderAddress::POSTCODE => '11111', OrderAddress::LASTNAME => 'lastname', OrderAddress::STREET => ['street'], @@ -75,7 +76,7 @@ public function testOrderAddressUpdate() $billingAddress = $actualOrder->getBillingAddress(); $validate = [ - OrderAddress::REGION => 'CA', + OrderAddress::REGION => 'California', OrderAddress::POSTCODE => '11111', OrderAddress::LASTNAME => 'lastname', OrderAddress::STREET => 'street', diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php index 021698f874e55..e28cca72e8fb8 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php @@ -76,7 +76,7 @@ public function testOrderGet(): void 'city' => 'Los Angeles', 'email' => 'customer@null.com', 'postcode' => '11111', - 'region' => 'CA' + 'region' => 'California' ]; $result = $this->makeServiceCall(self::ORDER_INCREMENT_ID); diff --git a/dev/tests/integration/framework/Magento/TestFramework/Interception/PluginList.php b/dev/tests/integration/framework/Magento/TestFramework/Interception/PluginList.php index 88c9086f8270b..0b5fc407d438b 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Interception/PluginList.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Interception/PluginList.php @@ -5,6 +5,8 @@ */ namespace Magento\TestFramework\Interception; +use Magento\Framework\Interception\ConfigLoaderInterface; +use Magento\Framework\Interception\PluginListGenerator; use Magento\Framework\Serialize\SerializerInterface; /** @@ -31,6 +33,8 @@ class PluginList extends \Magento\Framework\Interception\PluginList\PluginList * @param array $scopePriorityScheme * @param string|null $cacheId * @param SerializerInterface|null $serializer + * @param ConfigLoaderInterface|null $configLoader + * @param PluginListGenerator|null $pluginListGenerator * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -44,7 +48,9 @@ public function __construct( \Magento\Framework\ObjectManager\DefinitionInterface $classDefinitions, array $scopePriorityScheme, $cacheId = 'plugins', - SerializerInterface $serializer = null + SerializerInterface $serializer = null, + ConfigLoaderInterface $configLoader = null, + PluginListGenerator $pluginListGenerator = null ) { parent::__construct( $reader, @@ -57,7 +63,9 @@ public function __construct( $classDefinitions, $scopePriorityScheme, $cacheId, - $serializer + $serializer, + $configLoader, + $pluginListGenerator ); $this->_originScopeScheme = $this->_scopePriorityScheme; } diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Block/ResultTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Block/ResultTest.php index 76a4ff9714ebd..6d679a5aea7d4 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Block/ResultTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Block/ResultTest.php @@ -12,6 +12,7 @@ use Magento\Framework\View\LayoutInterface; use Magento\Search\Model\QueryFactory; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Search\ViewModel\ConfigProvider; class ResultTest extends \PHPUnit\Framework\TestCase { @@ -25,6 +26,11 @@ class ResultTest extends \PHPUnit\Framework\TestCase */ private $layout; + /** + * @var ConfigProvider + */ + private $configProvider; + /** * @inheritdoc */ @@ -32,9 +38,15 @@ protected function setUp(): void { $this->objectManager = Bootstrap::getObjectManager(); $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->configProvider = $this->objectManager->get(ConfigProvider::class); } - public function testSetListOrders() + /** + * Set list orders test + * + * @return void + */ + public function testSetListOrders(): void { $this->layout->addBlock(Text::class, 'head'); // The tested block is using head block @@ -62,6 +74,7 @@ public function testEscapeSearchText(string $searchValue, string $expectedOutput $searchResultBlock = $this->layout->createBlock(Result::class); /** @var Template $searchBlock */ $searchBlock = $this->layout->createBlock(Template::class); + $searchBlock->setData(['configProvider' => $this->configProvider]); $searchBlock->setTemplate('Magento_Search::form.mini.phtml'); /** @var RequestInterface $request */ $request = $this->objectManager->get(RequestInterface::class); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php index 32eddb28151a7..ffa84ca740e62 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php @@ -6,6 +6,7 @@ namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Indexer\Product\Price\Processor as PriceIndexerProcessor; use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\Catalog\Model\ResourceModel\Product\Collection; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; @@ -18,7 +19,7 @@ use Magento\Catalog\Api\Data\ProductInterface; /** - * Configurable test + * Test reindex of configurable products * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @magentoAppArea adminhtml @@ -64,7 +65,7 @@ protected function setUp(): void */ public function testGetProductFinalPriceIfOneOfChildIsDisabled(): void { - $configurableProduct = $this->getConfigurableProductFromCollection(); + $configurableProduct = $this->getConfigurableProductFromCollection(1); $this->assertEquals(10, $configurableProduct->getMinimalPrice()); $childProduct = $this->productRepository->getById(10, false, null, true); @@ -75,7 +76,7 @@ public function testGetProductFinalPriceIfOneOfChildIsDisabled(): void $this->productRepository->save($childProduct); $this->storeManager->setCurrentStore($currentStoreId); - $configurableProduct = $this->getConfigurableProductFromCollection(); + $configurableProduct = $this->getConfigurableProductFromCollection(1); $this->assertEquals(20, $configurableProduct->getMinimalPrice()); } @@ -93,7 +94,7 @@ public function testGetProductFinalPriceIfOneOfChildIsDisabled(): void */ public function testGetProductFinalPriceIfOneOfChildIsDisabledPerStore(): void { - $configurableProduct = $this->getConfigurableProductFromCollection(); + $configurableProduct = $this->getConfigurableProductFromCollection(1); $this->assertEquals(10, $configurableProduct->getMinimalPrice()); $childProduct = $this->productRepository->get('simple_10', false, null, true); @@ -106,7 +107,7 @@ public function testGetProductFinalPriceIfOneOfChildIsDisabledPerStore(): void $this->productRepository->save($childProduct); $this->storeManager->setCurrentStore($currentStoreId); - $configurableProduct = $this->getConfigurableProductFromCollection(); + $configurableProduct = $this->getConfigurableProductFromCollection(1); $this->assertEquals(20, $configurableProduct->getMinimalPrice()); } @@ -122,7 +123,7 @@ public function testGetProductFinalPriceIfOneOfChildIsDisabledPerStore(): void */ public function testGetProductMinimalPriceIfOneOfChildIsOutOfStock(): void { - $configurableProduct = $this->getConfigurableProductFromCollection(); + $configurableProduct = $this->getConfigurableProductFromCollection(1); $this->assertEquals(10, $configurableProduct->getMinimalPrice()); $childProduct = $this->productRepository->getById(10, false, null, true); @@ -130,25 +131,48 @@ public function testGetProductMinimalPriceIfOneOfChildIsOutOfStock(): void $stockItem->setIsInStock(Stock::STOCK_OUT_OF_STOCK); $this->stockRepository->save($stockItem); - $configurableProduct = $this->getConfigurableProductFromCollection(); + $configurableProduct = $this->getConfigurableProductFromCollection(1); $this->assertEquals(20, $configurableProduct->getMinimalPrice()); } + /** + * @magentoDataFixture Magento/Catalog/_files/enable_price_index_schedule.php + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable_with_assigned_simples.php + * @magentoDbIsolation disabled + * + * @return void + */ + public function testReindexWithCorrectPriority() + { + $configurableProduct = $this->productRepository->get('configurable'); + $childProduct1 = $this->productRepository->get('simple_1'); + $childProduct2 = $this->productRepository->get('simple_2'); + $priceIndexerProcessor = Bootstrap::getObjectManager()->get(PriceIndexerProcessor::class); + $priceIndexerProcessor->reindexList( + [$configurableProduct->getId(), $childProduct1->getId(), $childProduct2->getId()], + true + ); + + $configurableProduct = $this->getConfigurableProductFromCollection($configurableProduct->getId()); + $this->assertEquals($childProduct1->getPrice(), $configurableProduct->getMinimalPrice()); + } + /** * Retrieve configurable product. * Returns Configurable product that was created by Magento/ConfigurableProduct/_files/product_configurable.php * fixture * + * @param int $productId * @return ProductInterface */ - private function getConfigurableProductFromCollection(): ProductInterface + private function getConfigurableProductFromCollection(int $productId): ProductInterface { /** @var Collection $collection */ $collection = Bootstrap::getObjectManager()->get(CollectionFactory::class) ->create(); /** @var ProductInterface $configurableProduct */ $configurableProduct = $collection - ->addIdFilter([1]) + ->addIdFilter([$productId]) ->addMinimalPrice() ->load() ->getFirstItem(); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_assigned_simples.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_assigned_simples.php new file mode 100644 index 0000000000000..81a067195e902 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_assigned_simples.php @@ -0,0 +1,110 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Indexer\Product\Price\Processor as PriceIndexerProcessor; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Setup\CategorySetup; +use Magento\CatalogInventory\Model\Stock\Item; +use Magento\ConfigurableProduct\Helper\Product\Options\Factory; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Eav\Api\Data\AttributeOptionInterface; +use Magento\Eav\Model\Config; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +Bootstrap::getInstance()->reinitialize(); + +Resolver::getInstance()->requireDataFixture('Magento/ConfigurableProduct/_files/configurable_attribute.php'); + +$installer = Bootstrap::getObjectManager()->create(CategorySetup::class); +$attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + +$eavConfig = Bootstrap::getObjectManager()->get(Config::class); +$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); + +$product = Bootstrap::getObjectManager()->create(Product::class); +$product->setTypeId(Configurable::TYPE_CODE) + ->setAttributeSetId($attributeSetId) + ->setWebsiteIds([1]) + ->setName('Configurable Product') + ->setSku('configurable') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]); +$productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); +$product = $productRepository->save($product); + +$attributeValues = []; +$associatedProductIds = []; +/** @var AttributeOptionInterface[] $options */ +$options = $attribute->getOptions(); +array_shift($options); //remove the first option which is empty +$productNumber = 0; +foreach ($options as $option) { + $productNumber++; + + $childProduct = Bootstrap::getObjectManager()->create(Product::class); + $childProduct->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId($attributeSetId) + ->setWebsiteIds([1]) + ->setName('Configurable Option' . $option->getLabel()) + ->setSku('simple_' . $productNumber) + ->setPrice($productNumber * 10) + ->setTestConfigurable($option->getValue()) + ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData( + ['use_config_manage_stock' => 1,'qty' => $productNumber * 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1] + ); + $childProduct = $productRepository->save($childProduct); + + $stockItem = Bootstrap::getObjectManager()->create(Item::class); + $stockItem->load($childProduct->getId(), 'product_id'); + if (!$stockItem->getProductId()) { + $stockItem->setProductId($childProduct->getId()); + } + $stockItem->setUseConfigManageStock(1); + $stockItem->setQty($productNumber * 100); + $stockItem->setIsQtyDecimal(0); + $stockItem->setIsInStock(1); + $stockItem->save(); + + $attributeValues[] = [ + 'label' => 'test', + 'attribute_id' => $attribute->getId(), + 'value_index' => $option->getValue(), + ]; + $associatedProductIds[] = $childProduct->getId(); +} + +$indexerProcessor = Bootstrap::getObjectManager()->get(PriceIndexerProcessor::class); +$indexerProcessor->reindexList($associatedProductIds); + +$optionsFactory = Bootstrap::getObjectManager()->create(Factory::class); +$configurableOptions = $optionsFactory->create( + [ + [ + 'attribute_id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'position' => '0', + 'values' => $attributeValues, + ], + ] +); +$extensionConfigurableAttributes = $product->getExtensionAttributes(); +$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); +$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds); +$product->setExtensionAttributes($extensionConfigurableAttributes); +$product = $productRepository->save($product); + +$indexerProcessor = Bootstrap::getObjectManager()->get(PriceIndexerProcessor::class); +$indexerProcessor->reindexRow($product->getId()); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_assigned_simples_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_assigned_simples_rollback.php new file mode 100644 index 0000000000000..68621f78745e8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_assigned_simples_rollback.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\CatalogInventory\Model\Stock\Status; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Workaround\Override\Fixture\Resolver; + +$objectManager = Bootstrap::getObjectManager(); + +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +foreach (['simple_1', 'simple_2', 'configurable'] as $sku) { + try { + $product = $productRepository->get($sku, true); + + $stockStatus = $objectManager->create(Status::class); + $stockStatus->load($product->getEntityId(), 'product_id'); + $stockStatus->delete(); + + if ($product->getId()) { + $productRepository->delete($product); + } + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + //Product already removed + } +} + +Resolver::getInstance()->requireDataFixture('Magento/ConfigurableProduct/_files/configurable_attribute_rollback.php'); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Interception/AbstractPlugin.php b/dev/tests/integration/testsuite/Magento/Framework/Interception/AbstractPlugin.php index 1f65bca8f5f1d..b9deeb3bb968f 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Interception/AbstractPlugin.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Interception/AbstractPlugin.php @@ -5,6 +5,8 @@ */ namespace Magento\Framework\Interception; +use Magento\Framework\App\Filesystem\DirectoryList; + /** * Class GeneralTest * @@ -81,6 +83,10 @@ public function setUpInterceptionConfig($pluginConfig) $cacheManager->method('load')->willReturn(null); $definitions = new \Magento\Framework\ObjectManager\Definition\Runtime(); $relations = new \Magento\Framework\ObjectManager\Relations\Runtime(); + $configLoader = $this->createMock(ConfigLoaderInterface::class); + $logger = $this->createMock(\Psr\Log\LoggerInterface::class); + $directoryList = $this->createMock(DirectoryList::class); + $configWriter = $this->createMock(PluginListGenerator::class); $interceptionConfig = new Config\Config( $this->_configReader, $configScope, @@ -104,6 +110,10 @@ public function setUpInterceptionConfig($pluginConfig) \Magento\Framework\ObjectManager\DefinitionInterface::class => $definitions, \Magento\Framework\Interception\DefinitionInterface::class => $interceptionDefinitions, \Magento\Framework\Serialize\SerializerInterface::class => $json, + \Magento\Framework\Interception\ConfigLoaderInterface::class => $configLoader, + \Psr\Log\LoggerInterface::class => $logger, + \Magento\Framework\App\Filesystem\DirectoryList::class => $directoryList, + \Magento\Framework\App\ObjectManager\ConfigWriterInterface::class => $configWriter ]; $this->_objectManager = new \Magento\Framework\ObjectManager\ObjectManager( $factory, @@ -118,8 +128,8 @@ public function setUpInterceptionConfig($pluginConfig) 'preferences' => [ \Magento\Framework\Interception\PluginListInterface::class => \Magento\Framework\Interception\PluginList\PluginList::class, - \Magento\Framework\Interception\ChainInterface::class => - \Magento\Framework\Interception\Chain\Chain::class, + \Magento\Framework\Interception\ConfigWriterInterface::class => + \Magento\Framework\Interception\PluginListGenerator::class ], ] ); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Interception/PluginListGeneratorTest.php b/dev/tests/integration/testsuite/Magento/Framework/Interception/PluginListGeneratorTest.php new file mode 100644 index 0000000000000..8f1771759cee0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/Interception/PluginListGeneratorTest.php @@ -0,0 +1,141 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Interception; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem\DriverInterface; +use Magento\TestFramework\Application; +use Magento\TestFramework\Helper\Bootstrap; + +class PluginListGeneratorTest extends \PHPUnit\Framework\TestCase +{ + /** + * Generated plugin list config for frontend scope + */ + const CACHE_ID = 'primary|global|frontend|plugin-list'; + + /** + * @var PluginListGenerator + */ + private $model; + + /** + * @var DirectoryList + */ + private $directoryList; + + /** + * @var DriverInterface + */ + private $file; + + /** + * @var Application + */ + private $application; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $this->application = Bootstrap::getInstance()->getBootstrap()->getApplication(); + $this->directoryList = new DirectoryList(BP, $this->getCustomDirs()); + $this->file = Bootstrap::getObjectManager()->create(DriverInterface::class); + $reader = Bootstrap::getObjectManager()->create( + // phpstan:ignore "Class Magento\Framework\ObjectManager\Config\Reader\Dom\Proxy not found." + \Magento\Framework\ObjectManager\Config\Reader\Dom\Proxy::class + ); + $scopeConfig = Bootstrap::getObjectManager()->create(\Magento\Framework\Config\Scope::class); + $omConfig = Bootstrap::getObjectManager()->create( + \Magento\Framework\Interception\ObjectManager\Config\Developer::class + ); + $relations = Bootstrap::getObjectManager()->create( + \Magento\Framework\ObjectManager\Relations\Runtime::class + ); + $definitions = Bootstrap::getObjectManager()->create( + \Magento\Framework\Interception\Definition\Runtime::class + ); + $classDefinitions = Bootstrap::getObjectManager()->create( + \Magento\Framework\ObjectManager\Definition\Runtime::class + ); + // phpstan:ignore "Class Psr\Log\LoggerInterface\Proxy not found." + $logger = Bootstrap::getObjectManager()->create(\Psr\Log\LoggerInterface\Proxy::class); + $this->model = new PluginListGenerator( + $reader, + $scopeConfig, + $omConfig, + $relations, + $definitions, + $classDefinitions, + $logger, + $this->directoryList, + ['primary', 'global'] + ); + } + + /** + * Test plugin list configuration generation and load. + */ + public function testPluginListConfigGeneration() + { + $scopes = ['frontend']; + $this->model->write($scopes); + $configData = $this->model->load(self::CACHE_ID); + $this->assertNotEmpty($configData[0]); + $this->assertNotEmpty($configData[1]); + $this->assertNotEmpty($configData[2]); + $expected = [ + 1 => [ + 0 => 'genericHeaderPlugin', + 1 => 'asyncCssLoad', + 2 => 'response-http-page-cache' + ] + ]; + // Here in test is assumed that this class below has 3 plugins. But the amount of plugins and class itself + // may vary. If it is changed, please update these assertions. + $this->assertArrayHasKey( + 'Magento\\Framework\\App\\Response\\Http_sendResponse___self', + $configData[2], + 'Processed plugin does not exist in the processed plugins array.' + ); + $this->assertSame( + $expected, + $configData[2]['Magento\\Framework\\App\\Response\\Http_sendResponse___self'], + 'Plugin configurations are not equal' + ); + } + + /** + * Gets customized directory paths + * + * @return array + */ + private function getCustomDirs() + { + $path = DirectoryList::PATH; + $generated = "{$this->application->getTempDir()}/generated"; + + return [ + DirectoryList::GENERATED_METADATA => [$path => "{$generated}/metadata"], + ]; + } + + /** + * @inheritDoc + */ + protected function tearDown(): void + { + $filePath = $this->directoryList->getPath(DirectoryList::GENERATED_METADATA) + . '/' . self::CACHE_ID . '.' . 'php'; + + if (file_exists($filePath)) { + $this->file->deleteFile($filePath); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/Factory/CompiledTest.php b/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/Factory/CompiledTest.php index c620251ca9b67..7d3b9d2089cf9 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/Factory/CompiledTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/Factory/CompiledTest.php @@ -14,6 +14,9 @@ use Magento\Framework\ObjectManager\TestAsset\InterfaceImplementation; use Magento\Framework\ObjectManager\TestAsset\TestAssetInterface; +/** + * @magentoAppIsolation enabled + */ class CompiledTest extends AbstractFactoryRuntimeDefinitionsTestCases { /** diff --git a/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/Factory/Dynamic/DeveloperTest.php b/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/Factory/Dynamic/DeveloperTest.php index 7fa7e677e0d8d..c74c00de4ce53 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/Factory/Dynamic/DeveloperTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/Factory/Dynamic/DeveloperTest.php @@ -15,6 +15,9 @@ use Magento\Framework\ObjectManager\TestAsset\InterfaceImplementation; use Magento\Framework\ObjectManager\TestAsset\TestAssetInterface; +/** + * @magentoAppIsolation enabled + */ class DeveloperTest extends AbstractFactoryRuntimeDefinitionsTestCases { /** diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DeleteTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DeleteTest.php index 2a460a8ce622a..3d4f35a9e08ac 100644 --- a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DeleteTest.php +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Export/File/DeleteTest.php @@ -16,6 +16,8 @@ /** * Test for \Magento\ImportExport\Controller\Adminhtml\Export\File\Delete class. + * + * @magentoAppArea adminhtml */ class DeleteTest extends AbstractBackendController { diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Address/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Address/CollectionTest.php new file mode 100644 index 0000000000000..52284b3c9ddf9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Address/CollectionTest.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Model\ResourceModel\Order\Address; + +use Magento\Store\Model\StoreManagerInterface; +use Magento\Sales\Model\Order\Payment; +use Magento\Sales\Model\Order; +use Magento\Sales\Api\Data\OrderAddressInterface as OrderAddress; +use Magento\Backend\Model\Locale\Resolver; +use Magento\Framework\Locale\ResolverInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Test for address collection + * + * @magentoAppArea adminhtml + */ +class CollectionTest extends TestCase +{ + /** + * @var ResolverInterface|MockObject + */ + private $localeResolverMock; + + /** + * @var CollectionFactory + */ + private $addressCollectionFactory; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->localeResolverMock = $this->createMock(ResolverInterface::class); + Bootstrap::getObjectManager()->removeSharedInstance(ResolverInterface::class); + Bootstrap::getObjectManager()->removeSharedInstance(Resolver::class); + Bootstrap::getObjectManager()->addSharedInstance($this->localeResolverMock, ResolverInterface::class); + Bootstrap::getObjectManager()->addSharedInstance($this->localeResolverMock, Resolver::class); + + $addressData = [ + OrderAddress::REGION => 'Alabama', + OrderAddress::REGION_ID => '1', + OrderAddress::POSTCODE => '11111', + OrderAddress::LASTNAME => 'lastname', + OrderAddress::FIRSTNAME => 'firstname', + OrderAddress::STREET => 'street', + OrderAddress::CITY => 'Montgomery', + OrderAddress::EMAIL => 'admin@example.com', + OrderAddress::TELEPHONE => '11111111', + OrderAddress::COUNTRY_ID => 'US' + ]; + $billingAddress = Bootstrap::getObjectManager()->create(OrderAddress::class, ['data' => $addressData]); + $billingAddress->setAddressType('billing'); + $shippingAddress = clone $billingAddress; + $shippingAddress->setId(null)->setAddressType('shipping'); + $payment = Bootstrap::getObjectManager()->create(Payment::class); + $payment->setMethod('payflowpro') + ->setCcExpMonth('5') + ->setCcLast4('0005') + ->setCcType('AE') + ->setCcExpYear('2022'); + $order = Bootstrap::getObjectManager()->create(Order::class); + $order->setIncrementId('100000001') + ->setSubtotal(100) + ->setBaseSubtotal(100) + ->setCustomerEmail('admin@example.com') + ->setCustomerIsGuest(true) + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->setStoreId(Bootstrap::getObjectManager()->get(StoreManagerInterface::class)->getStore()->getId()) + ->setPayment($payment); + $order->save(); + + $this->addressCollectionFactory = Bootstrap::getObjectManager()->get(CollectionFactory::class); + } + + /** + * @magentoDataFixture Magento/Directory/_files/region_name_jp.php + */ + public function testCollectionWithJpLocale(): void + { + $locale = 'JA_jp'; + $this->localeResolverMock->method('getLocale')->willReturn($locale); + + $order = Bootstrap::getObjectManager()->create(Order::class) + ->loadByIncrementId('100000001'); + + $collection = $this->addressCollectionFactory->create()->setOrderFilter($order); + foreach ($collection as $address) { + $this->assertEquals('アラバマ', $address->getData(OrderAddress::REGION)); + } + } +} diff --git a/lib/internal/Magento/Framework/Interception/ConfigLoaderInterface.php b/lib/internal/Magento/Framework/Interception/ConfigLoaderInterface.php new file mode 100644 index 0000000000000..2a739f4cf9486 --- /dev/null +++ b/lib/internal/Magento/Framework/Interception/ConfigLoaderInterface.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Interception; + +/** + * Interception configuration loader interface. + */ +interface ConfigLoaderInterface +{ + /** + * Load interception configuration data per scope. + * + * @param string $cacheId + * @return array + */ + public function load(string $cacheId): array; +} diff --git a/lib/internal/Magento/Framework/Interception/ConfigWriterInterface.php b/lib/internal/Magento/Framework/Interception/ConfigWriterInterface.php new file mode 100644 index 0000000000000..9193937b65816 --- /dev/null +++ b/lib/internal/Magento/Framework/Interception/ConfigWriterInterface.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Interception; + +/** + * Interception config writer interface. + */ +interface ConfigWriterInterface +{ + /** + * Write interception configuration for scopes. + * + * @param array $scopes + * @return void + */ + public function write(array $scopes): void; +} diff --git a/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php b/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php index 610ae9473a725..26697e70a8f87 100644 --- a/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php +++ b/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php @@ -9,7 +9,9 @@ use Magento\Framework\Config\Data\Scoped; use Magento\Framework\Config\ReaderInterface; use Magento\Framework\Config\ScopeInterface; +use Magento\Framework\Interception\ConfigLoaderInterface; use Magento\Framework\Interception\DefinitionInterface; +use Magento\Framework\Interception\PluginListGenerator; use Magento\Framework\Interception\PluginListInterface as InterceptionPluginList; use Magento\Framework\Interception\ObjectManager\ConfigInterface; use Magento\Framework\ObjectManager\RelationsInterface; @@ -20,8 +22,6 @@ /** * Plugin config, provides list of plugins for a type - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class PluginList extends Scoped implements InterceptionPluginList { @@ -78,14 +78,19 @@ class PluginList extends Scoped implements InterceptionPluginList protected $_pluginInstances = []; /** - * @var \Psr\Log\LoggerInterface + * @var SerializerInterface */ - private $logger; + private $serializer; /** - * @var SerializerInterface + * @var ConfigLoaderInterface */ - private $serializer; + private $configLoader; + + /** + * @var PluginListGenerator + */ + private $pluginListGenerator; /** * Constructor @@ -101,6 +106,8 @@ class PluginList extends Scoped implements InterceptionPluginList * @param array $scopePriorityScheme * @param string|null $cacheId * @param SerializerInterface|null $serializer + * @param ConfigLoaderInterface|null $configLoader + * @param PluginListGenerator|null $pluginListGenerator * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -114,7 +121,9 @@ public function __construct( ClassDefinitions $classDefinitions, array $scopePriorityScheme = ['global'], $cacheId = 'plugins', - SerializerInterface $serializer = null + SerializerInterface $serializer = null, + ConfigLoaderInterface $configLoader = null, + PluginListGenerator $pluginListGenerator = null ) { $this->serializer = $serializer ?: $objectManager->get(Serialize::class); parent::__construct($reader, $configScope, $cache, $cacheId, $this->serializer); @@ -124,6 +133,8 @@ public function __construct( $this->_classDefinitions = $classDefinitions; $this->_scopePriorityScheme = $scopePriorityScheme; $this->_objectManager = $objectManager; + $this->configLoader = $configLoader ?: $this->_objectManager->get(ConfigLoaderInterface::class); + $this->pluginListGenerator = $pluginListGenerator ?: $this->_objectManager->get(PluginListGenerator::class); } /** @@ -131,88 +142,10 @@ public function __construct( * * @param string $type * @return array - * @throws \InvalidArgumentException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function _inheritPlugins($type) { - $type = ltrim($type, '\\'); - if (!isset($this->_inherited[$type])) { - $realType = $this->_omConfig->getOriginalInstanceType($type); - - if ($realType !== $type) { - $plugins = $this->_inheritPlugins($realType); - } elseif ($this->_relations->has($type)) { - $relations = $this->_relations->getParents($type); - $plugins = []; - foreach ($relations as $relation) { - if ($relation) { - $relationPlugins = $this->_inheritPlugins($relation); - if ($relationPlugins) { - $plugins = array_replace_recursive($plugins, $relationPlugins); - } - } - } - } else { - $plugins = []; - } - if (isset($this->_data[$type])) { - if (!$plugins) { - $plugins = $this->_data[$type]; - } else { - $plugins = array_replace_recursive($plugins, $this->_data[$type]); - } - } - $this->_inherited[$type] = null; - if (is_array($plugins) && count($plugins)) { - $this->filterPlugins($plugins); - uasort($plugins, [$this, '_sort']); - $this->trimInstanceStartingBackslash($plugins); - $this->_inherited[$type] = $plugins; - $lastPerMethod = []; - foreach ($plugins as $key => $plugin) { - // skip disabled plugins - if (isset($plugin['disabled']) && $plugin['disabled']) { - unset($plugins[$key]); - continue; - } - $pluginType = $this->_omConfig->getOriginalInstanceType($plugin['instance']); - if (!class_exists($pluginType)) { - throw new \InvalidArgumentException('Plugin class ' . $pluginType . ' doesn\'t exist'); - } - foreach ($this->_definitions->getMethodList($pluginType) as $pluginMethod => $methodTypes) { - $current = isset($lastPerMethod[$pluginMethod]) ? $lastPerMethod[$pluginMethod] : '__self'; - $currentKey = $type . '_' . $pluginMethod . '_' . $current; - if ($methodTypes & DefinitionInterface::LISTENER_AROUND) { - $this->_processed[$currentKey][DefinitionInterface::LISTENER_AROUND] = $key; - $lastPerMethod[$pluginMethod] = $key; - } - if ($methodTypes & DefinitionInterface::LISTENER_BEFORE) { - $this->_processed[$currentKey][DefinitionInterface::LISTENER_BEFORE][] = $key; - } - if ($methodTypes & DefinitionInterface::LISTENER_AFTER) { - $this->_processed[$currentKey][DefinitionInterface::LISTENER_AFTER][] = $key; - } - } - } - } - return $plugins; - } - return $this->_inherited[$type]; - } - - /** - * Trims starting backslash from plugin instance name - * - * @param array $plugins - * @return void - */ - private function trimInstanceStartingBackslash(&$plugins) - { - foreach ($plugins as &$plugin) { - $plugin['instance'] = ltrim($plugin['instance'], '\\'); - } + return $this->pluginListGenerator->inheritPlugins($type, $this->_data, $this->_inherited, $this->_processed); } /** @@ -224,16 +157,7 @@ private function trimInstanceStartingBackslash(&$plugins) */ protected function _sort($itemA, $itemB) { - if (isset($itemA['sortOrder'])) { - if (isset($itemB['sortOrder'])) { - return $itemA['sortOrder'] - $itemB['sortOrder']; - } - return $itemA['sortOrder']; - } elseif (isset($itemB['sortOrder'])) { - return (0 - (int)$itemB['sortOrder']); - } else { - return 0; - } + return ($itemA['sortOrder'] ?? PHP_INT_MIN) - ($itemB['sortOrder'] ?? PHP_INT_MIN); } /** @@ -280,8 +204,8 @@ public function getNext($type, $method, $code = '__self') protected function _loadScopedData() { $scope = $this->_configScope->getCurrentScope(); - if (false == isset($this->_loadedScopes[$scope])) { - $index = array_search($scope, $this->_scopePriorityScheme); + if (false === isset($this->_loadedScopes[$scope])) { + $index = array_search($scope, $this->_scopePriorityScheme, true); /** * Force current scope to be at the end of the scheme to ensure that default priority scopes are loaded. * Mostly happens when the current scope is primary. @@ -294,57 +218,47 @@ protected function _loadScopedData() $this->_scopePriorityScheme[] = $scope; $cacheId = implode('|', $this->_scopePriorityScheme) . "|" . $this->_cacheId; - $data = $this->_cache->load($cacheId); - if ($data) { - list($this->_data, $this->_inherited, $this->_processed) = $this->serializer->unserialize($data); - foreach ($this->_scopePriorityScheme as $scopeCode) { - $this->_loadedScopes[$scopeCode] = true; - } - } else { - foreach ($this->_loadScopedVirtualTypes() as $class) { - $this->_inheritPlugins($class); - } - foreach ($this->getClassDefinitions() as $class) { - $this->_inheritPlugins($class); - } - $this->_cache->save( - $this->serializer->serialize([$this->_data, $this->_inherited, $this->_processed]), - $cacheId - ); - } - $this->_pluginInstances = []; - } - } + $configData = $this->configLoader->load($cacheId); - /** - * Load virtual types for current scope - * - * @return array - */ - private function _loadScopedVirtualTypes() - { - $virtualTypes = []; - foreach ($this->_scopePriorityScheme as $scopeCode) { - if (!isset($this->_loadedScopes[$scopeCode])) { - $data = $this->_reader->read($scopeCode) ?: []; - unset($data['preferences']); - if (count($data) > 0) { - $this->_inherited = []; - $this->_processed = []; - $this->merge($data); - foreach ($data as $class => $config) { - if (isset($config['type'])) { - $virtualTypes[] = $class; - } + if ($configData) { + [$this->_data, $this->_inherited, $this->_processed] = $configData; + $this->_loadedScopes[$scope] = true; + } else { + $data = $this->_cache->load($cacheId); + if ($data) { + [$this->_data, $this->_inherited, $this->_processed] = $this->serializer->unserialize($data); + foreach ($this->_scopePriorityScheme as $scopeCode) { + $this->_loadedScopes[$scopeCode] = true; + } + } else { + [ + $virtualTypes, + $this->_scopePriorityScheme, + $this->_loadedScopes, + $this->_data, + $this->_inherited, + $this->_processed + ] = $this->pluginListGenerator->loadScopedVirtualTypes( + $this->_scopePriorityScheme, + $this->_loadedScopes, + $this->_data, + $this->_inherited, + $this->_processed + ); + foreach ($virtualTypes as $class) { + $this->_inheritPlugins($class); } + foreach ($this->getClassDefinitions() as $class) { + $this->_inheritPlugins($class); + } + $this->_cache->save( + $this->serializer->serialize([$this->_data, $this->_inherited, $this->_processed]), + $cacheId + ); } - $this->_loadedScopes[$scopeCode] = true; - } - if ($this->isCurrentScope($scopeCode)) { - break; } + $this->_pluginInstances = []; } - return $virtualTypes; } /** @@ -355,7 +269,7 @@ private function _loadScopedVirtualTypes() */ protected function isCurrentScope($scopeCode) { - return $this->_configScope->getCurrentScope() == $scopeCode; + return $this->_configScope->getCurrentScope() === $scopeCode; } /** @@ -376,45 +290,6 @@ protected function getClassDefinitions() */ public function merge(array $config) { - foreach ($config as $type => $typeConfig) { - if (isset($typeConfig['plugins'])) { - $type = ltrim($type, '\\'); - if (isset($this->_data[$type])) { - $this->_data[$type] = array_replace_recursive($this->_data[$type], $typeConfig['plugins']); - } else { - $this->_data[$type] = $typeConfig['plugins']; - } - } - } - } - - /** - * Remove from list not existing plugins - * - * @param array $plugins - * @return void - */ - private function filterPlugins(array &$plugins) - { - foreach ($plugins as $name => $plugin) { - if (empty($plugin['instance'])) { - unset($plugins[$name]); - $this->getLogger()->info("Reference to undeclared plugin with name '{$name}'."); - } - } - } - - /** - * Get logger - * - * @return \Psr\Log\LoggerInterface - * @deprecated 100.2.0 - */ - private function getLogger() - { - if ($this->logger === null) { - $this->logger = $this->_objectManager->get(\Psr\Log\LoggerInterface::class); - } - return $this->logger; + $this->_data = $this->pluginListGenerator->merge($config, $this->_data); } } diff --git a/lib/internal/Magento/Framework/Interception/PluginListGenerator.php b/lib/internal/Magento/Framework/Interception/PluginListGenerator.php new file mode 100644 index 0000000000000..effc291bb883b --- /dev/null +++ b/lib/internal/Magento/Framework/Interception/PluginListGenerator.php @@ -0,0 +1,431 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Interception; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Config\ReaderInterface; +use Magento\Framework\Config\ScopeInterface; +use Magento\Framework\Interception\ObjectManager\ConfigInterface; +use Magento\Framework\ObjectManager\DefinitionInterface as ClassDefinitions; +use Magento\Framework\ObjectManager\RelationsInterface; +use Psr\Log\LoggerInterface; + +/** + * Plugin list configuration writer and loader for scopes. + */ +class PluginListGenerator implements ConfigWriterInterface, ConfigLoaderInterface +{ + /** + * @var ScopeInterface + */ + private $scopeConfig; + + /** + * Configuration reader + * + * @var ReaderInterface + */ + private $reader; + + /** + * Cache tag + * + * @var string + */ + private $cacheId = 'plugin-list'; + + /** + * Loaded scopes + * + * @var array + */ + private $loadedScopes = []; + + /** + * Type config + * + * @var ConfigInterface + */ + private $omConfig; + + /** + * Class relations information provider + * + * @var RelationsInterface + */ + private $relations; + + /** + * List of interception methods per plugin + * + * @var DefinitionInterface + */ + private $definitions; + + /** + * List of interceptable application classes + * + * @var ClassDefinitions + */ + private $classDefinitions; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var DirectoryList + */ + private $directoryList; + + /** + * @var array + */ + private $pluginData; + + /** + * @var array + */ + private $inherited = []; + + /** + * @var array + */ + private $processed; + + /** + * Scope priority loading scheme + * + * @var string[] + */ + private $scopePriorityScheme; + + /** + * @var array + */ + private $globalScopePluginData = []; + + /** + * @param ReaderInterface $reader + * @param ScopeInterface $scopeConfig + * @param ConfigInterface $omConfig + * @param RelationsInterface $relations + * @param DefinitionInterface $definitions + * @param ClassDefinitions $classDefinitions + * @param LoggerInterface $logger + * @param DirectoryList $directoryList + * @param array $scopePriorityScheme + */ + public function __construct( + ReaderInterface $reader, + ScopeInterface $scopeConfig, + ConfigInterface $omConfig, + RelationsInterface $relations, + DefinitionInterface $definitions, + ClassDefinitions $classDefinitions, + LoggerInterface $logger, + DirectoryList $directoryList, + array $scopePriorityScheme = ['global'] + ) { + $this->reader = $reader; + $this->scopeConfig = $scopeConfig; + $this->omConfig = $omConfig; + $this->relations = $relations; + $this->definitions = $definitions; + $this->classDefinitions = $classDefinitions; + $this->logger = $logger; + $this->directoryList = $directoryList; + $this->scopePriorityScheme = $scopePriorityScheme; + } + + /** + * @inheritdoc + */ + public function write(array $scopes): void + { + foreach ($scopes as $scope) { + $this->scopeConfig->setCurrentScope($scope); + if (false === isset($this->loadedScopes[$scope])) { + if (false === in_array($scope, $this->scopePriorityScheme, true)) { + $this->scopePriorityScheme[] = $scope; + } + $cacheId = implode('|', $this->scopePriorityScheme) . "|" . $this->cacheId; + [ + $virtualTypes, + $this->scopePriorityScheme, + $this->loadedScopes, + $this->pluginData, + $this->inherited, + $this->processed + ] = $this->loadScopedVirtualTypes( + $this->scopePriorityScheme, + $this->loadedScopes, + $this->pluginData, + $this->inherited, + $this->processed + ); + foreach ($virtualTypes as $class) { + $this->inheritPlugins($class, $this->pluginData, $this->inherited, $this->processed); + } + foreach (array_keys($this->pluginData) as $className) { + $this->inheritPlugins($className, $this->pluginData, $this->inherited, $this->processed); + } + foreach ($this->getClassDefinitions() as $class) { + $this->inheritPlugins($class, $this->pluginData, $this->inherited, $this->processed); + } + $this->writeConfig( + $cacheId, + [$this->pluginData, $this->inherited, $this->processed] + ); + // need global & primary scopes plugin data for other scopes + if ($scope === 'global') { + $this->globalScopePluginData = $this->pluginData; + } + if (count($this->scopePriorityScheme) > 2) { + array_pop($this->scopePriorityScheme); + // merge global & primary scopes plugin data to other scopes by default + $this->pluginData = $this->globalScopePluginData; + } + } + } + } + + /** + * @inheritdoc + */ + public function load(string $cacheId): array + { + $file = $this->directoryList->getPath(DirectoryList::GENERATED_METADATA) . '/' . $cacheId . '.' . 'php'; + if (file_exists($file)) { + return include $file; + } + + return []; + } + + /** + * Load virtual types for current scope + * + * @param array $scopePriorityScheme + * @param array $loadedScopes + * @param array|null $pluginData + * @param array $inherited + * @param array $processed + * @return array + */ + public function loadScopedVirtualTypes($scopePriorityScheme, $loadedScopes, $pluginData, $inherited, $processed) + { + $virtualTypes = []; + foreach ($scopePriorityScheme as $scopeCode) { + if (!isset($loadedScopes[$scopeCode])) { + $data = $this->reader->read($scopeCode) ?: []; + unset($data['preferences']); + if (count($data) > 0) { + $inherited = []; + $processed = []; + $pluginData = $this->merge($data, $pluginData); + foreach ($data as $class => $config) { + if (isset($config['type'])) { + $virtualTypes[] = $class; + } + } + } + $loadedScopes[$scopeCode] = true; + } + if ($this->isCurrentScope($scopeCode)) { + break; + } + } + return [$virtualTypes, $scopePriorityScheme, $loadedScopes, $pluginData, $inherited, $processed]; + } + + /** + * Returns class definitions + * + * @return array + */ + private function getClassDefinitions() + { + return $this->classDefinitions->getClasses(); + } + + /** + * Whether scope code is current scope code + * + * @param string $scopeCode + * @return bool + */ + private function isCurrentScope($scopeCode) + { + return $this->scopeConfig->getCurrentScope() === $scopeCode; + } + + /** + * Collect parent types configuration for requested type + * + * @param string $type + * @param array $pluginData + * @param array $inherited + * @param array $processed + * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + public function inheritPlugins($type, &$pluginData, &$inherited, &$processed) + { + $type = ltrim($type, '\\'); + if (!isset($inherited[$type])) { + $realType = $this->omConfig->getOriginalInstanceType($type); + + if ($realType !== $type) { + $plugins = $this->inheritPlugins($realType, $pluginData, $inherited, $processed); + } elseif ($this->relations->has($type)) { + $relations = $this->relations->getParents($type); + $plugins = []; + foreach ($relations as $relation) { + if ($relation) { + $relationPlugins = $this->inheritPlugins($relation, $pluginData, $inherited, $processed); + if ($relationPlugins) { + $plugins = array_replace_recursive($plugins, $relationPlugins); + } + } + } + } else { + $plugins = []; + } + if (isset($pluginData[$type])) { + if (!$plugins) { + $plugins = $pluginData[$type]; + } else { + $plugins = array_replace_recursive($plugins, $pluginData[$type]); + } + } + $inherited[$type] = null; + if (is_array($plugins) && count($plugins)) { + $this->filterPlugins($plugins); + uasort($plugins, function ($itemA, $itemB) { + return ($itemA['sortOrder'] ?? PHP_INT_MIN) - ($itemB['sortOrder'] ?? PHP_INT_MIN); + }); + $this->trimInstanceStartingBackslash($plugins); + $inherited[$type] = $plugins; + $lastPerMethod = []; + foreach ($plugins as $key => $plugin) { + // skip disabled plugins + if (isset($plugin['disabled']) && $plugin['disabled']) { + unset($plugins[$key]); + continue; + } + $pluginType = $this->omConfig->getOriginalInstanceType($plugin['instance']); + if (!class_exists($pluginType)) { + throw new \InvalidArgumentException('Plugin class ' . $pluginType . ' doesn\'t exist'); + } + foreach ($this->definitions->getMethodList($pluginType) as $pluginMethod => $methodTypes) { + $current = $lastPerMethod[$pluginMethod] ?? '__self'; + $currentKey = $type . '_' . $pluginMethod . '_' . $current; + if ($methodTypes & DefinitionInterface::LISTENER_AROUND) { + $processed[$currentKey][DefinitionInterface::LISTENER_AROUND] = $key; + $lastPerMethod[$pluginMethod] = $key; + } + if ($methodTypes & DefinitionInterface::LISTENER_BEFORE) { + $processed[$currentKey][DefinitionInterface::LISTENER_BEFORE][] = $key; + } + if ($methodTypes & DefinitionInterface::LISTENER_AFTER) { + $processed[$currentKey][DefinitionInterface::LISTENER_AFTER][] = $key; + } + } + } + } + return $plugins; + } + return $inherited[$type]; + } + + /** + * Trims starting backslash from plugin instance name + * + * @param array $plugins + * @return void + */ + public function trimInstanceStartingBackslash(&$plugins) + { + foreach ($plugins as &$plugin) { + $plugin['instance'] = ltrim($plugin['instance'], '\\'); + } + } + + /** + * Remove from list not existing plugins + * + * @param array $plugins + * @return void + */ + public function filterPlugins(array &$plugins) + { + foreach ($plugins as $name => $plugin) { + if (empty($plugin['instance'])) { + unset($plugins[$name]); + $this->logger->info("Reference to undeclared plugin with name '{$name}'."); + } + } + } + + /** + * Merge configuration + * + * @param array $config + * @param array|null $pluginData + * @return array + */ + public function merge(array $config, $pluginData) + { + foreach ($config as $type => $typeConfig) { + if (isset($typeConfig['plugins'])) { + $type = ltrim($type, '\\'); + if (isset($pluginData[$type])) { + $pluginData[$type] = array_replace_recursive($pluginData[$type], $typeConfig['plugins']); + } else { + $pluginData[$type] = $typeConfig['plugins']; + } + } + } + + return $pluginData; + } + + /** + * Writes config in storage + * + * @param string $key + * @param array $config + * @return void + * @throws \Magento\Framework\Exception\FileSystemException + */ + private function writeConfig(string $key, array $config) + { + $this->initialize(); + $configuration = sprintf('<?php return %s;', var_export($config, true)); + file_put_contents( + $this->directoryList->getPath(DirectoryList::GENERATED_METADATA) . '/' . $key . '.php', + $configuration + ); + } + + /** + * Initializes writer + * + * @return void + * @throws \Magento\Framework\Exception\FileSystemException + */ + private function initialize() + { + if (!file_exists($this->directoryList->getPath(DirectoryList::GENERATED_METADATA))) { + mkdir($this->directoryList->getPath(DirectoryList::GENERATED_METADATA)); + } + } +} diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php b/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php index 56740268026c2..b0cb500eeed66 100644 --- a/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php @@ -9,23 +9,23 @@ use Magento\Framework\Config\CacheInterface; use Magento\Framework\Config\ScopeInterface; +use Magento\Framework\Interception\ConfigLoaderInterface; use Magento\Framework\Interception\ObjectManager\ConfigInterface; use Magento\Framework\Interception\PluginList\PluginList; +use Magento\Framework\Interception\PluginListGenerator; use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item; -use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item\Enhanced; use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemContainer; +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemContainerPlugin\Simple as ItemContainerPlugin; use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemPlugin\Advanced; use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemPlugin\Simple; use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\StartingBackslash; -use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\StartingBackslash\Plugin; +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\StartingBackslash\Plugin as StartingBackslashPlugin; use Magento\Framework\ObjectManager\Config\Reader\Dom; use Magento\Framework\ObjectManager\Definition\Runtime; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Serialize\SerializerInterface; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Psr\Log\LoggerInterface; require_once __DIR__ . '/../Custom/Module/Model/Item.php'; require_once __DIR__ . '/../Custom/Module/Model/Item/Enhanced.php'; @@ -57,90 +57,146 @@ class PluginListTest extends TestCase */ private $cacheMock; - /** - * @var LoggerInterface|MockObject - */ - private $loggerMock; - /** * @var SerializerInterface|MockObject */ private $serializerMock; /** - * @var ObjectManagerInterface|MockObject + * @var ConfigLoaderInterface|MockObject */ - private $objectManagerMock; + private $configLoaderMock; protected function setUp(): void { - $readerMap = include __DIR__ . '/../_files/reader_mock_map.php'; + $loadScoped = include __DIR__ . '/../_files/load_scoped_mock_map.php'; $readerMock = $this->createMock(Dom::class); - $readerMock->expects($this->any())->method('read')->willReturnMap($readerMap); $this->configScopeMock = $this->getMockForAbstractClass(ScopeInterface::class); $this->cacheMock = $this->getMockBuilder(CacheInterface::class) ->setMethods(['get']) ->getMockForAbstractClass(); // turn cache off - $this->cacheMock->expects($this->any()) - ->method('get') - ->willReturn(false); + $this->cacheMock->method('get')->willReturn(false); $omConfigMock = $this->getMockForAbstractClass( ConfigInterface::class ); - $omConfigMock->expects($this->any())->method('getOriginalInstanceType')->willReturnArgument(0); + $omConfigMock->method('getOriginalInstanceType')->willReturnArgument(0); - $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + $objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) ->setMethods(['get']) ->getMockForAbstractClass(); - $this->objectManagerMock->expects($this->any()) - ->method('get') - ->willReturnArgument(0); + $objectManagerMock->method('get')->willReturnArgument(0); $this->serializerMock = $this->getMockForAbstractClass(SerializerInterface::class); - $definitions = new Runtime(); - - $objectManagerHelper = new ObjectManager($this); - $this->object = $objectManagerHelper->getObject( - PluginList::class, - [ - 'reader' => $readerMock, - 'configScope' => $this->configScopeMock, - 'cache' => $this->cacheMock, - 'relations' => new \Magento\Framework\ObjectManager\Relations\Runtime(), - 'omConfig' => $omConfigMock, - 'definitions' => new \Magento\Framework\Interception\Definition\Runtime(), - 'objectManager' => $this->objectManagerMock, - 'classDefinitions' => $definitions, - 'scopePriorityScheme' => ['global'], - 'cacheId' => 'interception', - 'serializer' => $this->serializerMock - ] - ); - - $this->loggerMock = $this->getMockForAbstractClass(LoggerInterface::class); - $objectManagerHelper->setBackwardCompatibleProperty( - $this->object, - 'logger', - $this->loggerMock - ); + $this->configLoaderMock = $this->getMockBuilder(ConfigLoaderInterface::class) + ->onlyMethods(['load']) + ->getMockForAbstractClass(); + $pluginListGeneratorMock = $this->getMockBuilder(PluginListGenerator::class) + ->disableOriginalConstructor() + ->onlyMethods(['loadScopedVirtualTypes', 'inheritPlugins']) + ->getMock(); + $pluginListGeneratorMock->method('loadScopedVirtualTypes') + ->willReturnMap($loadScoped); + + $definitions = $this->getMockBuilder(Runtime::class) + ->disableOriginalConstructor() + ->getMock(); + $definitions->method('getClasses')->willReturn([]); + + // tested class is a mock to be able to set its protected properties values in closure + $this->object = $this->getMockBuilder(PluginList::class) + ->disableProxyingToOriginalMethods() + ->onlyMethods(['_inheritPlugins']) + ->setConstructorArgs( + [ + 'reader' => $readerMock, + 'configScope' => $this->configScopeMock, + 'cache' => $this->cacheMock, + 'relations' => new \Magento\Framework\ObjectManager\Relations\Runtime(), + 'omConfig' => $omConfigMock, + 'definitions' => new \Magento\Framework\Interception\Definition\Runtime(), + 'objectManager' => $objectManagerMock, + 'classDefinitions' => $definitions, + 'scopePriorityScheme' => ['global'], + 'cacheId' => 'interception', + 'serializer' => $this->serializerMock, + 'configLoader' => $this->configLoaderMock, + 'pluginListGenerator' => $pluginListGeneratorMock + ] + ) + ->getMock(); } public function testGetPlugin() { - $this->configScopeMock->expects($this->any())->method('getCurrentScope')->willReturn('backend'); + $inheritPlugins = function ($type) { + $inheritedItem = [ + Item::class => [ + 'advanced_plugin' => [ + 'sortOrder' => 5, + 'instance' => Advanced::class, + ], + 'simple_plugin' => [ + 'sortOrder' => 10, + 'instance' => Simple::class + ] + ] + ]; + $processedItem = [ + 'Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item_getName___self' => [ + 2 => 'advanced_plugin', + 4 => ['advanced_plugin'] + ], + 'Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item_getName_advanced_plugin' => [ + 4 => ['simple_plugin'] + ] + ]; + $inheritedItemContainer = [ + ItemContainer::class => [ + 'simple_plugin' => [ + 'sortOrder' => 15, + 'instance' => ItemContainerPlugin::class + ] + ] + ]; + $processedItemContainer = [ + 'Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemContainer_getName___self' => [ + 4 => ['simple_plugin'] + ] + ]; + $inheritedStartingBackslash = [ + StartingBackslash::class => [ + 'simple_plugin' => [ + 'sortOrder' => 20, + 'instance' => StartingBackslashPlugin::class + ] + ] + ]; + + if ($type === 'Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item') { + $this->_inherited = $inheritedItem; /** @phpstan-ignore-line */ + $this->_processed = $processedItem; /** @phpstan-ignore-line */ + } + if ($type === 'Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemContainer') { + $this->_inherited = array_merge($inheritedItem, $inheritedItemContainer); /** @phpstan-ignore-line */ + $this->_processed = array_merge($processedItem, $processedItemContainer); /** @phpstan-ignore-line */ + } + if ($type === 'Magento\Framework\Interception\Test\Unit\Custom\Module\Model\StartingBackslash') { + /** @phpstan-ignore-next-line */ + $this->_inherited = array_merge($inheritedItem, $inheritedItemContainer, $inheritedStartingBackslash); + $this->_processed = array_merge($processedItem, $processedItemContainer); /** @phpstan-ignore-line */ + } + }; + $inheritPlugins = $inheritPlugins->bindTo($this->object, PluginList::class); + $this->object->method('_inheritPlugins')->willReturnCallback($inheritPlugins); + + $this->configScopeMock->method('getCurrentScope')->willReturn('backend'); $this->object->getNext(Item::class, 'getName'); - $this->object->getNext( - ItemContainer::class, - 'getName' - ); - $this->object->getNext( - StartingBackslash::class, - 'getName' - ); + $this->object->getNext(ItemContainer::class, 'getName'); + $this->object->getNext(StartingBackslash::class, 'getName'); $this->assertEquals( Simple::class, $this->object->getPlugin( @@ -156,14 +212,14 @@ public function testGetPlugin() ) ); $this->assertEquals( - \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemContainerPlugin\Simple::class, + ItemContainerPlugin::class, $this->object->getPlugin( ItemContainer::class, 'simple_plugin' ) ); $this->assertEquals( - Plugin::class, + StartingBackslashPlugin::class, $this->object->getPlugin( StartingBackslash::class, 'simple_plugin' @@ -189,13 +245,33 @@ public function testGetPlugins( array $scopePriorityScheme = ['global'] ): void { $this->setScopePriorityScheme($scopePriorityScheme); - $this->configScopeMock->expects( - $this->any() - )->method( - 'getCurrentScope' - )->willReturn( - $scopeCode - ); + $this->configScopeMock->method('getCurrentScope')->willReturn($scopeCode); + + $inheritPlugins = function ($type) { + $inheritedItem = [ + Item::class => [ + 'simple_plugin' => [ + 'sortOrder' => 10, + 'instance' => Simple::class + ] + ] + ]; + $processedItem = [ + 'Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item_getName___self' => [ + 4 => [ + 'simple_plugin' + ] + ], + ]; + + if ($type === 'Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item') { + $this->_inherited = $inheritedItem; /** @phpstan-ignore-line */ + $this->_processed = $processedItem; /** @phpstan-ignore-line */ + } + }; + $inheritPlugins = $inheritPlugins->bindTo($this->object, PluginList::class); + $this->object->method('_inheritPlugins')->willReturnCallback($inheritPlugins); + $this->assertEquals($expectedResult, $this->object->getNext($type, $method, $code)); } @@ -209,139 +285,10 @@ public function getPluginsDataProvider() [4 => ['simple_plugin']], Item::class, 'getName', 'global', - ], - [ - // advanced plugin has lower sort order - [2 => 'advanced_plugin', 4 => ['advanced_plugin']], - Item::class, - 'getName', - 'backend' - ], - [ - // advanced plugin has lower sort order - [4 => ['simple_plugin']], - Item::class, - 'getName', - 'backend', - 'advanced_plugin' - ], - // simple plugin is disabled in configuration for - // \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item in frontend - [null, Item::class, 'getName', 'frontend'], - // test plugin inheritance - [ - [4 => ['simple_plugin']], - Enhanced::class, - 'getName', - 'global' - ], - [ - // simple plugin is disabled in configuration for parent - [2 => 'advanced_plugin', 4 => ['advanced_plugin']], - Enhanced::class, - 'getName', - 'frontend' - ], - [ - null, - ItemContainer::class, - 'getName', - 'global' - ], - [ - [4 => ['simple_plugin']], - ItemContainer::class, - 'getName', - 'backend' - ], - [ - // even though the scope is primary, both primary and global scopes are loaded - // because global is in default priority scheme - [ - 4 => [ - 'primary_plugin', - 'simple_plugin', - ] - ], - \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class, - 'getName', - 'primary', - '__self', - ['primary', 'global'] - ], - [ - [ - 4 => [ - 'primary_plugin', - 'simple_plugin', - ] - ], - \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class, - 'getName', - 'global', - '__self', - ['primary', 'global'] - ], - [ - [ - 4 => [ - 'primary_plugin', - ] - ], - \Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class, - 'getName', - 'frontend', - '__self', - ['primary', 'global'] - ], + ] ]; } - /** - * @covers \Magento\Framework\Interception\PluginList\PluginList::getNext - * @covers \Magento\Framework\Interception\PluginList\PluginList::_inheritPlugins - */ - public function testInheritPluginsWithNonExistingClass() - { - $this->expectException('InvalidArgumentException'); - $this->configScopeMock->expects($this->any()) - ->method('getCurrentScope') - ->willReturn('frontend'); - - $this->object->getNext('SomeType', 'someMethod'); - } - - public function testLoadScopedDataNotCached() - { - $this->configScopeMock->expects($this->exactly(3)) - ->method('getCurrentScope') - ->willReturn('scope'); - $this->serializerMock->expects($this->once()) - ->method('serialize'); - $this->serializerMock->expects($this->never()) - ->method('unserialize'); - $this->cacheMock->expects($this->once()) - ->method('save'); - - $this->assertNull($this->object->getNext('Type', 'method')); - } - - /** - * @covers \Magento\Framework\Interception\PluginList\PluginList::getNext - * @covers \Magento\Framework\Interception\PluginList\PluginList::_inheritPlugins - */ - public function testInheritPluginsWithNotExistingPlugin() - { - $this->loggerMock->expects($this->once()) - ->method('info') - ->with("Reference to undeclared plugin with name 'simple_plugin'."); - $this->configScopeMock->expects($this->any()) - ->method('getCurrentScope') - ->willReturn('frontend'); - - $this->assertNull($this->object->getNext('typeWithoutInstance', 'someMethod')); - } - /** * @covers \Magento\Framework\Interception\PluginList\PluginList::getNext * @covers \Magento\Framework\Interception\PluginList\PluginList::_loadScopedData @@ -365,6 +312,23 @@ public function testLoadScopedDataCached() ->with('global|scope|interception') ->willReturn($serializedData); + $inheritPlugins = function ($type) { + $inherited = [ + 0 => 'key', + 'Type' => null + ]; + $processed = [ + 0 => 'key' + ]; + + if ($type === 'Type') { + $this->_inherited = $inherited; /** @phpstan-ignore-line */ + $this->_processed = $processed; /** @phpstan-ignore-line */ + } + }; + $inheritPlugins = $inheritPlugins->bindTo($this->object, PluginList::class); + $this->object->method('_inheritPlugins')->willReturnCallback($inheritPlugins); + $this->assertNull($this->object->getNext('Type', 'method')); } @@ -372,29 +336,37 @@ public function testLoadScopedDataCached() * @covers \Magento\Framework\Interception\PluginList\PluginList::getNext * @covers \Magento\Framework\Interception\PluginList\PluginList::_loadScopedData */ - public function testLoadScopeDataWithEmptyData() + public function testLoadScopedDataGenerated() { - $this->objectManagerMock->expects($this->any()) - ->method('get') - ->willReturnArgument(0); - $this->configScopeMock->expects($this->any()) + $this->configScopeMock->expects($this->once()) ->method('getCurrentScope') - ->willReturn('emptyscope'); + ->willReturn('scope'); - $this->assertEquals( - [4 => ['simple_plugin']], - $this->object->getNext( - Item::class, - 'getName' - ) - ); - $this->assertEquals( - Simple::class, - $this->object->getPlugin( - Item::class, - 'simple_plugin' - ) - ); + $data = [['key'], ['key'], ['key']]; + + $this->configLoaderMock->expects($this->once()) + ->method('load') + ->with('global|scope|interception') + ->willReturn($data); + + $inheritPlugins = function ($type) { + $inherited = [ + 0 => 'key', + 'Type' => null + ]; + $processed = [ + 0 => 'key' + ]; + + if ($type === 'Type') { + $this->_inherited = $inherited; /** @phpstan-ignore-line */ + $this->_processed = $processed; /** @phpstan-ignore-line */ + } + }; + $inheritPlugins = $inheritPlugins->bindTo($this->object, PluginList::class); + $this->object->method('_inheritPlugins')->willReturnCallback($inheritPlugins); + + $this->assertNull($this->object->getNext('Type', 'method')); } /** diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/_files/load_scoped_mock_map.php b/lib/internal/Magento/Framework/Interception/Test/Unit/_files/load_scoped_mock_map.php new file mode 100644 index 0000000000000..b5002512dc093 --- /dev/null +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/_files/load_scoped_mock_map.php @@ -0,0 +1,86 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item; +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemContainer; +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemContainerPlugin\Simple; +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemPlugin\Advanced; +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\ItemPlugin\Simple as ItemPluginSimple; +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\StartingBackslash; +use Magento\Framework\Interception\Test\Unit\Custom\Module\Model\StartingBackslash\Plugin; + +return [ + [ + [1 => 'global'], + [], + [], + [], + null, + [ + [], + [1 => 'global'], + ['global' => true], + [ + Item::class => [ + 'simple_plugin' => [ + 'sortOrder' => 10, + 'instance' => ItemPluginSimple::class, + ], + ] + ], + [], + [] + ], + ], + [ + [ + 'global', + 'backend' + ], + [], + [], + [], + null, + [ + [], + [ + 'global', + 'backend' + ], + [ + 'global' => true, + 'backend' => true + ], + [ + Item::class => [ + 'simple_plugin' => [ + 'sortOrder' => 10, + 'instance' => Simple::class, + ], + 'advanced_plugin' => [ + 'sortOrder' => 5, + 'instance' => Advanced::class, + ], + ], + ItemContainer::class => [ + 'simple_plugin' => [ + 'sortOrder' => 15, + 'instance' => Simple::class, + ], + ], + StartingBackslash::class => [ + 'simple_plugin' => [ + 'sortOrder' => 20, + 'instance' => Plugin::class, + ], + ] + ], + [], + [] + ] + ] +]; diff --git a/lib/internal/Magento/Framework/Locale/Format.php b/lib/internal/Magento/Framework/Locale/Format.php index 934c9638c7392..e332840327bf7 100644 --- a/lib/internal/Magento/Framework/Locale/Format.php +++ b/lib/internal/Magento/Framework/Locale/Format.php @@ -15,6 +15,17 @@ class Format implements \Magento\Framework\Locale\FormatInterface */ private const JAPAN_LOCALE_CODE = 'ja_JP'; + /** + * Arab locale code + */ + private const ARABIC_LOCALE_CODES = [ + 'ar_DZ', + 'ar_EG', + 'ar_KW', + 'ar_MA', + 'ar_SA', + ]; + /** * @var \Magento\Framework\App\ScopeResolverInterface */ @@ -73,6 +84,11 @@ public function getNumber($value) return (float)$value; } + /** Normalize for Arabic locale */ + if (in_array($this->_localeResolver->getLocale(), self::ARABIC_LOCALE_CODES)) { + $value = $this->normalizeArabicLocale($value); + } + //trim spaces and apostrophes $value = preg_replace('/[^0-9^\^.,-]/m', '', $value); @@ -163,4 +179,22 @@ public function getPriceFormat($localeCode = null, $currencyCode = null) return $result; } + + /** + * Normalizes the number of Arabic locale. + * + * Substitutes Arabic thousands grouping and Arabic decimal separator symbols (U+066C, U+066B) + * with common grouping and decimal separator + * + * @param string $value + * @return string + */ + private function normalizeArabicLocale($value): string + { + $arabicGroupSymbol = '٬'; + $arabicDecimalSymbol = '٫'; + $value = str_replace([$arabicGroupSymbol, $arabicDecimalSymbol], [',', '.'], $value); + + return $value; + } } diff --git a/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php b/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php index a204a733dc848..9c992ecff245c 100644 --- a/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php +++ b/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php @@ -146,6 +146,8 @@ public function provideNumbers(): array ['2,054.00', 2054], ['4,000', 4000.0, 'ja_JP'], ['4,000', 4.0, 'en_US'], + ['2٬599٫50', 2599.50, 'ar_EG'], + ['2٬000٬000٫99', 2000000.99, 'ar_SA'], ]; } } diff --git a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php index 56a4a85b17d99..4c50a3de4fb31 100644 --- a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php +++ b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php @@ -5,8 +5,9 @@ */ namespace Magento\Setup\Console\Command; -use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Filesystem\DriverInterface; +use Magento\Framework\Filesystem\Io\File; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Magento\Framework\Filesystem; @@ -72,6 +73,11 @@ class DiCompileCommand extends Command */ private $componentRegistrar; + /** + * @var File + */ + private $file; + /** * Constructor * @@ -82,6 +88,8 @@ class DiCompileCommand extends Command * @param Filesystem $filesystem * @param DriverInterface $fileDriver * @param \Magento\Framework\Component\ComponentRegistrar $componentRegistrar + * @param File|null $file + * @throws \Magento\Setup\Exception */ public function __construct( DeploymentConfig $deploymentConfig, @@ -90,7 +98,8 @@ public function __construct( ObjectManagerProvider $objectManagerProvider, Filesystem $filesystem, DriverInterface $fileDriver, - ComponentRegistrar $componentRegistrar + ComponentRegistrar $componentRegistrar, + File $file = null ) { $this->deploymentConfig = $deploymentConfig; $this->directoryList = $directoryList; @@ -99,6 +108,7 @@ public function __construct( $this->filesystem = $filesystem; $this->fileDriver = $fileDriver; $this->componentRegistrar = $componentRegistrar; + $this->file = $file ?: ObjectManager::getInstance()->get(File::class); parent::__construct(); } @@ -227,10 +237,10 @@ private function getExcludedModulePaths(array $modulePaths) { $modulesByBasePath = []; foreach ($modulePaths as $modulePath) { - $moduleDir = basename($modulePath); - $vendorPath = dirname($modulePath); - $vendorDir = basename($vendorPath); - $basePath = dirname($vendorPath); + $moduleDir = $this->file->getPathInfo($modulePath)['basename']; + $vendorPath = $this->fileDriver->getParentDirectory($modulePath); + $vendorDir = $this->file->getPathInfo($vendorPath)['basename']; + $basePath = $this->fileDriver->getParentDirectory($vendorPath); $modulesByBasePath[$basePath][$vendorDir][] = $moduleDir; } @@ -360,12 +370,9 @@ private function configureObjectManager(OutputInterface $output) private function getOperationsConfiguration( array $compiledPathsList ) { - $excludePatterns = []; - foreach ($this->excludedPathsList as $excludedPaths) { - $excludePatterns = array_merge($excludedPaths, $excludePatterns); - } + $excludePatterns = array_merge([], ...array_values($this->excludedPathsList)); - $operations = [ + return [ OperationFactory::PROXY_GENERATOR => [], OperationFactory::REPOSITORY_GENERATOR => [ 'paths' => $compiledPathsList['application'], @@ -400,8 +407,7 @@ private function getOperationsConfiguration( $compiledPathsList['generated_helpers'], ], OperationFactory::APPLICATION_ACTION_LIST_GENERATOR => [], + OperationFactory::PLUGIN_LIST_GENERATOR => [], ]; - - return $operations; } } diff --git a/setup/src/Magento/Setup/Module/Di/App/Task/Operation/PluginListGenerator.php b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/PluginListGenerator.php new file mode 100644 index 0000000000000..c1314ec39c245 --- /dev/null +++ b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/PluginListGenerator.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Setup\Module\Di\App\Task\Operation; + +use Magento\Framework\Config\ScopeInterface; +use Magento\Setup\Module\Di\App\Task\OperationInterface; +use Magento\Framework\Interception\ConfigWriterInterface; + +/** + * Writes plugin list configuration data per scope to generated metadata. + */ +class PluginListGenerator implements OperationInterface +{ + /** + * @var ScopeInterface + */ + private $scopeConfig; + + /** + * @var ConfigWriterInterface + */ + private $configWriter; + + /** + * @param ScopeInterface $scopeConfig + * @param ConfigWriterInterface $configWriter + */ + public function __construct( + ScopeInterface $scopeConfig, + ConfigWriterInterface $configWriter + ) { + $this->scopeConfig = $scopeConfig; + $this->configWriter = $configWriter; + } + + /** + * @inheritDoc + */ + public function doOperation() + { + $scopes = $this->scopeConfig->getAllScopes(); + // remove primary scope for production mode as it is only called in developer mode + $scopes = array_diff($scopes, ['primary']); + + $this->configWriter->write($scopes); + } + + /** + * @inheritDoc + */ + public function getName() + { + return 'Plugin list generation'; + } +} diff --git a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php b/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php index 607790e41421c..07ff60c367392 100644 --- a/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php +++ b/setup/src/Magento/Setup/Module/Di/App/Task/OperationFactory.php @@ -19,45 +19,50 @@ class OperationFactory private $objectManager; /** - * Area + * Area config generator operation definition */ const AREA_CONFIG_GENERATOR = 'area'; /** - * Interception + * Interception operation definition */ const INTERCEPTION = 'interception'; /** - * Interception cache + * Interception cache operation definition */ const INTERCEPTION_CACHE = 'interception_cache'; /** - * Repository generator + * Repository generator operation definition */ const REPOSITORY_GENERATOR = 'repository_generator'; /** - * Proxy generator + * Proxy generator operation definition */ const PROXY_GENERATOR = 'proxy_generator'; /** - * Service data attributes generator + * Service data attributes generator operation definition */ const DATA_ATTRIBUTES_GENERATOR = 'extension_attributes_generator'; /** - * Application code generator + * Application code generator operation definition */ const APPLICATION_CODE_GENERATOR = 'application_code_generator'; /** - * Application action list generator + * Application action list generator operation definition */ const APPLICATION_ACTION_LIST_GENERATOR = 'application_action_list_generator'; + /** + * Plugin list generator operation definition + */ + const PLUGIN_LIST_GENERATOR = 'plugin_list_generator'; + /** * Operations definitions * @@ -73,6 +78,7 @@ class OperationFactory self::REPOSITORY_GENERATOR => \Magento\Setup\Module\Di\App\Task\Operation\RepositoryGenerator::class, self::PROXY_GENERATOR => \Magento\Setup\Module\Di\App\Task\Operation\ProxyGenerator::class, self::APPLICATION_ACTION_LIST_GENERATOR => AppActionListGenerator::class, + self::PLUGIN_LIST_GENERATOR => PluginListGenerator::class, ]; /** diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/DiCompileCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/DiCompileCommandTest.php index e269f89073dd7..0386353a0b2df 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/DiCompileCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/DiCompileCommandTest.php @@ -65,6 +65,9 @@ class DiCompileCommandTest extends TestCase /** @var OutputFormatterInterface|MockObject */ private $outputFormatterMock; + /** @var Filesystem\Io\File|MockObject */ + private $fileMock; + protected function setUp(): void { $this->deploymentConfigMock = $this->createMock(DeploymentConfig::class); @@ -96,6 +99,14 @@ protected function setUp(): void $this->fileDriverMock = $this->getMockBuilder(File::class) ->disableOriginalConstructor() ->getMock(); + $this->fileDriverMock->method('getParentDirectory')->willReturnMap( + [ + ['/path/to/module/one', '/path/to/module'], + ['/path/to/module', '/path/to'], + ['/path (1)/to/module/two', '/path (1)/to/module'], + ['/path (1)/to/module', '/path (1)/to'], + ] + ); $this->componentRegistrarMock = $this->createMock(ComponentRegistrar::class); $this->componentRegistrarMock->expects($this->any())->method('getPaths')->willReturnMap([ [ComponentRegistrar::MODULE, ['/path/to/module/one', '/path (1)/to/module/two']], @@ -108,6 +119,17 @@ protected function setUp(): void $this->outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->outputMock->method('getFormatter') ->willReturn($this->outputFormatterMock); + $this->fileMock = $this->getMockBuilder(Filesystem\Io\File::class) + ->disableOriginalConstructor() + ->getMock(); + $this->fileMock->method('getPathInfo')->willReturnMap( + [ + ['/path/to/module/one', ['basename' => 'one']], + ['/path/to/module', ['basename' => 'module']], + ['/path (1)/to/module/two', ['basename' => 'two']], + ['/path (1)/to/module', ['basename' => 'module']], + ] + ); $this->command = new DiCompileCommand( $this->deploymentConfigMock, @@ -116,7 +138,8 @@ protected function setUp(): void $objectManagerProviderMock, $this->filesystemMock, $this->fileDriverMock, - $this->componentRegistrarMock + $this->componentRegistrarMock, + $this->fileMock ); } @@ -160,7 +183,7 @@ public function testExecute() ->with(ProgressBar::class) ->willReturn($progressBar); - $this->managerMock->expects($this->exactly(8))->method('addOperation') + $this->managerMock->expects($this->exactly(9))->method('addOperation') ->withConsecutive( [OperationFactory::PROXY_GENERATOR, []], [OperationFactory::REPOSITORY_GENERATOR, $this->anything()], @@ -178,7 +201,8 @@ public function testExecute() [OperationFactory::INTERCEPTION, $this->anything()], [OperationFactory::AREA_CONFIG_GENERATOR, $this->anything()], [OperationFactory::INTERCEPTION_CACHE, $this->anything()], - [OperationFactory::APPLICATION_ACTION_LIST_GENERATOR, $this->anything()] + [OperationFactory::APPLICATION_ACTION_LIST_GENERATOR, $this->anything()], + [OperationFactory::PLUGIN_LIST_GENERATOR, $this->anything()] ); $this->managerMock->expects($this->once())->method('process');