@@ -22,12 +24,14 @@ $helper = $this->helper(\Magento\Search\Helper\Data::class);
isSuggestionsAllowed()):?>
+ data-mage-init='{"quickSearch":{
+ "formSelector":"#search_mini_form",
+ "url":"= $block->escapeUrl($helper->getSuggestUrl())?>",
+ "destinationSelector":"#search_autocomplete",
+ "minSearchLength":"= $block->escapeHtml($helper->getMinQueryLength()) ?>"}
+ }'
+
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 @@
-
+
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 @@
-
-
+
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 @@
-
-
+
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 @@
-
-
+
-
-
+
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 @@
-
-
+
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 @@
-
-
+
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 @@
-
+
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 @@
+
+
@@ -431,6 +433,16 @@
+
+
+ Magento\Framework\ObjectManager\Config\Reader\Dom\Proxy
+ \Psr\Log\LoggerInterface\Proxy
+
+ - primary
+ - global
+
+
+
Magento\Framework\App\ResourceConnection\ConnectionFactory
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 @@
+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 @@
+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 @@
+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 @@
+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 @@
+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 @@
+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('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 @@
+ '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 @@
+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');