From 66e63e010af38399b7b5a4ad81a665d84d7e830d Mon Sep 17 00:00:00 2001 From: Simon Sippert Date: Mon, 28 Jun 2021 11:48:46 +0200 Subject: [PATCH 01/87] Remove casting to int on increment ID which can be prefixed by a string --- .../Sales/view/frontend/templates/order/print/invoice.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml b/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml index 3c0196d59329c..e87a8578052b5 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml @@ -14,7 +14,7 @@
- escapeHtml(__('Invoice #')) ?>getIncrementId() ?> + escapeHtml(__('Invoice #')) ?>getIncrementId() ?>
From 16ef8b1e28bd92f3ee086d8181c398cc47a45207 Mon Sep 17 00:00:00 2001 From: Jakub Winkler Date: Thu, 15 Jul 2021 10:40:45 +0200 Subject: [PATCH 02/87] #33486 issue bug-fix --- app/code/Magento/Eav/Model/Entity/Attribute.php | 2 +- app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Eav/Model/Entity/Attribute.php b/app/code/Magento/Eav/Model/Entity/Attribute.php index 04175c2da94d1..e24787bb0056f 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute.php @@ -356,12 +356,12 @@ public function getBackendTypeByInput($type) case 'text': case 'gallery': case 'media_image': - case 'multiselect': $field = 'varchar'; break; case 'image': case 'textarea': + case 'multiselect': $field = 'text'; break; diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeTest.php index 476f458d25f8e..549ca7cf982e8 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeTest.php @@ -62,7 +62,7 @@ public static function dataGetBackendTypeByInput() ['text', 'varchar'], ['gallery', 'varchar'], ['media_image', 'varchar'], - ['multiselect', 'varchar'], + ['multiselect', 'text'], ['image', 'text'], ['textarea', 'text'], ['date', 'datetime'], From 2be10b7f4aeb1779356fafbd65ad1c8d2d95dd58 Mon Sep 17 00:00:00 2001 From: Jakub Winkler Date: Sun, 18 Jul 2021 12:23:00 +0200 Subject: [PATCH 03/87] #bug-33486 - fixing integration tests for multiselect attributes stores in _text entity table --- .../Catalog/Model/ProductGettersTest.php | 26 ++++++++++++++++--- .../Catalog/_files/multiselect_attribute.php | 25 +++++++++--------- .../multiselect_attribute_with_html.php | 2 +- ...select_attribute_with_incorrect_values.php | 2 +- ...ultiselect_attribute_with_source_model.php | 2 +- .../products_with_multiselect_attribute.php | 14 ++++++++++ .../Model/Adapter/ElasticsearchTest.php | 2 +- .../_files/multiselect_attribute.php | 2 +- .../Search/_files/filterable_attribute.php | 2 +- .../order_address_with_multi_attribute.php | 2 +- 10 files changed, 56 insertions(+), 23 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductGettersTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductGettersTest.php index 12eefd8f59b60..2ce4a4f8876ec 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductGettersTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductGettersTest.php @@ -215,9 +215,9 @@ public function testGetAttributeTextArray() $product = $this->productRepository->get('simple_ms_2'); $product->getAttributeText('multiselect_attribute'); $expected = [ - 'Option 2', - 'Option 3', - 'Option 4 "!@#$%^&*' + 'Multiselect option 2', + 'Multiselect option 3', + 'Multiselect option 4' ]; self::assertEquals( $expected, @@ -225,6 +225,26 @@ public function testGetAttributeTextArray() ); } + /** + * @magentoDataFixture Magento/Catalog/_files/products_with_multiselect_attribute.php + */ + public function testMultipleMultiselectValues() + { + $expectedArray = []; + + for ($i = 1; $i < 200; $i++) { + $expectedArray[] = sprintf('Multiselect option %d', $i); + } + + $product = $this->productRepository->get('simple_ms_3'); + + self::assertEquals( + $expectedArray, + $product->getAttributeText('multiselect_attribute') + ); + } + + public function testGetCustomDesignDate() { $this->assertEquals(['from' => null, 'to' => null], $this->_model->getCustomDesignDate()); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php index 7dacdc42463a9..1425a2f60ebde 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php @@ -12,6 +12,15 @@ $attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class ); + +$valueOptionArray = []; +$orderArray = []; + +for ($i = 1; $i < 200; $i++) { + $valueOptionArray[sprintf('option_%d', $i)] = [sprintf('Multiselect option %d', $i)]; + $orderArray[sprintf('option_%d', $i)] = $i; +} + $entityType = $installer->getEntityTypeId('catalog_product'); if (!$attribute->loadByCode($entityType, 'multiselect_attribute')->getAttributeId()) { $attribute->setData( @@ -34,21 +43,11 @@ 'used_in_product_listing' => 0, 'used_for_sort_by' => 0, 'frontend_label' => ['Multiselect Attribute'], - 'backend_type' => 'varchar', + 'backend_type' => 'text', 'backend_model' => \Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend::class, 'option' => [ - 'value' => [ - 'option_1' => ['Option 1'], - 'option_2' => ['Option 2'], - 'option_3' => ['Option 3'], - 'option_4' => ['Option 4 "!@#$%^&*'] - ], - 'order' => [ - 'option_1' => 1, - 'option_2' => 2, - 'option_3' => 3, - 'option_4' => 4, - ], + 'value' => $valueOptionArray, + 'order' => $orderArray ], ] ); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_html.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_html.php index bb2bb5016b426..de68fc9351eca 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_html.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_html.php @@ -41,7 +41,7 @@ 'used_in_product_listing' => 0, 'used_for_sort_by' => 0, 'frontend_label' => ['Multiselect Attribute'], - 'backend_type' => 'varchar', + 'backend_type' => 'text', 'backend_model' => ArrayBackend::class, 'option' => [ 'value' => [ diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_incorrect_values.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_incorrect_values.php index 5e32afbcd9027..a73c30eba6a34 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_incorrect_values.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_incorrect_values.php @@ -33,7 +33,7 @@ 'used_in_product_listing' => 0, 'used_for_sort_by' => 0, 'frontend_label' => ['Multiselect Attribute'], - 'backend_type' => 'varchar', + 'backend_type' => 'text', 'backend_model' => \Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend::class, 'option' => [ 'value' => [ diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_source_model.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_source_model.php index 6dcba90bf44fa..7909b4cc8a88a 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_source_model.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_with_source_model.php @@ -34,7 +34,7 @@ 'used_in_product_listing' => 0, 'used_for_sort_by' => 0, 'frontend_label' => ['Multiselect Attribute with Source Model'], - 'backend_type' => 'varchar', + 'backend_type' => 'text', 'backend_model' => \Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend::class, 'source_model' => \Magento\Catalog\_files\MultiselectSourceMock::class ] diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute.php index 5131cfc47b85b..9904f18edfbc4 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute.php @@ -54,3 +54,17 @@ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->save(); + +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setId($optionIds[2] * 10) + ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) + ->setWebsiteIds([1]) + ->setName('With Multiselect 3') + ->setSku('simple_ms_3') + ->setPrice(10) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setMultiselectAttribute(array_values($optionIds)) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Adapter/ElasticsearchTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Adapter/ElasticsearchTest.php index 986d98f0f2f14..9d96a5b860631 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Adapter/ElasticsearchTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Adapter/ElasticsearchTest.php @@ -140,7 +140,7 @@ public function createNewAttribute(): void 'used_in_product_listing' => 0, 'used_for_sort_by' => 0, 'frontend_label' => ['Multiselect Attribute'], - 'backend_type' => 'varchar', + 'backend_type' => 'text', 'backend_model' => ArrayBackend::class, 'option' => [ 'value' => [ diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/multiselect_attribute.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/multiselect_attribute.php index 0f47445323092..b9a1771c2ffd2 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/multiselect_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/multiselect_attribute.php @@ -34,7 +34,7 @@ 'used_in_product_listing' => 0, 'used_for_sort_by' => 0, 'frontend_label' => ['Multiselect Attribute'], - 'backend_type' => 'varchar', + 'backend_type' => 'text', 'backend_model' => \Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend::class, 'option' => [ 'value' => [ diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attribute.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attribute.php index a74669e9890af..42b68cf02fe0c 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attribute.php @@ -54,7 +54,7 @@ 'value' => ['option_0' => ['Option 1'], 'option_1' => ['Option 2']], 'order' => ['option_0' => 1, 'option_1' => 2], ], - 'backend_type' => 'varchar', + 'backend_type' => 'text', ] ); $multiselectAttribute->save(); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_address_with_multi_attribute.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_address_with_multi_attribute.php index be454aef872a8..a079a2ca3a515 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order_address_with_multi_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_address_with_multi_attribute.php @@ -40,7 +40,7 @@ 'frontend_input' => 'multiselect', 'frontend_label' => ['Multiselect Attribute'], 'sort_order' => '0', - 'backend_type' => 'varchar', + 'backend_type' => 'text', 'is_user_defined' => 1, 'is_system' => 0, 'is_required' => '0', From b53d2afcf6c23e3679729bc0f5c3ac4d81d2844b Mon Sep 17 00:00:00 2001 From: Ihor Sviziev Date: Wed, 4 Aug 2021 16:21:29 +0300 Subject: [PATCH 04/87] Optimize QuoteIdToMaskedQuoteId model and fix infinite loop --- .../Quote/Model/QuoteIdToMaskedQuoteId.php | 44 +++++++++++-------- .../Quote/Model/ResourceModel/Quote.php | 22 ++++++++++ .../Model/ResourceModel/Quote/QuoteIdMask.php | 23 ++++++++++ 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/app/code/Magento/Quote/Model/QuoteIdToMaskedQuoteId.php b/app/code/Magento/Quote/Model/QuoteIdToMaskedQuoteId.php index 2e802f47cfefe..8d1e002a832fa 100644 --- a/app/code/Magento/Quote/Model/QuoteIdToMaskedQuoteId.php +++ b/app/code/Magento/Quote/Model/QuoteIdToMaskedQuoteId.php @@ -7,7 +7,10 @@ namespace Magento\Quote\Model; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\Quote\Model\ResourceModel\Quote\QuoteIdMask as QuoteIdMaskResource; /** @@ -16,32 +19,31 @@ class QuoteIdToMaskedQuoteId implements QuoteIdToMaskedQuoteIdInterface { /** - * @var QuoteIdMaskFactory - */ - private $quoteIdMaskFactory; - /** - * @var CartRepositoryInterface + * @var QuoteIdMaskResource */ - private $cartRepository; + private $quoteIdMaskResource; /** - * @var QuoteIdMaskResource + * @var QuoteResource */ - private $quoteIdMaskResource; + private $quoteResource; /** * @param QuoteIdMaskFactory $quoteIdMaskFactory * @param CartRepositoryInterface $cartRepository * @param QuoteIdMaskResource $quoteIdMaskResource + * @param QuoteResource|null $quoteResourceModel + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( QuoteIdMaskFactory $quoteIdMaskFactory, CartRepositoryInterface $cartRepository, - QuoteIdMaskResource $quoteIdMaskResource + QuoteIdMaskResource $quoteIdMaskResource, + QuoteResource $quoteResourceModel = null ) { - $this->quoteIdMaskFactory = $quoteIdMaskFactory; - $this->cartRepository = $cartRepository; $this->quoteIdMaskResource = $quoteIdMaskResource; + $this->quoteResource = $quoteResourceModel ?? ObjectManager::getInstance()->get(QuoteResource::class); } /** @@ -49,13 +51,19 @@ public function __construct( */ public function execute(int $quoteId): string { - /* Check the quote exists to avoid database constraint issues */ - $this->cartRepository->get($quoteId); - - $quoteIdMask = $this->quoteIdMaskFactory->create(); - $this->quoteIdMaskResource->load($quoteIdMask, $quoteId, 'quote_id'); - $maskedId = $quoteIdMask->getMaskedId() ?? ''; + // Check the quote exists to avoid database constraint issues + if (!$this->quoteResource->isExists($quoteId)) { + throw new NoSuchEntityException( + __( + 'No such entity with %fieldName = %fieldValue', + [ + 'fieldName' => 'quoteId', + 'fieldValue' => $quoteId + ] + ) + ); + } - return $maskedId; + return (string)$this->quoteIdMaskResource->getMaskedQuoteId($quoteId); } } diff --git a/app/code/Magento/Quote/Model/ResourceModel/Quote.php b/app/code/Magento/Quote/Model/ResourceModel/Quote.php index e6350dd5aeb2b..59eb446903b89 100644 --- a/app/code/Magento/Quote/Model/ResourceModel/Quote.php +++ b/app/code/Magento/Quote/Model/ResourceModel/Quote.php @@ -316,4 +316,26 @@ public function save(\Magento\Framework\Model\AbstractModel $object) return $this; } + + /** + * Quickly check if quote exists + * + * Uses direct DB query due to performance reasons + * + * @param int $quoteId + * @return bool + */ + public function isExists(int $quoteId): bool + { + $connection = $this->getConnection(); + $mainTable = $this->getMainTable(); + $idFieldName = $this->getIdFieldName(); + + $field = $connection->quoteIdentifier(sprintf('%s.%s', $mainTable, $idFieldName)); + $select = $connection->select() + ->from($mainTable, [$idFieldName]) + ->where($field . '=?', $quoteId); + + return (bool)$connection->fetchOne($select); + } } diff --git a/app/code/Magento/Quote/Model/ResourceModel/Quote/QuoteIdMask.php b/app/code/Magento/Quote/Model/ResourceModel/Quote/QuoteIdMask.php index d40cf525e8659..5f9c4febd23ff 100644 --- a/app/code/Magento/Quote/Model/ResourceModel/Quote/QuoteIdMask.php +++ b/app/code/Magento/Quote/Model/ResourceModel/Quote/QuoteIdMask.php @@ -22,4 +22,27 @@ protected function _construct() { $this->_init('quote_id_mask', 'entity_id'); } + + /** + * Retrieves masked quote id + * + * Uses direct DB query due to performance reasons + * + * @param int $quoteId + * @return string|null + */ + public function getMaskedQuoteId(int $quoteId): ?string + { + $connection = $this->getConnection(); + $mainTable = $this->getMainTable(); + $field = $connection->quoteIdentifier(sprintf('%s.%s', $mainTable, 'quote_id')); + + $select = $connection->select() + ->from($mainTable, ['masked_id']) + ->where($field . '=?', $quoteId); + + $result = $connection->fetchOne($select); + + return $result ?: null; + } } From 8864695fc72c52a6a585685c38cd3691028c39d2 Mon Sep 17 00:00:00 2001 From: Simon Sippert Date: Fri, 27 Aug 2021 17:18:47 +0200 Subject: [PATCH 05/87] Add escaping to increment ID Co-authored-by: Ihor Sviziev --- .../Sales/view/frontend/templates/order/print/invoice.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml b/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml index e87a8578052b5..c4dfa88eacfaa 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml @@ -14,7 +14,7 @@
- escapeHtml(__('Invoice #')) ?>getIncrementId() ?> + escapeHtml(__('Invoice #')) ?>escapeHtml($_invoice->getIncrementId()) ?>
From 3f289196cf35559249ca0f60cebe52c678fbb303 Mon Sep 17 00:00:00 2001 From: Lyra Ghost Date: Mon, 30 Aug 2021 12:15:32 -0400 Subject: [PATCH 06/87] Fix active state on multiselect crumb --- .../web/css/source/actions/_actions-multiselect.less | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less index 96a607346fbe8..036b956c79897 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less @@ -285,10 +285,15 @@ } &:active { + padding: 0; transform: scale(.9); + + &:before { + font-size: 1em; + } } - &:before { + &:before{ font-size: 1em; } } From 041a247b658478521373050a5c5b29680f73bbf5 Mon Sep 17 00:00:00 2001 From: Lyra Ghost Date: Mon, 30 Aug 2021 12:19:26 -0400 Subject: [PATCH 07/87] Fix whitespace --- .../backend/web/css/source/actions/_actions-multiselect.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less index 036b956c79897..16cfe206f3e75 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less @@ -293,7 +293,7 @@ } } - &:before{ + &:before { font-size: 1em; } } From 8d77e8bc58b000d60e2aacfee9fc01c34d0151a4 Mon Sep 17 00:00:00 2001 From: Simon Sippert Date: Wed, 1 Sep 2021 11:05:00 +0200 Subject: [PATCH 08/87] Fix integration test --- .../Magento/Sales/Block/Order/PrintOrder/InvoiceTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/InvoiceTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/InvoiceTest.php index 5bdd9aa0f3d1c..768bf1e9feac7 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/InvoiceTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/InvoiceTest.php @@ -125,11 +125,11 @@ public function testPrintInvoice(): void Xpath::getElementsCountForXpath( sprintf( "//div[contains(@class, 'order-title')]/strong[contains(text(), '%s')]", - __('Invoice #%1', (int)$invoice->getIncrementId()) + __('Invoice #%1', $invoice->getIncrementId()) ), $blockHtml ), - sprintf('Title for %s was not found.', __('Invoice #%1', (int)$invoice->getIncrementId())) + sprintf('Title for %s was not found.', __('Invoice #%1', $invoice->getIncrementId())) ); $this->assertOrderInformation($order, $blockHtml); } From 61adcaa86e00e9efe2d7822c041c4c00e09531e7 Mon Sep 17 00:00:00 2001 From: Ihor Sviziev Date: Thu, 2 Sep 2021 00:17:36 +0300 Subject: [PATCH 09/87] Remove casting to int on increment ID which can be prefixed by a string Add test coverage --- .../Block/Order/PrintOrder/InvoiceTest.php | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/InvoiceTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/InvoiceTest.php index 768bf1e9feac7..adad0757f34b6 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/InvoiceTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Order/PrintOrder/InvoiceTest.php @@ -117,6 +117,37 @@ public function testPrintInvoice(): void $order = $this->orderFactory->create()->loadByIncrementId('100000555'); $invoice = $order->getInvoiceCollection()->getFirstItem(); $this->assertNotNull($invoice->getId()); + $this->assertTrue(is_numeric($invoice->getIncrementId())); + $this->registerOrder($order); + $this->registerInvoice($invoice); + $blockHtml = $this->renderPrintInvoiceBlock(); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf( + "//div[contains(@class, 'order-title')]/strong[contains(text(), '%s')]", + __('Invoice #%1', $invoice->getIncrementId()) + ), + $blockHtml + ), + sprintf('Title for %s was not found.', __('Invoice #%1', $invoice->getIncrementId())) + ); + $this->assertOrderInformation($order, $blockHtml); + } + + /** + * @magentoDataFixture Magento/Sales/_files/invoices_for_items.php + * + * @return void + */ + public function testPrintInvoiceWithStringPrefix(): void + { + $order = $this->orderFactory->create()->loadByIncrementId('100000555'); + $invoice = $order->getInvoiceCollection()->getFirstItem(); + $this->assertNotNull($invoice->getId()); + // set text prefix to increment id + $invoice->setIncrementId('prefix-' . $invoice->getIncrementId()); + $this->assertFalse(is_numeric($invoice->getIncrementId())); $this->registerOrder($order); $this->registerInvoice($invoice); $blockHtml = $this->renderPrintInvoiceBlock(); From ef76b7e2b885da45cb37641ba9fe3ef27c68f422 Mon Sep 17 00:00:00 2001 From: Teun Lassche Date: Mon, 6 Sep 2021 09:46:21 +0200 Subject: [PATCH 10/87] Cache subsequent glob calls --- lib/internal/Magento/Framework/Filesystem/Glob.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/internal/Magento/Framework/Filesystem/Glob.php b/lib/internal/Magento/Framework/Filesystem/Glob.php index 9d8de333e35fa..02af8790ffdc3 100644 --- a/lib/internal/Magento/Framework/Filesystem/Glob.php +++ b/lib/internal/Magento/Framework/Filesystem/Glob.php @@ -14,6 +14,7 @@ */ class Glob extends LaminasGlob { + protected static $cache = []; /** * Find pathnames matching a pattern. * @@ -24,11 +25,16 @@ class Glob extends LaminasGlob */ public static function glob($pattern, $flags = 0, $forceFallback = false) { + $key = $pattern . '|' . $flags . '|' . ($forceFallback ? 1 : 0); + if (isset(self::$cache[$key])) { + return self::$cache[$key]; + } try { $result = LaminasGlob::glob($pattern, $flags, $forceFallback); } catch (LaminasRuntimeException $e) { $result = []; } + self::$cache[$key] = $result; return $result; } } From a06fcadce841ee46ec1d05d0055ef6f1d6a2730c Mon Sep 17 00:00:00 2001 From: Teun Lassche Date: Mon, 6 Sep 2021 10:18:05 +0200 Subject: [PATCH 11/87] Make glob cache property private Co-authored-by: Ihor Sviziev --- lib/internal/Magento/Framework/Filesystem/Glob.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Filesystem/Glob.php b/lib/internal/Magento/Framework/Filesystem/Glob.php index 02af8790ffdc3..d43ce1ba244df 100644 --- a/lib/internal/Magento/Framework/Filesystem/Glob.php +++ b/lib/internal/Magento/Framework/Filesystem/Glob.php @@ -14,7 +14,7 @@ */ class Glob extends LaminasGlob { - protected static $cache = []; + private static $cache = []; /** * Find pathnames matching a pattern. * From 82f040f3f17da08d6af0d51c0f5ed33abd94d040 Mon Sep 17 00:00:00 2001 From: Jakub Winkler Date: Sat, 18 Sep 2021 14:48:23 +0200 Subject: [PATCH 12/87] #33486 - fixing tests and applying code changes to indexes and attributes --- .../Product/Indexer/Eav/Source.php | 6 +- ...pdateMultiselectAttributesBackendTypes.php | 103 ++++++++++++++++++ .../Model/Rule/Condition/Product.php | 2 +- .../Attribute/DataProvider/MultipleSelect.php | 2 +- .../Catalog/Model/ProductGettersTest.php | 12 +- .../Product/Indexer/Eav/SourceTest.php | 18 ++- .../Catalog/_files/multiselect_attribute.php | 64 ++++++++++- .../_files/multiselect_attribute_rollback.php | 3 + .../products_with_multiselect_attribute.php | 36 ++++-- 9 files changed, 208 insertions(+), 38 deletions(-) create mode 100644 app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php index e625e38b59f31..b32c34678fe20 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php @@ -105,7 +105,7 @@ protected function _getIndexableAttributes($multiSelect) ); if ($multiSelect == true) { - $select->where('ea.backend_type = ?', 'varchar')->where('ea.frontend_input = ?', 'multiselect'); + $select->where('ea.backend_type = ?', 'text')->where('ea.frontend_input = ?', 'multiselect'); } else { $select->where('ea.backend_type = ?', 'int')->where('ea.frontend_input IN( ? )', ['select', 'boolean']); } @@ -303,14 +303,14 @@ protected function _prepareMultiselectIndex($entityIds = null, $attributeId = nu // prepare get multiselect values query $productValueExpression = $connection->getCheckSql('pvs.value_id > 0', 'pvs.value', 'pvd.value'); $select = $connection->select()->from( - ['pvd' => $this->getTable('catalog_product_entity_varchar')], + ['pvd' => $this->getTable('catalog_product_entity_text')], [] )->join( ['cs' => $this->getTable('store')], '', [] )->joinLeft( - ['pvs' => $this->getTable('catalog_product_entity_varchar')], + ['pvs' => $this->getTable('catalog_product_entity_text')], "pvs.{$productIdField} = pvd.{$productIdField} AND pvs.attribute_id = pvd.attribute_id" . ' AND pvs.store_id=cs.store_id', [] diff --git a/app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php b/app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php new file mode 100644 index 0000000000000..06ffb40a7a1f2 --- /dev/null +++ b/app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php @@ -0,0 +1,103 @@ +dataSetup = $dataSetup; + $this->eavSetupFactory = $eavSetupFactory; + } + + /** + * @return array + */ + public static function getDependencies() + { + return []; + } + + /** + * @return array + */ + public function getAliases() + { + return []; + } + + /** + * @return UpdateMultiselectAttributesBackendTypes + * @throws LocalizedException + */ + public function apply() + { + $this->dataSetup->startSetup(); + + $connection = $this->dataSetup->getConnection(); + $attributeTable = $connection->getTableName('eav_attribute'); + /** @var EavSetup $eavSetup */ + $eavSetup = $this->eavSetupFactory->create(['setup' => $this->dataSetup]); + $entityTypeId = $eavSetup->getEntityTypeId(Product::ENTITY); + $attributesToMigrate = $connection->fetchCol( + $connection + ->select() + ->from($attributeTable, ['attribute_id']) + ->where('entity_type_id = ?', $entityTypeId) + ->where('backend_type = ?', 'varchar') + ->where('frontend_input = ?', 'multiselect') + ); + + $varcharTable = $connection->getTableName('catalog_product_entity_varchar'); + $textTable = $connection->getTableName('catalog_product_entity_text'); + $varcharTableDataSql = $connection + ->select() + ->from($varcharTable) + ->where('attribute_id in (?)', $attributesToMigrate); + $dataToMigrate = array_map(static function ($row) { + $row['value_id'] = null; + return $row; + }, $connection->fetchAll($varcharTableDataSql)); + + foreach (array_chunk($dataToMigrate, 2000) as $dataChunk) { + $connection->insertMultiple($textTable, $dataChunk); + } + + $connection->query($connection->deleteFromSelect($varcharTableDataSql, $varcharTable)); + + foreach ($attributesToMigrate as $attributeId) { + $eavSetup->updateAttribute($entityTypeId, $attributeId, 'backend_type', 'text'); + } + + $this->dataSetup->endSetup(); + + return $this; + } +} diff --git a/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php b/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php index e4bb0a8a66b0f..ce6cac82806ab 100644 --- a/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php +++ b/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php @@ -182,7 +182,7 @@ protected function addGlobalAttribute( $linkField = $attribute->getEntity()->getLinkField(); $collection->getSelect()->join( - [$alias => $collection->getTable('catalog_product_entity_varchar')], + [$alias => $collection->getTable($attribute->getBackendTable())], "($alias.$linkField = e.$linkField) AND ($alias.store_id = $storeId)" . " AND ($alias.attribute_id = {$attribute->getId()})", [] diff --git a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/MultipleSelect.php b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/MultipleSelect.php index 4d72f5b316ea0..fc6cea3a986ae 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/MultipleSelect.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Eav/Model/Attribute/DataProvider/MultipleSelect.php @@ -89,7 +89,7 @@ protected function getUpdateExpectedData(): array 'frontend_class' => null, 'used_for_sort_by' => '0', 'is_user_defined' => '1', - 'backend_type' => 'varchar', + 'backend_type' => 'text', ] ); } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductGettersTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductGettersTest.php index 2ce4a4f8876ec..9996ac4836831 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductGettersTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductGettersTest.php @@ -207,6 +207,7 @@ public function testGetAttributeText() $this->assertEquals('Enabled', $this->_model->getAttributeText('status')); } + /** * @magentoDataFixture Magento/Catalog/_files/products_with_multiselect_attribute.php */ @@ -215,9 +216,9 @@ public function testGetAttributeTextArray() $product = $this->productRepository->get('simple_ms_2'); $product->getAttributeText('multiselect_attribute'); $expected = [ - 'Multiselect option 2', - 'Multiselect option 3', - 'Multiselect option 4' + 'Option 2', + 'Option 3', + 'Option 4 "!@#$%^&*' ]; self::assertEquals( $expected, @@ -225,10 +226,11 @@ public function testGetAttributeTextArray() ); } + /** * @magentoDataFixture Magento/Catalog/_files/products_with_multiselect_attribute.php */ - public function testMultipleMultiselectValues() + public function testMultipleMultiselectTextValues() { $expectedArray = []; @@ -240,7 +242,7 @@ public function testMultipleMultiselectValues() self::assertEquals( $expectedArray, - $product->getAttributeText('multiselect_attribute') + $product->getAttributeText('multiselect_attribute_text') ); } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php index 6ecfdbe567c07..b9460d950338c 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php @@ -138,26 +138,23 @@ public function testReindexMultiselectAttribute() /** @var $options \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection */ $options = $objectManager->create(\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class); $options->setAttributeFilter($attr->getId()); - $optionIds = $options->getAllIds(); - $product1Id = $optionIds[0] * 10; - $product2Id = $optionIds[1] * 10; /** @var \Magento\Catalog\Model\Product $product1 **/ - $product1 = $productRepository->getById($product1Id); + $product1 = $productRepository->get('simple_ms_1'); $product1->setSpecialFromDate(date('Y-m-d H:i:s')); $product1->setNewsFromDate(date('Y-m-d H:i:s')); $productRepository->save($product1); /** @var \Magento\Catalog\Model\Product $product2 **/ - $product2 = $productRepository->getById($product2Id); - $product1->setSpecialFromDate(date('Y-m-d H:i:s')); - $product1->setNewsFromDate(date('Y-m-d H:i:s')); + $product2 = $productRepository->get('simple_ms_2'); + $product2->setSpecialFromDate(date('Y-m-d H:i:s')); + $product2->setNewsFromDate(date('Y-m-d H:i:s')); $productRepository->save($product2); $this->_eavIndexerProcessor->reindexAll(); $connection = $this->productResource->getConnection(); $select = $connection->select()->from($this->productResource->getTable('catalog_product_index_eav')) - ->where('entity_id in (?)', [$product1Id, $product2Id]) + ->where('entity_id in (?)', [$product1->getId(), $product2->getId()]) ->where('attribute_id = ?', $attr->getId()); $result = $connection->fetchAll($select); @@ -219,6 +216,7 @@ public function testReindexMultiselectAttributeWithSourceModel() $sourceModel = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( MultiselectSourceMock::class ); + $options = $sourceModel->getAllOptions(); $product1Id = $options[0]['value'] * 10; $product2Id = $options[1]['value'] * 10; @@ -231,8 +229,8 @@ public function testReindexMultiselectAttributeWithSourceModel() /** @var \Magento\Catalog\Model\Product $product2 **/ $product2 = $productRepository->getById($product2Id); - $product1->setSpecialFromDate(date('Y-m-d H:i:s')); - $product1->setNewsFromDate(date('Y-m-d H:i:s')); + $product2->setSpecialFromDate(date('Y-m-d H:i:s')); + $product2->setNewsFromDate(date('Y-m-d H:i:s')); $productRepository->save($product2); $this->_eavIndexerProcessor->reindexAll(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php index 1425a2f60ebde..e308d764e1f6b 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php @@ -8,8 +8,13 @@ $installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Catalog\Setup\CategorySetup::class ); -/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ -$attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( +/** @var $attributeMultiselect \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ +$attributeMultiselect = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class +); + +/** @var $attributeMultiselectText \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ +$attributeMultiselectText = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class ); @@ -22,12 +27,59 @@ } $entityType = $installer->getEntityTypeId('catalog_product'); -if (!$attribute->loadByCode($entityType, 'multiselect_attribute')->getAttributeId()) { - $attribute->setData( +if (!$attributeMultiselect->loadByCode($entityType, 'multiselect_attribute')->getAttributeId()) { + $attributeMultiselect->setData( [ 'attribute_code' => 'multiselect_attribute', 'entity_type_id' => $entityType, 'is_global' => 1, + 'is_user_defined' => 1, + 'frontend_input' => 'multiselect', + 'is_unique' => 0, + 'is_required' => 0, + 'is_searchable' => 0, + 'is_visible_in_advanced_search' => 0, + 'is_comparable' => 0, + 'is_filterable' => 1, + 'is_filterable_in_search' => 0, + 'is_used_for_promo_rules' => 0, + 'is_html_allowed_on_front' => 1, + 'is_visible_on_front' => 0, + 'used_in_product_listing' => 0, + 'used_for_sort_by' => 0, + 'frontend_label' => ['Multiselect Attribute'], + 'backend_type' => 'text', + 'backend_model' => \Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend::class, + 'option' => [ + 'value' => [ + 'option_1' => ['Option 1'], + 'option_2' => ['Option 2'], + 'option_3' => ['Option 3'], + 'option_4' => ['Option 4 "!@#$%^&*'] + ], + 'order' => [ + 'option_1' => 1, + 'option_2' => 2, + 'option_3' => 3, + 'option_4' => 4, + ], + ], + ] + ); + $attributeMultiselect->save(); + + /* Assign attribute to attribute set */ + $installer->addAttributeToGroup('catalog_product', 'Default', 'General', $attributeMultiselect->getId()); +} + + +if (!$attributeMultiselectText->loadByCode($entityType, 'multiselect_attribute_text')->getAttributeId()) { + $attributeMultiselectText->setData( + [ + 'attribute_code' => 'multiselect_attribute_text', + 'entity_type_id' => $entityType, + 'is_global' => 1, + 'is_user_defined' => 1, 'frontend_input' => 'multiselect', 'is_unique' => 0, @@ -51,8 +103,8 @@ ], ] ); - $attribute->save(); + $attributeMultiselectText->save(); /* Assign attribute to attribute set */ - $installer->addAttributeToGroup('catalog_product', 'Default', 'General', $attribute->getId()); + $installer->addAttributeToGroup('catalog_product', 'Default', 'General', $attributeMultiselectText->getId()); } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_rollback.php index 9c139c0d4c62d..8ff875b334dc0 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute_rollback.php @@ -14,5 +14,8 @@ $attribute->load('multiselect_attribute', 'attribute_code'); $attribute->delete(); +$attribute->load('multiselect_attribute_text', 'attribute_code'); +$attribute->delete(); + $registry->unregister('isSecureArea'); $registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute.php index 9904f18edfbc4..8ab595809d338 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute.php @@ -12,23 +12,35 @@ /** Create product with options and multiselect attribute */ $eavConfig = Bootstrap::getObjectManager()->get(Config::class); -$attribute = $eavConfig->getAttribute(Product::ENTITY, 'multiselect_attribute'); +$multiSelectAttribute = $eavConfig->getAttribute(Product::ENTITY, 'multiselect_attribute'); +$multiSelectAttributeText = $eavConfig->getAttribute(Product::ENTITY, 'multiselect_attribute_text'); + /** @var $installer \Magento\Catalog\Setup\CategorySetup */ $installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Catalog\Setup\CategorySetup::class ); -/** @var $options \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection */ -$options = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( +/** @var $multiSelectAttributeOptions \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection */ +$multiSelectAttributeOptions = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class ); -$options->setAttributeFilter($attribute->getId()); -$optionIds = $options->getAllIds(); + +$multiSelectAttributeOptions->setAttributeFilter($multiSelectAttribute->getId()); +$multiSelectAttributeOptionsIds = $multiSelectAttributeOptions->getAllIds(); + + +/** @var $multiSelectAttributeOptionsText \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection */ +$multiSelectAttributeOptionsText = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class +); + +$multiSelectAttributeOptionsText->setAttributeFilter($multiSelectAttributeText->getId()); +$multiSelectAttributeOptionsTextIds = $multiSelectAttributeOptionsText->getAllIds(); /** @var $product \Magento\Catalog\Model\Product */ $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) - ->setId($optionIds[0] * 10) + ->setId($multiSelectAttributeOptionsIds[0] * 10) ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) ->setWebsiteIds([1]) ->setName('With Multiselect 1') @@ -36,35 +48,35 @@ ->setPrice(10) ->setDescription('Hello " &" Bring the water bottle when you can!') ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) - ->setMultiselectAttribute([$optionIds[0]]) + ->setMultiselectAttribute([$multiSelectAttributeOptionsIds[0]]) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->save(); $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) - ->setId($optionIds[1] * 10) + ->setId($multiSelectAttributeOptionsIds[1] * 10) ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) ->setWebsiteIds([1]) ->setName('With Multiselect 2') ->setSku('simple_ms_2') ->setPrice(10) ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) - ->setMultiselectAttribute([$optionIds[1], $optionIds[2], $optionIds[3]]) + ->setMultiselectAttribute([$multiSelectAttributeOptionsIds[1], $multiSelectAttributeOptionsIds[2], $multiSelectAttributeOptionsIds[3]]) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->save(); $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) - ->setId($optionIds[2] * 10) + ->setId($multiSelectAttributeOptionsTextIds[2] * 10) ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) ->setWebsiteIds([1]) - ->setName('With Multiselect 3') + ->setName('With Multiselect Text') ->setSku('simple_ms_3') ->setPrice(10) ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) - ->setMultiselectAttribute(array_values($optionIds)) + ->setMultiselectAttributeText(array_values($multiSelectAttributeOptionsTextIds)) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->save(); From c78a5a6b02631736b6ef655d0c0c585292adc170 Mon Sep 17 00:00:00 2001 From: Jakub Winkler Date: Sat, 18 Sep 2021 19:04:43 +0200 Subject: [PATCH 13/87] #33486 - final test fix, fixing typo in SourceTest.php file --- .../Model/ResourceModel/Entity/Attribute.php | 2 +- .../Product/Indexer/Eav/SourceTest.php | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php index 29cad62bf0ca4..7e702baf39b9a 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php @@ -523,7 +523,7 @@ private function clearSelectedOptionInEntities(AbstractModel $object, int $optio $where = $connection->quoteInto('attribute_id = ?', $attributeId); $update = []; - if ($object->getBackendType() === 'varchar') { + if ($object->getBackendType() === 'text') { $where.= ' AND ' . $connection->prepareSqlCondition('value', ['finset' => $optionId]); $concat = $connection->getConcatSql(["','", 'value', "','"]); $expr = $connection->quoteInto( diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php index b9460d950338c..22579a28f3398 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php @@ -69,7 +69,7 @@ public function testReindexEntitiesForConfigurableProduct() /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attr **/ $attr = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class) - ->getAttribute('catalog_product', 'test_configurable'); + ->getAttribute('catalog_product', 'test_configurable'); $attr->setIsFilterable(1)->save(); $this->_eavIndexerProcessor->reindexAll(); @@ -133,28 +133,31 @@ public function testReindexMultiselectAttribute() /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attr **/ $attr = $objectManager->get(\Magento\Eav\Model\Config::class) - ->getAttribute('catalog_product', 'multiselect_attribute'); + ->getAttribute('catalog_product', 'multiselect_attribute'); /** @var $options \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection */ $options = $objectManager->create(\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class); $options->setAttributeFilter($attr->getId()); + $optionIds = $options->getAllIds(); + $product1Id = $optionIds[0] * 10; + $product2Id = $optionIds[1] * 10; /** @var \Magento\Catalog\Model\Product $product1 **/ - $product1 = $productRepository->get('simple_ms_1'); + $product1 = $productRepository->getById($product1Id); $product1->setSpecialFromDate(date('Y-m-d H:i:s')); $product1->setNewsFromDate(date('Y-m-d H:i:s')); $productRepository->save($product1); /** @var \Magento\Catalog\Model\Product $product2 **/ - $product2 = $productRepository->get('simple_ms_2'); - $product2->setSpecialFromDate(date('Y-m-d H:i:s')); - $product2->setNewsFromDate(date('Y-m-d H:i:s')); + $product2 = $productRepository->getById($product2Id); + $product1->setSpecialFromDate(date('Y-m-d H:i:s')); + $product1->setNewsFromDate(date('Y-m-d H:i:s')); $productRepository->save($product2); $this->_eavIndexerProcessor->reindexAll(); $connection = $this->productResource->getConnection(); $select = $connection->select()->from($this->productResource->getTable('catalog_product_index_eav')) - ->where('entity_id in (?)', [$product1->getId(), $product2->getId()]) + ->where('entity_id in (?)', [$product1Id, $product2Id]) ->where('attribute_id = ?', $attr->getId()); $result = $connection->fetchAll($select); @@ -210,13 +213,12 @@ public function testReindexMultiselectAttributeWithSourceModel() /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attr **/ $attr = $objectManager->get(\Magento\Eav\Model\Config::class) - ->getAttribute('catalog_product', 'multiselect_attr_with_source'); + ->getAttribute('catalog_product', 'multiselect_attr_with_source'); /** @var $sourceModel MultiselectSourceMock */ $sourceModel = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( MultiselectSourceMock::class ); - $options = $sourceModel->getAllOptions(); $product1Id = $options[0]['value'] * 10; $product2Id = $options[1]['value'] * 10; From b638595533ab01757d407da1716098f12a4e51cf Mon Sep 17 00:00:00 2001 From: Jakub Winkler Date: Sun, 19 Sep 2021 12:10:32 +0200 Subject: [PATCH 14/87] #33486 - static file testing fix --- ...pdateMultiselectAttributesBackendTypes.php | 7 +- .../Catalog/Model/ProductGettersTest.php | 3 - .../Product/Indexer/Eav/SourceTest.php | 5 - .../products_with_multiselect_attribute.php | 4 +- .../Magento/Test/Local/LiveCodeTest.php | 506 ++++++++++++++++++ .../PHPCompatibilityMagento/ruleset.xml | 13 + .../Local/_files/blacklist/strict_type.txt | 1 + .../Local/_files/phpcpd/blacklist/common.txt | 111 ++++ .../_files/phpcpd/blacklist/inventory.txt | 11 + .../Test/Local/_files/phpmd/ruleset.xml | 49 ++ .../Local/_files/phpstan/blacklist/common.txt | 23 + .../Test/Local/_files/phpstan/phpstan.neon | 49 ++ .../Test/Local/_files/whitelist/common.txt | 2 + 13 files changed, 771 insertions(+), 13 deletions(-) create mode 100644 dev/tests/static/testsuite/Magento/Test/Local/LiveCodeTest.php create mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/PHPCompatibilityMagento/ruleset.xml create mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/blacklist/strict_type.txt create mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/common.txt create mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/inventory.txt create mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/phpmd/ruleset.xml create mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/blacklist/common.txt create mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/phpstan.neon create mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/whitelist/common.txt diff --git a/app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php b/app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php index 06ffb40a7a1f2..fc83d7b9716a8 100644 --- a/app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php +++ b/app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php @@ -38,7 +38,7 @@ public function __construct( } /** - * @return array + * @inheritdoc */ public static function getDependencies() { @@ -46,7 +46,7 @@ public static function getDependencies() } /** - * @return array + * @inheritdoc */ public function getAliases() { @@ -54,8 +54,7 @@ public function getAliases() } /** - * @return UpdateMultiselectAttributesBackendTypes - * @throws LocalizedException + * @inheritdoc */ public function apply() { diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductGettersTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductGettersTest.php index 9996ac4836831..76b90ff8fe732 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductGettersTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductGettersTest.php @@ -207,7 +207,6 @@ public function testGetAttributeText() $this->assertEquals('Enabled', $this->_model->getAttributeText('status')); } - /** * @magentoDataFixture Magento/Catalog/_files/products_with_multiselect_attribute.php */ @@ -226,7 +225,6 @@ public function testGetAttributeTextArray() ); } - /** * @magentoDataFixture Magento/Catalog/_files/products_with_multiselect_attribute.php */ @@ -246,7 +244,6 @@ public function testMultipleMultiselectTextValues() ); } - public function testGetCustomDesignDate() { $this->assertEquals(['from' => null, 'to' => null], $this->_model->getCustomDesignDate()); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php index 22579a28f3398..7e7b311625df2 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php @@ -14,11 +14,6 @@ use Magento\Store\Model\StoreManagerInterface; use Magento\Store\Api\Data\StoreInterface; -/** - * Class SourceTest - * @magentoAppIsolation enabled - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ class SourceTest extends \PHPUnit\Framework\TestCase { /** diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute.php index 8ab595809d338..b6d96f4d114c1 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute.php @@ -62,7 +62,9 @@ ->setSku('simple_ms_2') ->setPrice(10) ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) - ->setMultiselectAttribute([$multiSelectAttributeOptionsIds[1], $multiSelectAttributeOptionsIds[2], $multiSelectAttributeOptionsIds[3]]) + ->setMultiselectAttribute( + [$multiSelectAttributeOptionsIds[1], $multiSelectAttributeOptionsIds[2], $multiSelectAttributeOptionsIds[3]] + ) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->save(); diff --git a/dev/tests/static/testsuite/Magento/Test/Local/LiveCodeTest.php b/dev/tests/static/testsuite/Magento/Test/Local/LiveCodeTest.php new file mode 100644 index 0000000000000..2df2b27c24fe3 --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Local/LiveCodeTest.php @@ -0,0 +1,506 @@ +readLists($globPatternsFolder . $whitelistFile); + } catch (\Exception $e) { + // no directories matched white list + return []; + } + $targetFiles = self::filterFiles($changedFiles, $fileTypes, $directoriesToCheck); + return $targetFiles; + } + + /** + * This method loads list of changed files. + * + * List may be generated by: + * - dev/tests/static/get_github_changes.php utility (allow to generate diffs between branches), + * - CLI command "git diff --name-only > dev/tests/static/testsuite/Magento/Test/_files/changed_files_local.txt", + * + * If no generated changed files list found "git diff" will be used to find not committed changed + * (tests should be invoked from target gir repo). + * + * Note: "static" modifier used for compatibility with legacy implementation of self::getWhitelist method + * + * @param string $changedFilesBaseDir Base dir with previously generated list files + * @return string[] List of changed files + */ + private static function getChangedFilesList($changedFilesBaseDir) + { + return FilesSearch::getFilesFromListFile( + $changedFilesBaseDir ?: self::getChangedFilesBaseDir(), + 'changed_files*', + function () { + // if no list files, probably, this is the dev environment + // phpcs:disable Generic.PHP.NoSilencedErrors,Magento2.Security.InsecureFunction + @exec('git diff --name-only', $changedFiles); + @exec('git diff --cached --name-only', $addedFiles); + // phpcs:enable + $changedFiles = array_unique(array_merge($changedFiles, $addedFiles)); + return $changedFiles; + } + ); + } + + /** + * Filter list of files. + * + * File removed from list: + * - if it not exists, + * - if allowed types are specified and file has another type (extension), + * - if allowed directories specified and file not located in one of them. + * + * Note: "static" modifier used for compatibility with legacy implementation of self::getWhitelist method + * + * @param string[] $files List of file paths to filter + * @param string[] $allowedFileTypes List of allowed file extensions (pass empty array to allow all) + * @param string[] $allowedDirectories List of allowed directories (pass empty array to allow all) + * @return string[] Filtered file paths + */ + private static function filterFiles(array $files, array $allowedFileTypes, array $allowedDirectories) + { + if (empty($allowedFileTypes)) { + $fileHasAllowedType = function () { + return true; + }; + } else { + $fileHasAllowedType = function ($file) use ($allowedFileTypes) { + return in_array(pathinfo($file, PATHINFO_EXTENSION), $allowedFileTypes); + }; + } + + if (empty($allowedDirectories)) { + $fileIsInAllowedDirectory = function () { + return true; + }; + } else { + $allowedDirectories = array_map('realpath', $allowedDirectories); + usort( + $allowedDirectories, + function ($dir1, $dir2) { + return strlen($dir1) - strlen($dir2); + } + ); + $fileIsInAllowedDirectory = function ($file) use ($allowedDirectories) { + foreach ($allowedDirectories as $directory) { + if (strpos($file, $directory) === 0) { + return true; + } + } + return false; + }; + } + + $filtered = array_filter( + $files, + function ($file) use ($fileHasAllowedType, $fileIsInAllowedDirectory) { + $file = realpath($file); + if (false === $file) { + return false; + } + return $fileHasAllowedType($file) && $fileIsInAllowedDirectory($file); + } + ); + + return $filtered; + } + + /** + * Retrieves full list of codebase paths without any files/folders filtered out + * + * @return array + */ + private function getFullWhitelist() + { + try { + return Files::init()->readLists(__DIR__ . '/_files/whitelist/common.txt'); + } catch (\Exception $e) { + // nothing is whitelisted + return []; + } + } + + /** + * Retrieves the lowest and highest PHP version specified in composer.json of project. + * + * @return array + */ + private function getTargetPhpVersions(): array + { + $composerJson = json_decode(file_get_contents(BP . '/composer.json'), true); + $versionsRange = []; + + if (isset($composerJson['require']['php'])) { + $versions = explode('||', $composerJson['require']['php']); + + //normalize version constraints + foreach ($versions as $key => $version) { + $version = ltrim($version, '^~'); + $version = str_replace('*', '999', $version); + + $versions[$key] = $version; + } + + //sort versions + usort($versions, 'version_compare'); + + $versionsRange[] = array_shift($versions); + if (!empty($versions)) { + $versionsRange[] = array_pop($versions); + } + foreach ($versionsRange as $key => $version) { + $versionParts = explode('.', $versionsRange[$key]); + $versionsRange[$key] = sprintf('%s.%s', $versionParts[0], $versionParts[1] ?? '0'); + } + } + + return $versionsRange; + } + + /** + * Returns whether a full scan was requested. + * + * This can be set in the `phpunit.xml` used to run these test cases, by setting the constant + * `TESTCODESTYLE_IS_FULL_SCAN` to `1`, e.g.: + * ```xml + * + * + * + * + * ``` + * + * @return bool + */ + private function isFullScan(): bool + { + return defined('TESTCODESTYLE_IS_FULL_SCAN') && TESTCODESTYLE_IS_FULL_SCAN === '1'; + } + + /** + * Test code quality using phpcs + */ + public function testCodeStyle() + { + $reportFile = self::$reportDir . '/phpcs_report.txt'; + if (!file_exists($reportFile)) { + touch($reportFile); + } + $codeSniffer = new CodeSniffer('Magento', $reportFile, new Wrapper()); + $fileList = $this->isFullScan() ? $this->getFullWhitelist() : self::getWhitelist(['php', 'phtml']); + $ignoreList = Files::init()->readLists(__DIR__ . '/_files/phpcs/ignorelist/*.txt'); + if ($ignoreList) { + $ignoreListPattern = sprintf('#(%s)#i', implode('|', $ignoreList)); + $fileList = array_filter( + $fileList, + function ($path) use ($ignoreListPattern) { + return !preg_match($ignoreListPattern, $path); + } + ); + } + + $result = $codeSniffer->run($fileList); + $report = file_get_contents($reportFile); + $this->assertEquals( + 0, + $result, + "PHP Code Sniffer detected {$result} violation(s): " . PHP_EOL . $report + ); + } + + /** + * Test code quality using phpmd + */ + public function testCodeMess() + { + $reportFile = self::$reportDir . '/phpmd_report.txt'; + $codeMessDetector = new CodeMessDetector(realpath(__DIR__ . '/_files/phpmd/ruleset.xml'), $reportFile); + + if (!$codeMessDetector->canRun()) { + $this->markTestSkipped('PHP Mess Detector is not available.'); + } + $fileList = self::getWhitelist(['php']); + $ignoreList = Files::init()->readLists(__DIR__ . '/_files/phpmd/ignorelist/*.txt'); + if ($ignoreList) { + $ignoreListPattern = sprintf('#(%s)#i', implode('|', $ignoreList)); + $fileList = array_filter( + $fileList, + function ($path) use ($ignoreListPattern) { + return !preg_match($ignoreListPattern, $path); + } + ); + } + + $result = $codeMessDetector->run($fileList); + + $output = ""; + if (file_exists($reportFile)) { + $output = file_get_contents($reportFile); + } + + $this->assertEquals( + Command::EXIT_SUCCESS, + $result, + "PHP Code Mess has found error(s):" . PHP_EOL . $output + ); + + // delete empty reports + if (file_exists($reportFile)) { + unlink($reportFile); + } + } + + /** + * Test code quality using phpcpd + */ + public function testCopyPaste() + { + $reportFile = self::$reportDir . '/phpcpd_report.xml'; + $copyPasteDetector = new CopyPasteDetector($reportFile); + + if (!$copyPasteDetector->canRun()) { + $this->markTestSkipped('PHP Copy/Paste Detector is not available.'); + } + + $blackList = []; + foreach (glob(__DIR__ . '/_files/phpcpd/blacklist/*.txt') as $list) { + $blackList[] = file($list, FILE_IGNORE_NEW_LINES); + } + $blackList = array_merge([], ...$blackList); + + $copyPasteDetector->setBlackList($blackList); + + $result = $copyPasteDetector->run([BP]); + + $output = file_exists($reportFile) ? file_get_contents($reportFile) : ''; + + $this->assertTrue( + $result, + "PHP Copy/Paste Detector has found error(s):" . PHP_EOL . $output + ); + } + + /** + * Tests whitelisted files for strict type declarations. + */ + public function testStrictTypes() + { + $changedFiles = AddedFiles::getAddedFilesList(self::getChangedFilesBaseDir()); + + try { + $blackList = Files::init()->readLists( + self::getBaseFilesFolder() . '/_files/blacklist/strict_type.txt' + ); + } catch (\Exception $e) { + // nothing matched black list + $blackList = []; + } + + $toBeTestedFiles = array_diff( + self::filterFiles($changedFiles, ['php'], []), + $blackList + ); + + $filesMissingStrictTyping = []; + foreach ($toBeTestedFiles as $fileName) { + $file = file_get_contents($fileName); + if (strstr($file, 'strict_types=1') === false) { + $filesMissingStrictTyping[] = $fileName; + } + } + + $this->assertCount( + 0, + $filesMissingStrictTyping, + "Following files are missing strict type declaration:" + . PHP_EOL + . implode(PHP_EOL, $filesMissingStrictTyping) + ); + } + + /** + * Test for compatibility to lowest PHP version declared in composer.json. + */ + public function testPhpCompatibility() + { + $targetVersions = $this->getTargetPhpVersions(); + $this->assertNotEmpty($targetVersions, 'No supported versions information in composer.json'); + $reportFile = self::$reportDir . '/phpcompatibility_report.txt'; + $rulesetDir = __DIR__ . '/_files/PHPCompatibilityMagento'; + + if (!file_exists($reportFile)) { + touch($reportFile); + } + + $codeSniffer = new PhpCompatibility($rulesetDir, $reportFile, new Wrapper()); + if (count($targetVersions) > 1) { + $codeSniffer->setTestVersion($targetVersions[0] . '-' . $targetVersions[1]); + } else { + $codeSniffer->setTestVersion($targetVersions[0]); + } + + $result = $codeSniffer->run( + $this->isFullScan() ? $this->getFullWhitelist() : self::getWhitelist(['php', 'phtml']) + ); + $report = file_get_contents($reportFile); + + $this->assertEquals( + 0, + $result, + 'PHP Compatibility detected violation(s):' . PHP_EOL . $report + ); + } + + /** + * Test code quality using PHPStan + * + * @throws \Exception + */ + public function testPhpStan() + { + $reportFile = self::$reportDir . '/phpstan_report.txt'; + $confFile = __DIR__ . '/_files/phpstan/phpstan.neon'; + + if (!file_exists($reportFile)) { + touch($reportFile); + } + + $fileList = self::getWhitelist(['php']); + $blackList = Files::init()->readLists(__DIR__ . '/_files/phpstan/blacklist/*.txt'); + if ($blackList) { + $blackListPattern = sprintf('#(%s)#i', implode('|', $blackList)); + $fileList = array_filter( + $fileList, + function ($path) use ($blackListPattern) { + return !preg_match($blackListPattern, $path); + } + ); + } + + $phpStan = new PhpStan($confFile, $reportFile); + $exitCode = $phpStan->run($fileList); + $report = file_get_contents($reportFile); + + $errorMessage = empty($report) ? + 'PHPStan command run failed.' : 'PHPStan detected violation(s):' . PHP_EOL . $report; + $this->assertEquals(0, $exitCode, $errorMessage); + } + + /** + * Tests whitelisted fixtures for reuse other fixtures. + */ + public function testFixtureReuse() + { + $changedFiles = self::getWhitelist(['php']); + $toBeTestedFiles = self::filterFiles($changedFiles, ['php'], []); + + $filesWithIncorrectReuse = []; + foreach ($toBeTestedFiles as $fileName) { + //check only _files and Fixtures directory + if (!preg_match('/integration.+\/(_files|Fixtures)/', $fileName)) { + continue; + } + $file = str_replace(["\n", "\r"], '', file_get_contents($fileName)); + if (preg_match('/(?assertEquals( + 0, + count($filesWithIncorrectReuse), + "The following files incorrectly reuse fixtures:" + . PHP_EOL + . implode(PHP_EOL, $filesWithIncorrectReuse) + . PHP_EOL + . 'Please use Magento\TestFramework\Workaround\Override\Fixture\Resolver::requireDataFixture' + ); + } +} diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/PHPCompatibilityMagento/ruleset.xml b/dev/tests/static/testsuite/Magento/Test/Local/_files/PHPCompatibilityMagento/ruleset.xml new file mode 100644 index 0000000000000..579a6d2416f20 --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Local/_files/PHPCompatibilityMagento/ruleset.xml @@ -0,0 +1,13 @@ + + + + Magento 2 specific ruleset which checks for PHP cross version compatibility. + + + + diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/blacklist/strict_type.txt b/dev/tests/static/testsuite/Magento/Test/Local/_files/blacklist/strict_type.txt new file mode 100644 index 0000000000000..877583e5b6a29 --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Local/_files/blacklist/strict_type.txt @@ -0,0 +1 @@ +# Format: or simply diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/common.txt new file mode 100644 index 0000000000000..efc7e669b3605 --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/common.txt @@ -0,0 +1,111 @@ +app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php +app/code/Magento/Backend/Block/Dashboard/Tab/Customers/Most.php +app/code/Magento/Backend/Test/Unit/Model/_files/ +app/code/Magento/Bundle +app/code/Magento/Catalog/Model/Product/Visibility.php +app/code/Magento/Catalog/Model/Product/Attribute/Source/Status.php +app/code/Magento/Catalog/Model/Category.php +app/code/Magento/Catalog/Model/ResourceModel/Product/Link/SaveHandler.php +dev +lib/internal/Magento/Framework/DB/Select/RendererProxy.php +lib/internal/Magento/Framework/DB/Query/Generator.php +app/code/Magento/Paypal/Model/Method/Agreement.php +app/code/Magento/Paypal/Test/Unit/Model/_files/additional_info_data.php +app/code/Magento/Downloadable/Model/Link.php +app/code/Magento/Downloadable/Model/Sales/Order/Pdf/Items/Creditmemo.php +app/code/Magento/Downloadable/Model/ResourceModel/Link/Collection.php +app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Links.php +app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Crosssell.php +app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Related.php +app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Upsell.php +app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php +app/code/Magento/Catalog/Block/Adminhtml/Category/Helper/Sortby/Available.php +app/code/Magento/Catalog/Model/Layer/Filter/Dynamic/Auto.php +app/code/Magento/Sales/Model/Order/Pdf/ +app/code/Magento/Sales/Model/Order/CreditmemoRepository.php +app/code/Magento/Sales/Model/Order/Email/Container/ +app/code/Magento/Sales/Model/Order/Invoice.php +app/code/Magento/Sales/Model/Order/Invoice/Item.php +app/code/Magento/Sales/Model/Order/Creditmemo/Item.php +app/code/Magento/Sales/Model/Order/ShipmentRepository.php +app/code/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Relation/Refund.php +app/code/Magento/Sales/Model/Order/Shipment/Sender/EmailSender.php +app/code/Magento/Sales/Model/Order/Invoice/Sender/EmailSender.php +app/code/Magento/Sales/Model/Order/Email/Sender/CreditmemoSender.php +app/code/Magento/Cms/Model/ResourceModel/Page/Grid/Collection.php +app/code/Magento/Shipping/Block/Order/Shipment.php +app/code/Magento/Sales/Block/Order/Creditmemo.php +app/code/Magento/Sales/Block/Order/Invoice.php +app/code/Magento/Sales/Block/Order/PrintOrder/Creditmemo.php +app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/NewAction.php +app/code/Magento/AdvancedCheckout/Controller/Adminhtml/Index/ConfigureQuoteItems.php +app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php +app/code/Magento/Cron/Model/Config/Backend/Product/Alert.php +app/code/Magento/Customer/Controller/Adminhtml/Address/Viewfile.php +app/code/Magento/Customer/Model/Metadata/Form/AbstractData.php +app/code/Magento/GiftMessage/Block/Adminhtml/Sales/Order/View/Items.php +app/code/Magento/ImportExport/Model/Export/Entity/AbstractEntity.php +app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php +app/code/Magento/Newsletter/Block/Adminhtml/Queue/Preview/Form.php +app/code/Magento/ProductAlert/Model/Observer.php +app/code/Magento/Reports/Block/Adminhtml/Sales/Invoiced/Grid.php +app/code/Magento/Reports/Model/ResourceModel/Customer/Totals/Collection.php +app/code/Magento/Review/Block/Customer/View.php +app/code/Magento/Rule/Test/Mftf/Helper/RuleHelper.php +app/code/Magento/Sales/Block/Adminhtml/Order/Status/NewStatus/Form.php +app/code/Magento/Sales/Model/ResourceModel/Collection/AbstractCollection.php +app/code/Magento/Sales/Model/ResourceModel/Report/ +app/code/Magento/Search/Block/Adminhtml/Dashboard/Last.php +app/code/Magento/Shipping/Model/Carrier/AbstractCarrierOnline.php +app/code/Magento/CatalogRule/Model/ResourceModel/SaveHandler.php +lib/internal/Magento/Framework/HTTP/Client/Socket.php +lib/internal/Magento/Framework/App/Config/Scope/Validator.php +app/code/Magento/Cron/Model/Schedule.php +generated/code +lib/internal/Magento/Framework/Filesystem/Driver/File/Mime.php +app/code/Magento/Shipping/Model/Rate/PackageResult.php +app/code/Magento/Ui/DataProvider/AbstractDataProvider.php +app/code/Magento/Ui/Config/Converter/Url.php +app/code/Magento/Ui/Config/Converter/Buttons.php +app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php +app/code/Magento/Sales/Api/Data/CreditmemoItemInterface.php +vendor +lib/internal/Magento/Framework/DataObject/Copy/Config/Data/Proxy.php +app/code/Magento/Catalog/Setup/Patch/Data/UpgradeWebsiteAttributes.php +app/code/Magento/CatalogUrlRewrite/Setup/Recurring.php +app/code/Magento/Checkout/Setup/Patch/Data/PrepareInitialCheckoutConfiguration.php +app/code/Magento/ConfigurableProduct/Setup/Recurring.php +app/code/Magento/Sales/Setup/SalesSetup.php +app/code/Magento/Weee/Setup/Recurring.php +app/code/Magento/Wishlist/Setup/Recurring.php +setup/src/Magento/Setup/Fixtures +app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Rows.php +app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php +app/code/Magento/Catalog/Model/Attribute/Backend/ConsumerWebsiteAssign.php +app/code/Magento/Catalog/Ui/Component/Listing/ +app/code/Magento/Payment/Gateway/Data/Order/AddressAdapter.php +app/code/Magento/SalesRule/Model/CouponRepository.php +app/code/Magento/SalesRule/Test/Unit/Model/Rule/Metadata/_files/MetaData.php +app/code/Magento/Tax/Model/Calculation/UnitBaseCalculator.php +app/code/Magento/Customer/Block/Widget/ +app/code/Magento/Persistent/Observer/EmulateCustomerObserver.php +app/code/Magento/Eav/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php +lib/internal/Magento/Framework/App/AreaList/Proxy.php +lib/internal/Magento/Framework/App/Route/ConfigInterface/Proxy.php +lib/internal/Magento/Framework/Backup/Filesystem/Iterator/Filter.php +app/code/Magento/Theme/Model/Indexer/Design/Config.php +lib/internal/Magento/Framework/Mview/Config/Data/Proxy.php +lib/internal/Magento/Framework/View/File/Collector/Override +lib/internal/Magento/Framework/MessageQueue/Consumer/Config/ConsumerConfigItem/ +lib/internal/Magento/Framework/MessageQueue/Publisher/Config/PublisherConfigItem/Iterator.php +lib/internal/Magento/Framework/MessageQueue/Topology/Config/ExchangeConfigItem/Iterator.php +app/code/Magento/Integration/Model/IntegrationConfig.php +*Test.php +Test/_files +Test/Unit/_files +Test/Integration/_files +app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php +app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/FieldProvider/FieldType/Resolver/CompositeResolver.php +app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php +app/code/Magento/Newsletter/Model/Queue/TransportBuilder.php +app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/bulk.phtml diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/inventory.txt b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/inventory.txt new file mode 100644 index 0000000000000..0a12e975e097e --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/inventory.txt @@ -0,0 +1,11 @@ +app/code/Magento/InventoryBundleProductIndexer/Indexer/SelectBuilder.php +app/code/Magento/InventoryConfigurableProductIndexer/Indexer/SelectBuilder.php +app/code/Magento/InventoryCatalogAdminUi/Controller/Adminhtml/Source/BulkAssignPost.php +app/code/Magento/InventoryBundleProductIndexer/Indexer/SourceItem/SourceItemIndexer.php +app/code/Magento/InventoryShippingAdminUi/Ui/DataProvider/GetSourcesByOrderIdSkuAndQty.php +app/code/Magento/InventoryBundleProductIndexer/Indexer/SourceItem/SiblingSkuListInStockProvider.php +app/code/Magento/InventoryInStorePickupWebapiExtension/Model/Rest/Swagger/Generator.php +app/code/Magento/InventoryAdminUi/Controller/Adminhtml/Source/MassDisable.php +app/code/Magento/InventoryBundleProductIndexer/Indexer/StockIndexer.php +app/code/Magento/InventoryBundleProductIndexer/Indexer/SelectBuilder.php +app/code/Magento/InventoryConfigurableProductIndexer/Indexer/SourceItem/SiblingSkuListInStockProvider.php diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpmd/ruleset.xml b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpmd/ruleset.xml new file mode 100644 index 0000000000000..0e3b5fa3d341c --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpmd/ruleset.xml @@ -0,0 +1,49 @@ + + + + Magento Code Check Rules + ../../../../../../framework + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/blacklist/common.txt new file mode 100644 index 0000000000000..6b0007f58830b --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/blacklist/common.txt @@ -0,0 +1,23 @@ +# Format: path to directory or path to file +# +# Example: +# app/code/Magento/Catalog +# dev/tests/static/framework/bootstrap.php +lib/internal/Magento/Framework/Cache/Backend/Eaccelerator.php +lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php +lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/InterceptorTest.php +lib/internal/Magento/Framework/Interception/Test/Unit/Config/ConfigTest.php +dev/tests/integration/framework/deployTestModules.php +dev/tests/integration/testsuite/Magento/Framework/Code/Generator/AutoloaderTest.php +dev/tests/integration/testsuite/Magento/Framework/Communication/ConfigTest.php +dev/tests/integration/testsuite/Magento/Framework/Filter/DirectiveProcessor/SimpleDirectiveTest.php +dev/tests/integration/testsuite/Magento/Framework/Session/ConfigTest.php +dev/tests/integration/testsuite/Magento/Framework/Session/SessionManagerTest.php +dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php +dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/CategoryTest.php +dev/tests/api-functional/testsuite/Magento/Customer/Api/AddressRepositoryTest.php +dev/tests/api-functional/testsuite/Magento/Framework/Model/Entity/HydratorTest.php +dev/tests/api-functional/testsuite/Magento/Integration/Model/AdminTokenServiceTest.php +dev/tests/api-functional/testsuite/Magento/Integration/Model/CustomerTokenServiceTest.php +app/code/Magento/Developer/Test/Unit/Console/Command/DevTestsRunCommandTest.php +app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/ColumnResolverTest.php diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/phpstan.neon b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/phpstan.neon new file mode 100644 index 0000000000000..aba7d0b46d297 --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/phpstan.neon @@ -0,0 +1,49 @@ +parameters: + checkExplicitMixedMissingReturn: true + checkPhpDocMissingReturn: true + reportUnmatchedIgnoredErrors: false + excludes_analyse: + - %rootDir%/../../../lib/internal/Magento/Framework/ObjectManager/Test/Unit/* + - %rootDir%/../../../*/_files/* + - %rootDir%/../../../dev/tests/*/Fixtures/* + - %rootDir%/../../../dev/tests/*/tmp/* + - %rootDir%/../../../dev/tests/*/_generated/* + - %rootDir%/../../../pub/* + scanDirectories: + - %rootDir%/../../../dev/tests/static/framework/tests/unit/testsuite/Magento + - %rootDir%/../../../dev/tests/integration/framework/tests/unit/testsuite/Magento + - %rootDir%/../../../dev/tests/api-functional/_files/Magento + bootstrapFiles: + - %rootDir%/../../../dev/tests/static/framework/autoload.php + - %rootDir%/../../../dev/tests/integration/framework/autoload.php + - %rootDir%/../../../dev/tests/api-functional/framework/autoload.php + - %rootDir%/../../../dev/tests/setup-integration/framework/autoload.php + - %rootDir%/../../../dev/tests/static/framework/Magento/PhpStan/autoload.php + ignoreErrors: + # Ignore PHPStan\Rules\Classes\UnusedConstructorParametersRule + - '#Constructor of class [a-zA-Z0-9\\_]+ has an unused parameter#' + # Ignore setCustomErrorHandler function not found in bootstrap files + - '#Function setCustomErrorHandler not found#' + # Ignore 'return statement is missing' error when 'void' is present in return type list + - '#Method (?:.*?) should return (?:.*?)void(?:.*?) but return statement is missing.#' + # Ignore constants, defined dynamically. + - '#Constant TESTS_WEB_API_ADAPTER not found.#' + - '#Constant TESTS_BASE_URL not found.#' + - '#Constant TESTS_XDEBUG_ENABLED not found.#' + - '#Constant TESTS_XDEBUG_SESSION not found.#' + - '#Constant INTEGRATION_TESTS_DIR not found.#' + - '#Constant MAGENTO_MODULES_PATH not found.#' + - '#Constant TESTS_MODULES_PATH not found.#' + - '#Constant TESTS_INSTALLATION_DB_CONFIG_FILE not found.#' + - '#Constant T_[A-Z_]+ not found.#' + + +services: + - + class: Magento\PhpStan\Reflection\Php\DataObjectClassReflectionExtension + tags: {phpstan.broker.methodsClassReflectionExtension: {priority: 100}} + + errorFormatter.filtered: + class: Magento\PhpStan\Formatters\FilteredErrorFormatter + arguments: + tableErrorFormatter: @errorFormatter.table diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/whitelist/common.txt b/dev/tests/static/testsuite/Magento/Test/Local/_files/whitelist/common.txt new file mode 100644 index 0000000000000..ffb7d924f9d5a --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Local/_files/whitelist/common.txt @@ -0,0 +1,2 @@ +# Format: or simply +app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php From 36c2c8a5a7bc2fc029dc82bcc0fc771a0b7c0dd9 Mon Sep 17 00:00:00 2001 From: Jakub Winkler Date: Sun, 19 Sep 2021 12:11:40 +0200 Subject: [PATCH 15/87] #33486 - static file testing fix - removing local tests --- .../Magento/Test/Local/LiveCodeTest.php | 506 ------------------ .../PHPCompatibilityMagento/ruleset.xml | 13 - .../Local/_files/blacklist/strict_type.txt | 1 - .../Local/_files/phpcpd/blacklist/common.txt | 111 ---- .../_files/phpcpd/blacklist/inventory.txt | 11 - .../Test/Local/_files/phpmd/ruleset.xml | 49 -- .../Local/_files/phpstan/blacklist/common.txt | 23 - .../Test/Local/_files/phpstan/phpstan.neon | 49 -- .../Test/Local/_files/whitelist/common.txt | 2 - 9 files changed, 765 deletions(-) delete mode 100644 dev/tests/static/testsuite/Magento/Test/Local/LiveCodeTest.php delete mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/PHPCompatibilityMagento/ruleset.xml delete mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/blacklist/strict_type.txt delete mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/common.txt delete mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/inventory.txt delete mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/phpmd/ruleset.xml delete mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/blacklist/common.txt delete mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/phpstan.neon delete mode 100644 dev/tests/static/testsuite/Magento/Test/Local/_files/whitelist/common.txt diff --git a/dev/tests/static/testsuite/Magento/Test/Local/LiveCodeTest.php b/dev/tests/static/testsuite/Magento/Test/Local/LiveCodeTest.php deleted file mode 100644 index 2df2b27c24fe3..0000000000000 --- a/dev/tests/static/testsuite/Magento/Test/Local/LiveCodeTest.php +++ /dev/null @@ -1,506 +0,0 @@ -readLists($globPatternsFolder . $whitelistFile); - } catch (\Exception $e) { - // no directories matched white list - return []; - } - $targetFiles = self::filterFiles($changedFiles, $fileTypes, $directoriesToCheck); - return $targetFiles; - } - - /** - * This method loads list of changed files. - * - * List may be generated by: - * - dev/tests/static/get_github_changes.php utility (allow to generate diffs between branches), - * - CLI command "git diff --name-only > dev/tests/static/testsuite/Magento/Test/_files/changed_files_local.txt", - * - * If no generated changed files list found "git diff" will be used to find not committed changed - * (tests should be invoked from target gir repo). - * - * Note: "static" modifier used for compatibility with legacy implementation of self::getWhitelist method - * - * @param string $changedFilesBaseDir Base dir with previously generated list files - * @return string[] List of changed files - */ - private static function getChangedFilesList($changedFilesBaseDir) - { - return FilesSearch::getFilesFromListFile( - $changedFilesBaseDir ?: self::getChangedFilesBaseDir(), - 'changed_files*', - function () { - // if no list files, probably, this is the dev environment - // phpcs:disable Generic.PHP.NoSilencedErrors,Magento2.Security.InsecureFunction - @exec('git diff --name-only', $changedFiles); - @exec('git diff --cached --name-only', $addedFiles); - // phpcs:enable - $changedFiles = array_unique(array_merge($changedFiles, $addedFiles)); - return $changedFiles; - } - ); - } - - /** - * Filter list of files. - * - * File removed from list: - * - if it not exists, - * - if allowed types are specified and file has another type (extension), - * - if allowed directories specified and file not located in one of them. - * - * Note: "static" modifier used for compatibility with legacy implementation of self::getWhitelist method - * - * @param string[] $files List of file paths to filter - * @param string[] $allowedFileTypes List of allowed file extensions (pass empty array to allow all) - * @param string[] $allowedDirectories List of allowed directories (pass empty array to allow all) - * @return string[] Filtered file paths - */ - private static function filterFiles(array $files, array $allowedFileTypes, array $allowedDirectories) - { - if (empty($allowedFileTypes)) { - $fileHasAllowedType = function () { - return true; - }; - } else { - $fileHasAllowedType = function ($file) use ($allowedFileTypes) { - return in_array(pathinfo($file, PATHINFO_EXTENSION), $allowedFileTypes); - }; - } - - if (empty($allowedDirectories)) { - $fileIsInAllowedDirectory = function () { - return true; - }; - } else { - $allowedDirectories = array_map('realpath', $allowedDirectories); - usort( - $allowedDirectories, - function ($dir1, $dir2) { - return strlen($dir1) - strlen($dir2); - } - ); - $fileIsInAllowedDirectory = function ($file) use ($allowedDirectories) { - foreach ($allowedDirectories as $directory) { - if (strpos($file, $directory) === 0) { - return true; - } - } - return false; - }; - } - - $filtered = array_filter( - $files, - function ($file) use ($fileHasAllowedType, $fileIsInAllowedDirectory) { - $file = realpath($file); - if (false === $file) { - return false; - } - return $fileHasAllowedType($file) && $fileIsInAllowedDirectory($file); - } - ); - - return $filtered; - } - - /** - * Retrieves full list of codebase paths without any files/folders filtered out - * - * @return array - */ - private function getFullWhitelist() - { - try { - return Files::init()->readLists(__DIR__ . '/_files/whitelist/common.txt'); - } catch (\Exception $e) { - // nothing is whitelisted - return []; - } - } - - /** - * Retrieves the lowest and highest PHP version specified in composer.json of project. - * - * @return array - */ - private function getTargetPhpVersions(): array - { - $composerJson = json_decode(file_get_contents(BP . '/composer.json'), true); - $versionsRange = []; - - if (isset($composerJson['require']['php'])) { - $versions = explode('||', $composerJson['require']['php']); - - //normalize version constraints - foreach ($versions as $key => $version) { - $version = ltrim($version, '^~'); - $version = str_replace('*', '999', $version); - - $versions[$key] = $version; - } - - //sort versions - usort($versions, 'version_compare'); - - $versionsRange[] = array_shift($versions); - if (!empty($versions)) { - $versionsRange[] = array_pop($versions); - } - foreach ($versionsRange as $key => $version) { - $versionParts = explode('.', $versionsRange[$key]); - $versionsRange[$key] = sprintf('%s.%s', $versionParts[0], $versionParts[1] ?? '0'); - } - } - - return $versionsRange; - } - - /** - * Returns whether a full scan was requested. - * - * This can be set in the `phpunit.xml` used to run these test cases, by setting the constant - * `TESTCODESTYLE_IS_FULL_SCAN` to `1`, e.g.: - * ```xml - * - * - * - * - * ``` - * - * @return bool - */ - private function isFullScan(): bool - { - return defined('TESTCODESTYLE_IS_FULL_SCAN') && TESTCODESTYLE_IS_FULL_SCAN === '1'; - } - - /** - * Test code quality using phpcs - */ - public function testCodeStyle() - { - $reportFile = self::$reportDir . '/phpcs_report.txt'; - if (!file_exists($reportFile)) { - touch($reportFile); - } - $codeSniffer = new CodeSniffer('Magento', $reportFile, new Wrapper()); - $fileList = $this->isFullScan() ? $this->getFullWhitelist() : self::getWhitelist(['php', 'phtml']); - $ignoreList = Files::init()->readLists(__DIR__ . '/_files/phpcs/ignorelist/*.txt'); - if ($ignoreList) { - $ignoreListPattern = sprintf('#(%s)#i', implode('|', $ignoreList)); - $fileList = array_filter( - $fileList, - function ($path) use ($ignoreListPattern) { - return !preg_match($ignoreListPattern, $path); - } - ); - } - - $result = $codeSniffer->run($fileList); - $report = file_get_contents($reportFile); - $this->assertEquals( - 0, - $result, - "PHP Code Sniffer detected {$result} violation(s): " . PHP_EOL . $report - ); - } - - /** - * Test code quality using phpmd - */ - public function testCodeMess() - { - $reportFile = self::$reportDir . '/phpmd_report.txt'; - $codeMessDetector = new CodeMessDetector(realpath(__DIR__ . '/_files/phpmd/ruleset.xml'), $reportFile); - - if (!$codeMessDetector->canRun()) { - $this->markTestSkipped('PHP Mess Detector is not available.'); - } - $fileList = self::getWhitelist(['php']); - $ignoreList = Files::init()->readLists(__DIR__ . '/_files/phpmd/ignorelist/*.txt'); - if ($ignoreList) { - $ignoreListPattern = sprintf('#(%s)#i', implode('|', $ignoreList)); - $fileList = array_filter( - $fileList, - function ($path) use ($ignoreListPattern) { - return !preg_match($ignoreListPattern, $path); - } - ); - } - - $result = $codeMessDetector->run($fileList); - - $output = ""; - if (file_exists($reportFile)) { - $output = file_get_contents($reportFile); - } - - $this->assertEquals( - Command::EXIT_SUCCESS, - $result, - "PHP Code Mess has found error(s):" . PHP_EOL . $output - ); - - // delete empty reports - if (file_exists($reportFile)) { - unlink($reportFile); - } - } - - /** - * Test code quality using phpcpd - */ - public function testCopyPaste() - { - $reportFile = self::$reportDir . '/phpcpd_report.xml'; - $copyPasteDetector = new CopyPasteDetector($reportFile); - - if (!$copyPasteDetector->canRun()) { - $this->markTestSkipped('PHP Copy/Paste Detector is not available.'); - } - - $blackList = []; - foreach (glob(__DIR__ . '/_files/phpcpd/blacklist/*.txt') as $list) { - $blackList[] = file($list, FILE_IGNORE_NEW_LINES); - } - $blackList = array_merge([], ...$blackList); - - $copyPasteDetector->setBlackList($blackList); - - $result = $copyPasteDetector->run([BP]); - - $output = file_exists($reportFile) ? file_get_contents($reportFile) : ''; - - $this->assertTrue( - $result, - "PHP Copy/Paste Detector has found error(s):" . PHP_EOL . $output - ); - } - - /** - * Tests whitelisted files for strict type declarations. - */ - public function testStrictTypes() - { - $changedFiles = AddedFiles::getAddedFilesList(self::getChangedFilesBaseDir()); - - try { - $blackList = Files::init()->readLists( - self::getBaseFilesFolder() . '/_files/blacklist/strict_type.txt' - ); - } catch (\Exception $e) { - // nothing matched black list - $blackList = []; - } - - $toBeTestedFiles = array_diff( - self::filterFiles($changedFiles, ['php'], []), - $blackList - ); - - $filesMissingStrictTyping = []; - foreach ($toBeTestedFiles as $fileName) { - $file = file_get_contents($fileName); - if (strstr($file, 'strict_types=1') === false) { - $filesMissingStrictTyping[] = $fileName; - } - } - - $this->assertCount( - 0, - $filesMissingStrictTyping, - "Following files are missing strict type declaration:" - . PHP_EOL - . implode(PHP_EOL, $filesMissingStrictTyping) - ); - } - - /** - * Test for compatibility to lowest PHP version declared in composer.json. - */ - public function testPhpCompatibility() - { - $targetVersions = $this->getTargetPhpVersions(); - $this->assertNotEmpty($targetVersions, 'No supported versions information in composer.json'); - $reportFile = self::$reportDir . '/phpcompatibility_report.txt'; - $rulesetDir = __DIR__ . '/_files/PHPCompatibilityMagento'; - - if (!file_exists($reportFile)) { - touch($reportFile); - } - - $codeSniffer = new PhpCompatibility($rulesetDir, $reportFile, new Wrapper()); - if (count($targetVersions) > 1) { - $codeSniffer->setTestVersion($targetVersions[0] . '-' . $targetVersions[1]); - } else { - $codeSniffer->setTestVersion($targetVersions[0]); - } - - $result = $codeSniffer->run( - $this->isFullScan() ? $this->getFullWhitelist() : self::getWhitelist(['php', 'phtml']) - ); - $report = file_get_contents($reportFile); - - $this->assertEquals( - 0, - $result, - 'PHP Compatibility detected violation(s):' . PHP_EOL . $report - ); - } - - /** - * Test code quality using PHPStan - * - * @throws \Exception - */ - public function testPhpStan() - { - $reportFile = self::$reportDir . '/phpstan_report.txt'; - $confFile = __DIR__ . '/_files/phpstan/phpstan.neon'; - - if (!file_exists($reportFile)) { - touch($reportFile); - } - - $fileList = self::getWhitelist(['php']); - $blackList = Files::init()->readLists(__DIR__ . '/_files/phpstan/blacklist/*.txt'); - if ($blackList) { - $blackListPattern = sprintf('#(%s)#i', implode('|', $blackList)); - $fileList = array_filter( - $fileList, - function ($path) use ($blackListPattern) { - return !preg_match($blackListPattern, $path); - } - ); - } - - $phpStan = new PhpStan($confFile, $reportFile); - $exitCode = $phpStan->run($fileList); - $report = file_get_contents($reportFile); - - $errorMessage = empty($report) ? - 'PHPStan command run failed.' : 'PHPStan detected violation(s):' . PHP_EOL . $report; - $this->assertEquals(0, $exitCode, $errorMessage); - } - - /** - * Tests whitelisted fixtures for reuse other fixtures. - */ - public function testFixtureReuse() - { - $changedFiles = self::getWhitelist(['php']); - $toBeTestedFiles = self::filterFiles($changedFiles, ['php'], []); - - $filesWithIncorrectReuse = []; - foreach ($toBeTestedFiles as $fileName) { - //check only _files and Fixtures directory - if (!preg_match('/integration.+\/(_files|Fixtures)/', $fileName)) { - continue; - } - $file = str_replace(["\n", "\r"], '', file_get_contents($fileName)); - if (preg_match('/(?assertEquals( - 0, - count($filesWithIncorrectReuse), - "The following files incorrectly reuse fixtures:" - . PHP_EOL - . implode(PHP_EOL, $filesWithIncorrectReuse) - . PHP_EOL - . 'Please use Magento\TestFramework\Workaround\Override\Fixture\Resolver::requireDataFixture' - ); - } -} diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/PHPCompatibilityMagento/ruleset.xml b/dev/tests/static/testsuite/Magento/Test/Local/_files/PHPCompatibilityMagento/ruleset.xml deleted file mode 100644 index 579a6d2416f20..0000000000000 --- a/dev/tests/static/testsuite/Magento/Test/Local/_files/PHPCompatibilityMagento/ruleset.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - Magento 2 specific ruleset which checks for PHP cross version compatibility. - - - - diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/blacklist/strict_type.txt b/dev/tests/static/testsuite/Magento/Test/Local/_files/blacklist/strict_type.txt deleted file mode 100644 index 877583e5b6a29..0000000000000 --- a/dev/tests/static/testsuite/Magento/Test/Local/_files/blacklist/strict_type.txt +++ /dev/null @@ -1 +0,0 @@ -# Format: or simply diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/common.txt deleted file mode 100644 index efc7e669b3605..0000000000000 --- a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/common.txt +++ /dev/null @@ -1,111 +0,0 @@ -app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php -app/code/Magento/Backend/Block/Dashboard/Tab/Customers/Most.php -app/code/Magento/Backend/Test/Unit/Model/_files/ -app/code/Magento/Bundle -app/code/Magento/Catalog/Model/Product/Visibility.php -app/code/Magento/Catalog/Model/Product/Attribute/Source/Status.php -app/code/Magento/Catalog/Model/Category.php -app/code/Magento/Catalog/Model/ResourceModel/Product/Link/SaveHandler.php -dev -lib/internal/Magento/Framework/DB/Select/RendererProxy.php -lib/internal/Magento/Framework/DB/Query/Generator.php -app/code/Magento/Paypal/Model/Method/Agreement.php -app/code/Magento/Paypal/Test/Unit/Model/_files/additional_info_data.php -app/code/Magento/Downloadable/Model/Link.php -app/code/Magento/Downloadable/Model/Sales/Order/Pdf/Items/Creditmemo.php -app/code/Magento/Downloadable/Model/ResourceModel/Link/Collection.php -app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Links.php -app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Crosssell.php -app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Related.php -app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Upsell.php -app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php -app/code/Magento/Catalog/Block/Adminhtml/Category/Helper/Sortby/Available.php -app/code/Magento/Catalog/Model/Layer/Filter/Dynamic/Auto.php -app/code/Magento/Sales/Model/Order/Pdf/ -app/code/Magento/Sales/Model/Order/CreditmemoRepository.php -app/code/Magento/Sales/Model/Order/Email/Container/ -app/code/Magento/Sales/Model/Order/Invoice.php -app/code/Magento/Sales/Model/Order/Invoice/Item.php -app/code/Magento/Sales/Model/Order/Creditmemo/Item.php -app/code/Magento/Sales/Model/Order/ShipmentRepository.php -app/code/Magento/Sales/Model/ResourceModel/Order/Creditmemo/Relation/Refund.php -app/code/Magento/Sales/Model/Order/Shipment/Sender/EmailSender.php -app/code/Magento/Sales/Model/Order/Invoice/Sender/EmailSender.php -app/code/Magento/Sales/Model/Order/Email/Sender/CreditmemoSender.php -app/code/Magento/Cms/Model/ResourceModel/Page/Grid/Collection.php -app/code/Magento/Shipping/Block/Order/Shipment.php -app/code/Magento/Sales/Block/Order/Creditmemo.php -app/code/Magento/Sales/Block/Order/Invoice.php -app/code/Magento/Sales/Block/Order/PrintOrder/Creditmemo.php -app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/NewAction.php -app/code/Magento/AdvancedCheckout/Controller/Adminhtml/Index/ConfigureQuoteItems.php -app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php -app/code/Magento/Cron/Model/Config/Backend/Product/Alert.php -app/code/Magento/Customer/Controller/Adminhtml/Address/Viewfile.php -app/code/Magento/Customer/Model/Metadata/Form/AbstractData.php -app/code/Magento/GiftMessage/Block/Adminhtml/Sales/Order/View/Items.php -app/code/Magento/ImportExport/Model/Export/Entity/AbstractEntity.php -app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php -app/code/Magento/Newsletter/Block/Adminhtml/Queue/Preview/Form.php -app/code/Magento/ProductAlert/Model/Observer.php -app/code/Magento/Reports/Block/Adminhtml/Sales/Invoiced/Grid.php -app/code/Magento/Reports/Model/ResourceModel/Customer/Totals/Collection.php -app/code/Magento/Review/Block/Customer/View.php -app/code/Magento/Rule/Test/Mftf/Helper/RuleHelper.php -app/code/Magento/Sales/Block/Adminhtml/Order/Status/NewStatus/Form.php -app/code/Magento/Sales/Model/ResourceModel/Collection/AbstractCollection.php -app/code/Magento/Sales/Model/ResourceModel/Report/ -app/code/Magento/Search/Block/Adminhtml/Dashboard/Last.php -app/code/Magento/Shipping/Model/Carrier/AbstractCarrierOnline.php -app/code/Magento/CatalogRule/Model/ResourceModel/SaveHandler.php -lib/internal/Magento/Framework/HTTP/Client/Socket.php -lib/internal/Magento/Framework/App/Config/Scope/Validator.php -app/code/Magento/Cron/Model/Schedule.php -generated/code -lib/internal/Magento/Framework/Filesystem/Driver/File/Mime.php -app/code/Magento/Shipping/Model/Rate/PackageResult.php -app/code/Magento/Ui/DataProvider/AbstractDataProvider.php -app/code/Magento/Ui/Config/Converter/Url.php -app/code/Magento/Ui/Config/Converter/Buttons.php -app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php -app/code/Magento/Sales/Api/Data/CreditmemoItemInterface.php -vendor -lib/internal/Magento/Framework/DataObject/Copy/Config/Data/Proxy.php -app/code/Magento/Catalog/Setup/Patch/Data/UpgradeWebsiteAttributes.php -app/code/Magento/CatalogUrlRewrite/Setup/Recurring.php -app/code/Magento/Checkout/Setup/Patch/Data/PrepareInitialCheckoutConfiguration.php -app/code/Magento/ConfigurableProduct/Setup/Recurring.php -app/code/Magento/Sales/Setup/SalesSetup.php -app/code/Magento/Weee/Setup/Recurring.php -app/code/Magento/Wishlist/Setup/Recurring.php -setup/src/Magento/Setup/Fixtures -app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Rows.php -app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php -app/code/Magento/Catalog/Model/Attribute/Backend/ConsumerWebsiteAssign.php -app/code/Magento/Catalog/Ui/Component/Listing/ -app/code/Magento/Payment/Gateway/Data/Order/AddressAdapter.php -app/code/Magento/SalesRule/Model/CouponRepository.php -app/code/Magento/SalesRule/Test/Unit/Model/Rule/Metadata/_files/MetaData.php -app/code/Magento/Tax/Model/Calculation/UnitBaseCalculator.php -app/code/Magento/Customer/Block/Widget/ -app/code/Magento/Persistent/Observer/EmulateCustomerObserver.php -app/code/Magento/Eav/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php -lib/internal/Magento/Framework/App/AreaList/Proxy.php -lib/internal/Magento/Framework/App/Route/ConfigInterface/Proxy.php -lib/internal/Magento/Framework/Backup/Filesystem/Iterator/Filter.php -app/code/Magento/Theme/Model/Indexer/Design/Config.php -lib/internal/Magento/Framework/Mview/Config/Data/Proxy.php -lib/internal/Magento/Framework/View/File/Collector/Override -lib/internal/Magento/Framework/MessageQueue/Consumer/Config/ConsumerConfigItem/ -lib/internal/Magento/Framework/MessageQueue/Publisher/Config/PublisherConfigItem/Iterator.php -lib/internal/Magento/Framework/MessageQueue/Topology/Config/ExchangeConfigItem/Iterator.php -app/code/Magento/Integration/Model/IntegrationConfig.php -*Test.php -Test/_files -Test/Unit/_files -Test/Integration/_files -app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php -app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/FieldProvider/FieldType/Resolver/CompositeResolver.php -app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php -app/code/Magento/Newsletter/Model/Queue/TransportBuilder.php -app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/bulk.phtml diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/inventory.txt b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/inventory.txt deleted file mode 100644 index 0a12e975e097e..0000000000000 --- a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpcpd/blacklist/inventory.txt +++ /dev/null @@ -1,11 +0,0 @@ -app/code/Magento/InventoryBundleProductIndexer/Indexer/SelectBuilder.php -app/code/Magento/InventoryConfigurableProductIndexer/Indexer/SelectBuilder.php -app/code/Magento/InventoryCatalogAdminUi/Controller/Adminhtml/Source/BulkAssignPost.php -app/code/Magento/InventoryBundleProductIndexer/Indexer/SourceItem/SourceItemIndexer.php -app/code/Magento/InventoryShippingAdminUi/Ui/DataProvider/GetSourcesByOrderIdSkuAndQty.php -app/code/Magento/InventoryBundleProductIndexer/Indexer/SourceItem/SiblingSkuListInStockProvider.php -app/code/Magento/InventoryInStorePickupWebapiExtension/Model/Rest/Swagger/Generator.php -app/code/Magento/InventoryAdminUi/Controller/Adminhtml/Source/MassDisable.php -app/code/Magento/InventoryBundleProductIndexer/Indexer/StockIndexer.php -app/code/Magento/InventoryBundleProductIndexer/Indexer/SelectBuilder.php -app/code/Magento/InventoryConfigurableProductIndexer/Indexer/SourceItem/SiblingSkuListInStockProvider.php diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpmd/ruleset.xml b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpmd/ruleset.xml deleted file mode 100644 index 0e3b5fa3d341c..0000000000000 --- a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpmd/ruleset.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - Magento Code Check Rules - ../../../../../../framework - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/blacklist/common.txt deleted file mode 100644 index 6b0007f58830b..0000000000000 --- a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/blacklist/common.txt +++ /dev/null @@ -1,23 +0,0 @@ -# Format: path to directory or path to file -# -# Example: -# app/code/Magento/Catalog -# dev/tests/static/framework/bootstrap.php -lib/internal/Magento/Framework/Cache/Backend/Eaccelerator.php -lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php -lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/InterceptorTest.php -lib/internal/Magento/Framework/Interception/Test/Unit/Config/ConfigTest.php -dev/tests/integration/framework/deployTestModules.php -dev/tests/integration/testsuite/Magento/Framework/Code/Generator/AutoloaderTest.php -dev/tests/integration/testsuite/Magento/Framework/Communication/ConfigTest.php -dev/tests/integration/testsuite/Magento/Framework/Filter/DirectiveProcessor/SimpleDirectiveTest.php -dev/tests/integration/testsuite/Magento/Framework/Session/ConfigTest.php -dev/tests/integration/testsuite/Magento/Framework/Session/SessionManagerTest.php -dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/AbstractFiltersTest.php -dev/tests/integration/testsuite/Magento/LayeredNavigation/Block/Navigation/CategoryTest.php -dev/tests/api-functional/testsuite/Magento/Customer/Api/AddressRepositoryTest.php -dev/tests/api-functional/testsuite/Magento/Framework/Model/Entity/HydratorTest.php -dev/tests/api-functional/testsuite/Magento/Integration/Model/AdminTokenServiceTest.php -dev/tests/api-functional/testsuite/Magento/Integration/Model/CustomerTokenServiceTest.php -app/code/Magento/Developer/Test/Unit/Console/Command/DevTestsRunCommandTest.php -app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/ColumnResolverTest.php diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/phpstan.neon b/dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/phpstan.neon deleted file mode 100644 index aba7d0b46d297..0000000000000 --- a/dev/tests/static/testsuite/Magento/Test/Local/_files/phpstan/phpstan.neon +++ /dev/null @@ -1,49 +0,0 @@ -parameters: - checkExplicitMixedMissingReturn: true - checkPhpDocMissingReturn: true - reportUnmatchedIgnoredErrors: false - excludes_analyse: - - %rootDir%/../../../lib/internal/Magento/Framework/ObjectManager/Test/Unit/* - - %rootDir%/../../../*/_files/* - - %rootDir%/../../../dev/tests/*/Fixtures/* - - %rootDir%/../../../dev/tests/*/tmp/* - - %rootDir%/../../../dev/tests/*/_generated/* - - %rootDir%/../../../pub/* - scanDirectories: - - %rootDir%/../../../dev/tests/static/framework/tests/unit/testsuite/Magento - - %rootDir%/../../../dev/tests/integration/framework/tests/unit/testsuite/Magento - - %rootDir%/../../../dev/tests/api-functional/_files/Magento - bootstrapFiles: - - %rootDir%/../../../dev/tests/static/framework/autoload.php - - %rootDir%/../../../dev/tests/integration/framework/autoload.php - - %rootDir%/../../../dev/tests/api-functional/framework/autoload.php - - %rootDir%/../../../dev/tests/setup-integration/framework/autoload.php - - %rootDir%/../../../dev/tests/static/framework/Magento/PhpStan/autoload.php - ignoreErrors: - # Ignore PHPStan\Rules\Classes\UnusedConstructorParametersRule - - '#Constructor of class [a-zA-Z0-9\\_]+ has an unused parameter#' - # Ignore setCustomErrorHandler function not found in bootstrap files - - '#Function setCustomErrorHandler not found#' - # Ignore 'return statement is missing' error when 'void' is present in return type list - - '#Method (?:.*?) should return (?:.*?)void(?:.*?) but return statement is missing.#' - # Ignore constants, defined dynamically. - - '#Constant TESTS_WEB_API_ADAPTER not found.#' - - '#Constant TESTS_BASE_URL not found.#' - - '#Constant TESTS_XDEBUG_ENABLED not found.#' - - '#Constant TESTS_XDEBUG_SESSION not found.#' - - '#Constant INTEGRATION_TESTS_DIR not found.#' - - '#Constant MAGENTO_MODULES_PATH not found.#' - - '#Constant TESTS_MODULES_PATH not found.#' - - '#Constant TESTS_INSTALLATION_DB_CONFIG_FILE not found.#' - - '#Constant T_[A-Z_]+ not found.#' - - -services: - - - class: Magento\PhpStan\Reflection\Php\DataObjectClassReflectionExtension - tags: {phpstan.broker.methodsClassReflectionExtension: {priority: 100}} - - errorFormatter.filtered: - class: Magento\PhpStan\Formatters\FilteredErrorFormatter - arguments: - tableErrorFormatter: @errorFormatter.table diff --git a/dev/tests/static/testsuite/Magento/Test/Local/_files/whitelist/common.txt b/dev/tests/static/testsuite/Magento/Test/Local/_files/whitelist/common.txt deleted file mode 100644 index ffb7d924f9d5a..0000000000000 --- a/dev/tests/static/testsuite/Magento/Test/Local/_files/whitelist/common.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Format: or simply -app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php From ddc95cc7aa0a691225c8eb8f905f735a0ee971e4 Mon Sep 17 00:00:00 2001 From: Jakub Winkler Date: Mon, 20 Sep 2021 07:07:10 +0200 Subject: [PATCH 16/87] #33486 - restoring @magentoAppIsolation to class --- .../Model/ResourceModel/Product/Indexer/Eav/SourceTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php index 7e7b311625df2..24d9d56fb0df7 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php @@ -14,6 +14,9 @@ use Magento\Store\Model\StoreManagerInterface; use Magento\Store\Api\Data\StoreInterface; +/** + * @magentoAppIsolation enabled + */ class SourceTest extends \PHPUnit\Framework\TestCase { /** From ddab158144b1e4cc6971bea51beb8c25cc2ebc07 Mon Sep 17 00:00:00 2001 From: Jakub Winkler Date: Mon, 20 Sep 2021 08:18:22 +0200 Subject: [PATCH 17/87] 33468 - restroing phpmd supressed warning --- .../Model/ResourceModel/Product/Indexer/Eav/SourceTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php index 24d9d56fb0df7..0e30015d98163 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/SourceTest.php @@ -16,6 +16,7 @@ /** * @magentoAppIsolation enabled + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class SourceTest extends \PHPUnit\Framework\TestCase { From b605562fabb6ac9b434b1fddd3b40f3610e7795e Mon Sep 17 00:00:00 2001 From: AZiniukhin Date: Thu, 23 Sep 2021 11:28:14 +0300 Subject: [PATCH 18/87] Fixed error DOT field with Portugal locale --- app/code/Magento/Customer/Block/Widget/Dob.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Customer/Block/Widget/Dob.php b/app/code/Magento/Customer/Block/Widget/Dob.php index ef2d2cca169f5..919db404df175 100644 --- a/app/code/Magento/Customer/Block/Widget/Dob.php +++ b/app/code/Magento/Customer/Block/Widget/Dob.php @@ -422,7 +422,8 @@ public function getTranslatedCalendarConfigJson(): string 'monthNamesShort' => array_values(iterator_to_array($monthsData['format']['abbreviated'])), 'dayNames' => array_values(iterator_to_array($daysData['format']['wide'])), 'dayNamesShort' => array_values(iterator_to_array($daysData['format']['abbreviated'])), - 'dayNamesMin' => array_values(iterator_to_array($daysData['format']['short'])), + 'dayNamesMin' => array_values(iterator_to_array( + ($daysData['format']['short']) ?: $daysData['format']['abbreviated'])), ] ); } From b6a7c44c8ed85a9a73b93cf2b46ab2d32ff67f31 Mon Sep 17 00:00:00 2001 From: Rimple Saini Date: Fri, 24 Sep 2021 15:27:39 +0530 Subject: [PATCH 19/87] AC-122::Paypal Admin: PayLater -> Pay Later --- .../Paypal/etc/adminhtml/system/express_checkout.xml | 8 ++++---- .../system/paypal_payflowpro_with_express_checkout.xml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml index 3389b072daff2..dad46b7d5c3a1 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml @@ -184,10 +184,10 @@ - + Advertise PayPal Credit is deprecated. - See PayPal PayLater details and list of supported regions + See PayPal Pay Later details and list of supported regions here.]]> @@ -331,9 +331,9 @@ - + 1 From 44d0cd2b9ce01ad43865e4e9921aa7fe92c9d738 Mon Sep 17 00:00:00 2001 From: abhattGlo Date: Mon, 27 Sep 2021 11:32:23 +0530 Subject: [PATCH 20/87] AC-1224::PayLater advertising(Admin UI) position text change to Under PayPal Checkout buttons --- .../Paypal/Model/System/Config/Source/PayLater/Position.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Paypal/Model/System/Config/Source/PayLater/Position.php b/app/code/Magento/Paypal/Model/System/Config/Source/PayLater/Position.php index af86fd2bcf03a..3dcc48891e6b8 100644 --- a/app/code/Magento/Paypal/Model/System/Config/Source/PayLater/Position.php +++ b/app/code/Magento/Paypal/Model/System/Config/Source/PayLater/Position.php @@ -21,7 +21,7 @@ public function getPositionsCPP(): array { return [ 'header' => __('Header (center)'), - 'near_pp_button' => __('Near PayPal Credit checkout button') + 'near_pp_button' => __('Under PayPal Checkout buttons') ]; } @@ -46,7 +46,7 @@ public function getPositionsHP(): array public function getPositionsCheckout(): array { return [ - 'near_pp_button' => __('Near PayPal Credit checkout button') + 'near_pp_button' => __('Under PayPal Checkout buttons') ]; } @@ -72,7 +72,7 @@ public function getPositionsCart(): array { return [ 'header' => __('Header (center)'), - 'near_pp_button' => __('Near PayPal Credit checkout button') + 'near_pp_button' => __('Under PayPal Checkout buttons') ]; } } From 2fd31d4730c31ebbd7d9f090babb1dcde1bbe590 Mon Sep 17 00:00:00 2001 From: glo5363 Date: Wed, 29 Sep 2021 15:05:10 +0530 Subject: [PATCH 21/87] AC-1225 Sandbox Testing ONLY Add buyer-country param --- app/code/Magento/Paypal/Model/Config.php | 11 +- app/code/Magento/Paypal/Model/SdkUrl.php | 17 +- .../Paypal/Test/Unit/Model/SdkUrlTest.php | 9 +- .../Unit/Model/_files/expected_url_config.php | 56 +- .../etc/adminhtml/system/express_checkout.xml | 654 ++++++++++++------ 5 files changed, 529 insertions(+), 218 deletions(-) diff --git a/app/code/Magento/Paypal/Model/Config.php b/app/code/Magento/Paypal/Model/Config.php index d08c7db4f3db7..0fe4a1c6be6c1 100644 --- a/app/code/Magento/Paypal/Model/Config.php +++ b/app/code/Magento/Paypal/Model/Config.php @@ -12,7 +12,6 @@ * Config model that is aware of all \Magento\Paypal payment methods * * Works with PayPal-specific system configuration - * @SuppressWarnings(PHPMD.ExcessivePublicCount) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ @@ -620,7 +619,8 @@ public function __construct( \Magento\Payment\Model\Source\CctypeFactory $cctypeFactory, \Magento\Paypal\Model\CertFactory $certFactory, $params = [] - ) { + ) + { parent::__construct($scopeConfig); $this->directoryHelper = $directoryHelper; $this->_storeManager = $storeManager; @@ -840,7 +840,7 @@ public function getCountryMethods($countryCode = null) public function getPayPalBasicStartUrl($token) { $params = [ - 'cmd' => '_express-checkout', + 'cmd' => '_express-checkout', 'token' => $token, ]; @@ -1519,6 +1519,7 @@ protected function _mapExpressFieldset($fieldName) case 'merchant_id': case 'client_id': case 'sandbox_client_id': + case 'buyer_country': case 'supported_locales': case 'smart_buttons_supported_locales': return "payment/{$this->_methodCode}/{$fieldName}"; @@ -1615,8 +1616,8 @@ protected function _mapWpukFieldset($fieldName) if ($this->_methodCode == self::METHOD_WPP_PE_EXPRESS && $this->isMethodAvailable(self::METHOD_PAYFLOWLINK)) { $pathPrefix = 'payment/payflow_link'; } elseif ($this->_methodCode == self::METHOD_WPP_PE_EXPRESS && $this->isMethodAvailable( - self::METHOD_PAYFLOWADVANCED - ) + self::METHOD_PAYFLOWADVANCED + ) ) { $pathPrefix = 'payment/payflow_advanced'; } elseif ($this->_methodCode == self::METHOD_WPP_PE_EXPRESS) { diff --git a/app/code/Magento/Paypal/Model/SdkUrl.php b/app/code/Magento/Paypal/Model/SdkUrl.php index e10f4ce2e886b..59e28eb5fed40 100644 --- a/app/code/Magento/Paypal/Model/SdkUrl.php +++ b/app/code/Magento/Paypal/Model/SdkUrl.php @@ -82,7 +82,8 @@ public function __construct( StoreManagerInterface $storeManager, $disallowedFundingMap = [], $unsupportedPaymentMethods = [] - ) { + ) + { $this->localeResolver = $localeResolver; $this->config = $configFactory->create(); $this->config->setMethod(Config::METHOD_EXPRESS); @@ -116,6 +117,7 @@ public function getUrl(): string $params['intent'] = $this->getIntent(); $params['merchant-id'] = $this->config->getValue('merchant_id'); $params['disable-funding'] = $this->getDisallowedFunding(); + $params['buyer-country'] = $this->getBuyerCountry(); $params = array_replace($params, $this->queryParams); } $params['components'] = implode(',', $components); @@ -156,7 +158,7 @@ private function areMessagesEnabled() */ private function areButtonsEnabled() { - return (bool)(int) $this->config->getValue('in_context'); + return (bool)(int)$this->config->getValue('in_context'); } /** @@ -171,6 +173,17 @@ private function getClientId() $this->config->getValue('client_id'); } + /** + * get Configured value for paypal buyer country + * @return string + */ + private function getBuyerCountry() + { + return (int)$this->config->getValue('sandbox_flag') ? + $this->config->getValue('buyer_country') : + ''; + } + /** * Returns disallowed funding from configuration after updating values * diff --git a/app/code/Magento/Paypal/Test/Unit/Model/SdkUrlTest.php b/app/code/Magento/Paypal/Test/Unit/Model/SdkUrlTest.php index 3b29017f494c4..b6aec6328acec 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/SdkUrlTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/SdkUrlTest.php @@ -85,6 +85,7 @@ protected function setUp(): void * @param string $locale * @param string $intent * @param string|null $disallowedFunding + * @param bool $isBuyerCountryEnabled * @param bool $isPaypalGuestCheckoutEnabled * @param array $expected * @dataProvider getConfigDataProvider @@ -93,9 +94,11 @@ public function testGetConfig( string $locale, string $intent, ?string $disallowedFunding, + bool $isBuyerCountryEnabled, bool $isPaypalGuestCheckoutEnabled, array $expected - ) { + ) + { $this->localeResolverMock->method('getLocale')->willReturn($locale); $this->configMock->method('getValue')->willReturnMap( [ @@ -103,6 +106,7 @@ public function testGetConfig( ['sandbox_client_id', null, 'sb'], ['sandbox_flag', null, true], ['disable_funding_options', null, $disallowedFunding], + ['buyer_country', null, $isBuyerCountryEnabled ? 'US' : ''], ['paymentAction', null, $intent], ['in_context', null, true], [ @@ -112,7 +116,6 @@ public function testGetConfig( ], ] ); - self::assertEquals($expected['sdkUrl'], $this->model->getUrl()); } @@ -150,7 +153,7 @@ private function getDisallowedFundingMap() private function getUnsupportedPaymentMethods() { return [ - 'venmo'=> 'venmo', + 'venmo' => 'venmo', 'bancontact' => 'bancontact', 'eps' => 'eps', 'giropay' => 'giropay', diff --git a/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_url_config.php b/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_url_config.php index 52d488104f059..22d647473acba 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_url_config.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_url_config.php @@ -10,7 +10,7 @@ * @param array $params * @return String */ -function generateExpectedPaypalSdkUrl(array $params) : String +function generateExpectedPaypalSdkUrl(array $params): string { return 'https://www.paypal.com/sdk/js?' . http_build_query($params); } @@ -20,6 +20,7 @@ function generateExpectedPaypalSdkUrl(array $params) : String 'es_MX', 'Authorization', 'CREDIT,ELV,CARD', + false, true, [ 'sdkUrl' => generateExpectedPaypalSdkUrl( @@ -55,6 +56,7 @@ function generateExpectedPaypalSdkUrl(array $params) : String 'en_BR', 'Sale', null, + false, true, [ 'sdkUrl' => generateExpectedPaypalSdkUrl( @@ -78,6 +80,7 @@ function generateExpectedPaypalSdkUrl(array $params) : String 'en_US', 'Order', null, + false, true, [ 'sdkUrl' => generateExpectedPaypalSdkUrl( @@ -102,6 +105,7 @@ function generateExpectedPaypalSdkUrl(array $params) : String 'Authorization', 'CREDIT,ELV', false, + false, [ 'sdkUrl' => generateExpectedPaypalSdkUrl( [ @@ -136,6 +140,56 @@ function generateExpectedPaypalSdkUrl(array $params) : String 'en_BR', 'Authorization', 'CREDIT,ELV', + false, + true, + [ + 'sdkUrl' => generateExpectedPaypalSdkUrl( + [ + 'client-id' => 'sb', + 'locale' => 'en_BR', + 'currency' => 'USD', + 'commit' => 'false', + 'intent' => 'authorize', + 'merchant-id' => 'merchant', + 'disable-funding' => implode( + ',', + ['credit', 'sepa', 'venmo', 'bancontact', 'eps', 'giropay', 'ideal', 'mybank', 'p24', 'sofort'] + ), + 'components' => implode(',', ['messages', 'buttons']) + ] + ) + ] + ], + 'buyer_country_enabled' => [ + 'en_BR', + 'Authorization', + 'CREDIT,ELV', + true, + true, + [ + 'sdkUrl' => generateExpectedPaypalSdkUrl( + [ + 'client-id' => 'sb', + 'locale' => 'en_BR', + 'currency' => 'USD', + 'commit' => 'false', + 'intent' => 'authorize', + 'merchant-id' => 'merchant', + 'disable-funding' => implode( + ',', + ['credit', 'sepa', 'venmo', 'bancontact', 'eps', 'giropay', 'ideal', 'mybank', 'p24', 'sofort'] + ), + 'buyer-country' => 'US', + 'components' => implode(',', ['messages', 'buttons']) + ] + ) + ] + ], + 'buyer_country_disabled' => [ + 'en_BR', + 'Authorization', + 'CREDIT,ELV', + false, true, [ 'sdkUrl' => generateExpectedPaypalSdkUrl( diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml index 3389b072daff2..4af5a5e3e6c40 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml @@ -5,8 +5,10 @@ * See COPYING.txt for license details. */ --> - - + + Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment paypal-other-section @@ -19,10 +21,12 @@ Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded - + Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded - + not-required @@ -33,25 +37,29 @@ validate-email 1 - + paypal/wpp/api_authentication Magento\Paypal\Model\Config::getApiAuthenticationMethods 1 - + paypal/wpp/api_username Magento\Config\Model\Config\Backend\Encrypted 1 - + paypal/wpp/api_password Magento\Config\Model\Config\Backend\Encrypted 1 - + paypal/wpp/api_signature Magento\Config\Model\Config\Backend\Encrypted @@ -69,7 +77,8 @@ 1 - + Get Credentials from PayPal @@ -97,19 +106,32 @@ Magento\Paypal\Block\Adminhtml\System\Config\ApiWizard 1 - + paypal/wpp/sandbox_flag Magento\Config\Model\Config\Source\Yesno 1 - + + + payment/paypal_express/buyer_country + Magento\Directory\Model\Config\Source\Country + 1 + + 1 + + + paypal/wpp/use_proxy Magento\Config\Model\Config\Source\Yesno 1 - + paypal/wpp/proxy_host 1 @@ -117,7 +139,8 @@ 1 - + Please enter at least 0 and at most 65535 paypal/wpp/proxy_port @@ -128,7 +151,8 @@ validate-digits validate-digits-range digits-range-0-65535 - + payment/paypal_express/active Magento\Config\Model\Config\Source\Yesno @@ -137,7 +161,8 @@ - + - You can look up your merchant ID by logging into https://www.paypal.com/. Click the profile icon on the top right side of the page and then select Profile and settings in the Business Profile menu. (If you do not see the profile icon at the top of the page, click Profile, which appears in the top menu when the My Account tab is selected.) Click My business info on the left, and the Merchant account ID is displayed in the list of profile items on the right. + You can look up your merchant ID by logging into https://www.paypal.com/. Click the profile + icon on the top right side of the page and then select Profile and settings in the Business Profile + menu. (If you do not see the profile icon at the top of the page, click Profile, which appears in + the top menu when the My Account tab is selected.) Click My business info on the left, and the + Merchant account ID is displayed in the list of profile items on the right. + payment/paypal_express/merchant_id Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\MerchantId required-entry @@ -160,7 +190,8 @@ 1 - + - + payment/paypal_express_bml/sort_order validate-number 1 - Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\BmlApiSortOrder + Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\BmlApiSortOrder + 1 - + Advertise PayPal Credit is deprecated. @@ -209,7 +243,8 @@ 0 - + payment/paypal_express_bml/publisher_id @@ -217,24 +252,30 @@ Get Publisher ID from PayPal - + + Magento\Paypal\Block\Adminhtml\System\Config\BmlApiWizard - + - + payment/paypal_express_bml/homepage_display Magento\Config\Model\Config\Source\Yesno 1 - + payment/paypal_express_bml/homepage_position - Magento\Paypal\Model\System\Config\Source\BmlPosition::getBmlPositionsHP + Magento\Paypal\Model\System\Config\Source\BmlPosition::getBmlPositionsHP + 1 - + payment/paypal_express_bml/homepage_size Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeHPH @@ -243,96 +284,130 @@ 0 - + Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeHPS 1 - + - + payment/paypal_express_bml/categorypage_display Magento\Config\Model\Config\Source\Yesno 1 - + payment/paypal_express_bml/categorypage_position - Magento\Paypal\Model\System\Config\Source\BmlPosition::getBmlPositionsCCP + Magento\Paypal\Model\System\Config\Source\BmlPosition::getBmlPositionsCCP + 1 - + payment/paypal_express_bml/categorypage_size Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCCPC 1 - 0 + + 0 + - + Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCCPS - 1 + + 1 + - + - + payment/paypal_express_bml/productpage_display Magento\Config\Model\Config\Source\Yesno 1 - + payment/paypal_express_bml/productpage_position - Magento\Paypal\Model\System\Config\Source\BmlPosition::getBmlPositionsCPP + Magento\Paypal\Model\System\Config\Source\BmlPosition::getBmlPositionsCPP + 1 - + payment/paypal_express_bml/productpage_size Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCPPC 1 - 0 + + 0 + - + Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCPPN - 1 + + 1 + - + - + payment/paypal_express_bml/checkout_display Magento\Config\Model\Config\Source\Yesno 1 - + payment/paypal_express_bml/checkout_position - Magento\Paypal\Model\System\Config\Source\BmlPosition::getBmlPositionsCheckout + Magento\Paypal\Model\System\Config\Source\BmlPosition::getBmlPositionsCheckout + 1 - + payment/paypal_express_bml/checkout_size - Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCheckoutC + Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCheckoutC + 1 - 0 + + 0 + - - Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCheckoutN + + Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCheckoutN + 1 - 1 + + 1 +