diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 4e82725a7fb08..c2e9fa9cef242 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,46 +1,80 @@ -# Contributor Covenant Code of Conduct +# Magento Code of Conduct ## Our Pledge -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. +We as members, contributors, and leaders pledge to make participation in our project and community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + ## Our Standards -Examples of behavior that contributes to creating a positive environment include: +Examples of behavior that contribute to a positive environment for our project and community include: + + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best, not just for us as individuals but for the overall community -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks +* The use of sexualized language or imagery and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Publishing others’ private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting + ## Our Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. +Project maintainers are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any instances of unacceptable behavior. -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. +This Code of Conduct applies when an individual is representing the project or its community both within project spaces and in public spaces. Examples of representing a project or community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at engcom@magento.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by first contacting the project team at engcom@adobe.com. Oversight of Adobe projects is handled by the Adobe Open Source Office, which has final say in any violations and enforcement of this Code of Conduct and can be reached at Grp-opensourceoffice@adobe.com. All complaints will be reviewed and investigated promptly and fairly. -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. +The project team must respect the privacy and security of the reporter of any incident. -## Attribution +Project maintainers who do not follow or enforce the Code of Conduct may face temporary or permanent repercussions as determined by other members of the project's leadership or the Adobe Open Source Office. + + +## Enforcement Guidelines + +Project maintainers will follow these Community Impact Guidelines in determining the consequences for any action they deem to be in violation of this Code of Conduct: + +### 1. Correction + +Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. +Consequence: A private, written warning from project maintainers describing the violation and why the behavior was unacceptable. A public apology may be requested from the violator before any further involvement in the project by violator. -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] +### 2. Warning + +Community Impact: A relatively minor violation through a single incident or series of actions. + +Consequence: A written warning from project maintainers that includes stated consequences for continued unacceptable behavior. Violator must refrain from interacting with the people involved for a specified period of time as determined by the project maintainers, including, but not limited to, unsolicited interaction with those enforcing the Code of Conduct through channels such as community spaces and social media. Continued violations may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +Community Impact: A more serious violation of community standards, including sustained unacceptable behavior. + +Consequence: A temporary ban from any interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Failure to comply with the temporary ban may lead to a permanent ban. + +### 4. Permanent Ban + +Community Impact: Demonstrating a consistent pattern of violation of community standards or an egregious violation of community standards, including, but not limited to, sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +Consequence: A permanent ban from any interaction with the community. + + +## Attribution -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ +This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. diff --git a/app/bootstrap.php b/app/bootstrap.php index 46d9e52b40019..8fbe2f770f53b 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -14,14 +14,14 @@ #ini_set('display_errors', 1); /* PHP version validation */ -if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 70400) { +if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 80100) { if (PHP_SAPI == 'cli') { - echo 'Magento supports PHP 7.4.0 or later. ' . + echo 'Magento supports PHP 8.1.0 or later. ' . 'Please read https://devdocs.magento.com/guides/v2.4/install-gde/system-requirements-tech.html'; } else { echo << -

Magento supports PHP 7.4.0 or later. Please read +

Magento supports PHP 8.1.0 or later. Please read Magento System Requirements. diff --git a/app/code/Magento/AdminAdobeIms/composer.json b/app/code/Magento/AdminAdobeIms/composer.json index 0e556a483a51d..623d2ceb77a09 100644 --- a/app/code/Magento/AdminAdobeIms/composer.json +++ b/app/code/Magento/AdminAdobeIms/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-adobe-ims": "*", "magento/module-adobe-ims-api": "*", diff --git a/app/code/Magento/AdminAnalytics/composer.json b/app/code/Magento/AdminAnalytics/composer.json index ef3829fd149c6..e2f2bb182422d 100644 --- a/app/code/Magento/AdminAnalytics/composer.json +++ b/app/code/Magento/AdminAnalytics/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-config": "*", diff --git a/app/code/Magento/AdminNotification/composer.json b/app/code/Magento/AdminNotification/composer.json index 28ca1f626a2cd..1354cc202d7d2 100644 --- a/app/code/Magento/AdminNotification/composer.json +++ b/app/code/Magento/AdminNotification/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "lib-libxml": "*", "magento/framework": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/AdobeIms/composer.json b/app/code/Magento/AdobeIms/composer.json index b8542bc129294..9a3d8f27a87d2 100644 --- a/app/code/Magento/AdobeIms/composer.json +++ b/app/code/Magento/AdobeIms/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-adobe-ims", "description": "Magento module responsible for authentication to Adobe services", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-adobe-ims-api": "*", "magento/module-authorization": "*", diff --git a/app/code/Magento/AdobeImsApi/composer.json b/app/code/Magento/AdobeImsApi/composer.json index 231f1ddfa1513..13a02442e5c9b 100644 --- a/app/code/Magento/AdobeImsApi/composer.json +++ b/app/code/Magento/AdobeImsApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-adobe-ims-api", "description": "Implementation of Magento module responsible for authentication to Adobe services", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/AdvancedPricingImportExport/composer.json b/app/code/Magento/AdvancedPricingImportExport/composer.json index 59ea74cf4ddcb..9ba5c58657f4f 100644 --- a/app/code/Magento/AdvancedPricingImportExport/composer.json +++ b/app/code/Magento/AdvancedPricingImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-catalog-import-export": "*", diff --git a/app/code/Magento/AdvancedSearch/Helper/Data.php b/app/code/Magento/AdvancedSearch/Helper/Data.php new file mode 100644 index 0000000000000..9516472773461 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Helper/Data.php @@ -0,0 +1,53 @@ +engineResolver = $engineResolver; + } + + /** + * Check if opensearch v2.x + * + * @return bool + */ + public function isClientOpenSearchV2(): bool + { + $searchEngine = $this->engineResolver->getCurrentSearchEngine(); + if (stripos($searchEngine, self::OPENSEARCH) !== false) { + if (substr(Client::VERSION, 0, 1) == self::MAJOR_VERSION) { + return true; + } + } + return false; + } +} diff --git a/app/code/Magento/AdvancedSearch/Model/Client/ClientFactory.php b/app/code/Magento/AdvancedSearch/Model/Client/ClientFactory.php index 05eb513d68399..70ff445fb2b6c 100644 --- a/app/code/Magento/AdvancedSearch/Model/Client/ClientFactory.php +++ b/app/code/Magento/AdvancedSearch/Model/Client/ClientFactory.php @@ -6,11 +6,12 @@ namespace Magento\AdvancedSearch\Model\Client; use Magento\Framework\ObjectManagerInterface; +use Magento\AdvancedSearch\Helper\Data; class ClientFactory implements ClientFactoryInterface { /** - * Object manager + * Object var * * @var ObjectManagerInterface */ @@ -21,14 +22,32 @@ class ClientFactory implements ClientFactoryInterface */ private $clientClass; + /** + * @var string + */ + private $openSearch; + + /** + * @var Data + */ + protected $helper; + /** * @param ObjectManagerInterface $objectManager * @param string $clientClass + * @param Data $helper + * @param string|null $openSearch */ - public function __construct(ObjectManagerInterface $objectManager, $clientClass) - { + public function __construct( + ObjectManagerInterface $objectManager, + $clientClass, + Data $helper, + $openSearch = null + ) { $this->objectManager = $objectManager; $this->clientClass = $clientClass; + $this->openSearch = $openSearch; + $this->helper = $helper; } /** @@ -39,8 +58,13 @@ public function __construct(ObjectManagerInterface $objectManager, $clientClass) */ public function create(array $options = []) { + $class = $this->clientClass; + if ($this->helper->isClientOpenSearchV2()) { + $class = $this->openSearch; + } + return $this->objectManager->create( - $this->clientClass, + $class, ['options' => $options] ); } diff --git a/app/code/Magento/AdvancedSearch/Test/Unit/Helper/DataTest.php b/app/code/Magento/AdvancedSearch/Test/Unit/Helper/DataTest.php new file mode 100644 index 0000000000000..936d7c0928f08 --- /dev/null +++ b/app/code/Magento/AdvancedSearch/Test/Unit/Helper/DataTest.php @@ -0,0 +1,72 @@ +contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->engineResolverMock = $this->getMockForAbstractClass(EngineResolverInterface::class); + + $this->engineResolverMock->expects($this->any()) + ->method('getCurrentSearchEngine') + ->willReturn(''); + + $this->objectManager = new ObjectManagerHelper($this); + $this->helper = $this->objectManager->getObject( + Data::class, + [ + 'context' => $this->contextMock, + 'engineResolver' => $this->engineResolverMock + ] + ); + } + + public function testIsClientOpenSearchV2() + { + $this->assertIsBool($this->helper->isClientOpenSearchV2()); + } +} diff --git a/app/code/Magento/AdvancedSearch/composer.json b/app/code/Magento/AdvancedSearch/composer.json index 30205c5255cdd..289207e2fa1c4 100644 --- a/app/code/Magento/AdvancedSearch/composer.json +++ b/app/code/Magento/AdvancedSearch/composer.json @@ -13,7 +13,7 @@ "magento/module-customer": "*", "magento/module-search": "*", "magento/module-store": "*", - "php": "~7.4.0||~8.1.0" + "php": "~8.1.0||~8.2.0" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/Amqp/composer.json b/app/code/Magento/Amqp/composer.json index c7d8d49fb0003..2382864a4c4f5 100644 --- a/app/code/Magento/Amqp/composer.json +++ b/app/code/Magento/Amqp/composer.json @@ -8,7 +8,7 @@ "magento/framework": "*", "magento/framework-amqp": "*", "magento/framework-message-queue": "*", - "php": "~7.4.0||~8.1.0" + "php": "~8.1.0||~8.2.0" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/Analytics/composer.json b/app/code/Magento/Analytics/composer.json index 9bf08b4b068ca..d52a4dc2a98a4 100644 --- a/app/code/Magento/Analytics/composer.json +++ b/app/code/Magento/Analytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-analytics", "description": "N/A", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-backend": "*", "magento/module-config": "*", "magento/module-integration": "*", diff --git a/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Notification/Dismiss.php b/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Notification/Dismiss.php index 0a71c130fb20a..d6feed3915a92 100644 --- a/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Notification/Dismiss.php +++ b/app/code/Magento/AsynchronousOperations/Controller/Adminhtml/Notification/Dismiss.php @@ -8,12 +8,13 @@ use Magento\AsynchronousOperations\Model\BulkNotificationManagement; use Magento\Backend\App\Action\Context; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\Controller\ResultFactory; /** * Class Bulk Notification Dismiss Controller */ -class Dismiss extends Action +class Dismiss extends Action implements HttpGetActionInterface { /** * @var BulkNotificationManagement @@ -43,7 +44,7 @@ protected function _isAllowed() } /** - * {@inheritdoc} + * @inheritdoc */ public function execute() { @@ -55,7 +56,7 @@ public function execute() $isAcknowledged = $this->notificationManagement->acknowledgeBulks($bulkUuids); /** @var \Magento\Framework\Controller\Result\Json $result */ - $result = $this->resultFactory->create(ResultFactory::TYPE_JSON); + $result = $this->resultFactory->create(ResultFactory::TYPE_RAW); if (!$isAcknowledged) { $result->setHttpResponseCode(400); } diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Notification/DismissTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Notification/DismissTest.php index 463989efdfa4c..b4fc8ff4f76cb 100644 --- a/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Notification/DismissTest.php +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Controller/Adminhtml/Notification/DismissTest.php @@ -11,6 +11,7 @@ use Magento\AsynchronousOperations\Model\BulkNotificationManagement; use Magento\Framework\App\RequestInterface; use Magento\Framework\Controller\Result\Json; +use Magento\Framework\Controller\Result\Raw; use Magento\Framework\Controller\ResultFactory; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use PHPUnit\Framework\MockObject\MockObject; @@ -43,6 +44,11 @@ class DismissTest extends TestCase */ private $jsonResultMock; + /** + * @var MockObject + */ + private $rawResultMock; + protected function setUp(): void { $objectManager = new ObjectManager($this); @@ -78,10 +84,10 @@ public function testExecute() $this->resultFactoryMock->expects($this->once()) ->method('create') - ->with(ResultFactory::TYPE_JSON, []) - ->willReturn($this->jsonResultMock); + ->with(ResultFactory::TYPE_RAW, []) + ->willReturn($this->rawResultMock); - $this->assertEquals($this->jsonResultMock, $this->model->execute()); + $this->assertEquals($this->rawResultMock, $this->model->execute()); } public function testExecuteSetsBadRequestResponseStatusIfBulkWasNotAcknowledgedCorrectly() @@ -95,7 +101,7 @@ public function testExecuteSetsBadRequestResponseStatusIfBulkWasNotAcknowledgedC $this->resultFactoryMock->expects($this->once()) ->method('create') - ->with(ResultFactory::TYPE_JSON, []) + ->with(ResultFactory::TYPE_RAW, []) ->willReturn($this->jsonResultMock); $this->notificationManagementMock->expects($this->once()) diff --git a/app/code/Magento/AsynchronousOperations/composer.json b/app/code/Magento/AsynchronousOperations/composer.json index b09ca94052e87..7efcf27821405 100644 --- a/app/code/Magento/AsynchronousOperations/composer.json +++ b/app/code/Magento/AsynchronousOperations/composer.json @@ -11,7 +11,7 @@ "magento/module-authorization": "*", "magento/module-backend": "*", "magento/module-ui": "*", - "php": "~7.4.0||~8.1.0" + "php": "~8.1.0||~8.2.0" }, "suggest": { "magento/module-admin-notification": "*", diff --git a/app/code/Magento/Authorization/composer.json b/app/code/Magento/Authorization/composer.json index d122e8b29b46e..268db947994fe 100644 --- a/app/code/Magento/Authorization/composer.json +++ b/app/code/Magento/Authorization/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*" }, diff --git a/app/code/Magento/AwsS3/composer.json b/app/code/Magento/AwsS3/composer.json index 19078b9ee7b77..9b9d55c18140a 100644 --- a/app/code/Magento/AwsS3/composer.json +++ b/app/code/Magento/AwsS3/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-remote-storage": "*" }, diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml index 7bb249e974c31..73b14bdc14151 100644 --- a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml @@ -19,8 +19,6 @@ - - diff --git a/app/code/Magento/Backend/composer.json b/app/code/Magento/Backend/composer.json index 65aa05fe71e56..a3d6c48757c9a 100644 --- a/app/code/Magento/Backend/composer.json +++ b/app/code/Magento/Backend/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backup": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/Backup/composer.json b/app/code/Magento/Backup/composer.json index e7437a3077aa7..2f7a82e9a5c82 100644 --- a/app/code/Magento/Backup/composer.json +++ b/app/code/Magento/Backup/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-cron": "*", diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/SetBundleProductAttributesActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/SetBundleProductAttributesActionGroup.xml index b940dfac865da..a58baac6f6b3c 100644 --- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/SetBundleProductAttributesActionGroup.xml +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/SetBundleProductAttributesActionGroup.xml @@ -60,6 +60,7 @@ + diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml index b51b3d348ccee..8b78ac7b5fe6e 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml @@ -124,5 +124,6 @@ + diff --git a/app/code/Magento/Bundle/composer.json b/app/code/Magento/Bundle/composer.json index 47be75a42c254..35972c3ba10de 100644 --- a/app/code/Magento/Bundle/composer.json +++ b/app/code/Magento/Bundle/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/BundleGraphQl/composer.json b/app/code/Magento/BundleGraphQl/composer.json index 70a619cbf6837..7d29641125a37 100644 --- a/app/code/Magento/BundleGraphQl/composer.json +++ b/app/code/Magento/BundleGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-catalog": "*", "magento/module-bundle": "*", "magento/module-graph-ql": "*", diff --git a/app/code/Magento/BundleImportExport/composer.json b/app/code/Magento/BundleImportExport/composer.json index ff7d0acc7c48d..d7a59a1795ff6 100644 --- a/app/code/Magento/BundleImportExport/composer.json +++ b/app/code/Magento/BundleImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-bundle": "*", "magento/module-store": "*", diff --git a/app/code/Magento/CacheInvalidate/composer.json b/app/code/Magento/CacheInvalidate/composer.json index c756a5fe602e9..6c635ea103b0c 100644 --- a/app/code/Magento/CacheInvalidate/composer.json +++ b/app/code/Magento/CacheInvalidate/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-page-cache": "*" }, diff --git a/app/code/Magento/Captcha/composer.json b/app/code/Magento/Captcha/composer.json index d4b94dbb586c2..0c39d988ba740 100644 --- a/app/code/Magento/Captcha/composer.json +++ b/app/code/Magento/Captcha/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-checkout": "*", diff --git a/app/code/Magento/CardinalCommerce/composer.json b/app/code/Magento/CardinalCommerce/composer.json index 4c49c92cec1ea..a6bc6bd72afd6 100644 --- a/app/code/Magento/CardinalCommerce/composer.json +++ b/app/code/Magento/CardinalCommerce/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-checkout": "*", "magento/module-payment": "*", diff --git a/app/code/Magento/Catalog/Model/Category/AttributeRepository.php b/app/code/Magento/Catalog/Model/Category/AttributeRepository.php index 3243bf718e663..65443e223e854 100644 --- a/app/code/Magento/Catalog/Model/Category/AttributeRepository.php +++ b/app/code/Magento/Catalog/Model/Category/AttributeRepository.php @@ -29,6 +29,11 @@ class AttributeRepository implements CategoryAttributeRepositoryInterface */ private $eavConfig; + /** + * @var array + */ + private $metadataCache; + /** * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder * @param \Magento\Framework\Api\FilterBuilder $filterBuilder @@ -48,7 +53,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria) { @@ -59,7 +64,7 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr } /** - * {@inheritdoc} + * @inheritdoc */ public function get($attributeCode) { @@ -70,23 +75,27 @@ public function get($attributeCode) } /** - * {@inheritdoc} + * @inheritdoc + * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getCustomAttributesMetadata($dataObjectClassName = null) { - $defaultAttributeSetId = $this->eavConfig - ->getEntityType(\Magento\Catalog\Api\Data\CategoryAttributeInterface::ENTITY_TYPE_CODE) - ->getDefaultAttributeSetId(); - $searchCriteria = $this->searchCriteriaBuilder->addFilters( - [ - $this->filterBuilder - ->setField('attribute_set_id') - ->setValue($defaultAttributeSetId) - ->create(), - ] - ); - - return $this->getList($searchCriteria->create())->getItems(); + if (!isset($this->metadataCache[$dataObjectClassName])) { + $defaultAttributeSetId = $this->eavConfig + ->getEntityType(\Magento\Catalog\Api\Data\CategoryAttributeInterface::ENTITY_TYPE_CODE) + ->getDefaultAttributeSetId(); + $searchCriteria = $this->searchCriteriaBuilder->addFilters( + [ + $this->filterBuilder + ->setField('attribute_set_id') + ->setValue($defaultAttributeSetId) + ->create(), + ] + ); + $this->metadataCache[$dataObjectClassName] = $this->getList($searchCriteria->create()) + ->getItems(); + } + return $this->metadataCache[$dataObjectClassName]; } } diff --git a/app/code/Magento/Catalog/Test/Fixture/Product.php b/app/code/Magento/Catalog/Test/Fixture/Product.php index c665acd09cd0f..c6d0905c539ed 100644 --- a/app/code/Magento/Catalog/Test/Fixture/Product.php +++ b/app/code/Magento/Catalog/Test/Fixture/Product.php @@ -10,7 +10,10 @@ use Magento\Catalog\Api\Data\ProductCustomOptionInterface; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Config\Source\ProductPriceOptionsInterface; use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Option as CustomOption; +use Magento\Catalog\Model\Product\Option\Value as CustomOptionValue; use Magento\Catalog\Model\Product\Type; use Magento\Catalog\Model\Product\Visibility; use Magento\Framework\DataObject; @@ -198,21 +201,57 @@ private function prepareOptions(array $data): array { $options = []; $default = [ - 'product_sku' => $data['sku'], - 'title' => 'customoption%order%%uniqid%', - 'type' => ProductCustomOptionInterface::OPTION_TYPE_FIELD, - 'is_require' => true, - 'price' => 10.0, - 'price_type' => 'fixed', - 'sku' => 'customoption%order%%uniqid%', - 'max_characters' => null, + CustomOption::KEY_PRODUCT_SKU => $data['sku'], + CustomOption::KEY_TITLE => 'customoption%order%%uniqid%', + CustomOption::KEY_TYPE => ProductCustomOptionInterface::OPTION_TYPE_FIELD, + CustomOption::KEY_IS_REQUIRE => true, + CustomOption::KEY_PRICE => 10.0, + CustomOption::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, + CustomOption::KEY_SKU => 'customoption%order%%uniqid%', + CustomOption::KEY_MAX_CHARACTERS => null, + CustomOption::KEY_SORT_ORDER => 1, 'values' => null, ]; + $defaultValue = [ + CustomOptionValue::KEY_TITLE => 'customoption%order%_%valueorder%%uniqid%', + CustomOptionValue::KEY_PRICE => 1, + CustomOptionValue::KEY_PRICE_TYPE => ProductPriceOptionsInterface::VALUE_FIXED, + CustomOptionValue::KEY_SKU => 'customoption%order%_%valueorder%%uniqid%', + CustomOptionValue::KEY_SORT_ORDER => 1, + ]; $sortOrder = 1; foreach ($data['options'] as $item) { - $option = $item + ['sort_order' => $sortOrder++] + $default; - $option['title'] = strtr($option['title'], ['%order%' => $option['sort_order']]); - $option['sku'] = strtr($option['sku'], ['%order%' => $option['sort_order']]); + $option = $item + [CustomOption::KEY_SORT_ORDER => $sortOrder++] + $default; + $option[CustomOption::KEY_TITLE] = strtr( + $option[CustomOption::KEY_TITLE], + ['%order%' => $option[CustomOption::KEY_SORT_ORDER]] + ); + $option[CustomOption::KEY_SKU] = strtr( + $option[CustomOption::KEY_SKU], + ['%order%' => $option[CustomOption::KEY_SORT_ORDER]] + ); + if (isset($item['values'])) { + $valueSortOrder = 1; + $option['values'] = []; + foreach ($item['values'] as $value) { + $value += [CustomOptionValue::KEY_SORT_ORDER => $valueSortOrder++] + $defaultValue; + $value[CustomOptionValue::KEY_TITLE] = strtr( + $value[CustomOptionValue::KEY_TITLE], + [ + '%order%' => $option[CustomOption::KEY_SORT_ORDER], + '%valueorder%' => $value[CustomOptionValue::KEY_SORT_ORDER] + ] + ); + $value[CustomOptionValue::KEY_SKU] = strtr( + $value[CustomOptionValue::KEY_SKU], + [ + '%order%' => $option[CustomOption::KEY_SORT_ORDER], + '%valueorder%' => $value[CustomOptionValue::KEY_SORT_ORDER] + ] + ); + $option['values'][] = $value; + } + } $options[] = $option; } diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminToggleProductGridColumnByClickingItsNameActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminToggleProductGridColumnByClickingItsNameActionGroup.xml new file mode 100644 index 0000000000000..36a43387ba57e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminToggleProductGridColumnByClickingItsNameActionGroup.xml @@ -0,0 +1,20 @@ + + + + + + + Click on 'Columns' name from Columns dropdown menu in Admin Product Grid. + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml index 28033aadbcf97..447af3f80ce2f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml @@ -39,6 +39,7 @@ + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml index 408669f29f1b3..526ac700a0b5a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml @@ -43,5 +43,6 @@ + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryTopToolbarSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryTopToolbarSection.xml index e063b5fc8c1b7..2c340add26267 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryTopToolbarSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryTopToolbarSection.xml @@ -14,5 +14,6 @@ + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeCategoryDisplaySettingsOnStorefrontTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeCategoryDisplaySettingsOnStorefrontTest.xml new file mode 100644 index 0000000000000..ec26e65c06789 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeCategoryDisplaySettingsOnStorefrontTest.xml @@ -0,0 +1,138 @@ + + + + + + + + + <description value="Verify correctness of Sorting, Navigation, Listing products at the Storefront Category"/> + <severity value="MAJOR"/> + <testCaseId value="AC-4150"/> + <group value="Catalog"/> + </annotations> + <before> + <!-- create category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <!-- create 11 simple products --> + <createData entity="SimpleProduct" stepKey="createSimpleProduct1"> + <field key="price">10</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct2"> + <field key="price">11</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct3"> + <field key="price">12</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct4"> + <field key="price">13</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct5"> + <field key="price">14</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct6"> + <field key="price">15</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct7"> + <field key="price">16</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct8"> + <field key="price">17</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct9"> + <field key="price">18</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct10"> + <field key="price">19</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct11"> + <field key="price">20</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct12"> + <field key="price">21</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData><createData entity="SimpleProduct" stepKey="createSimpleProduct13"> + <field key="price">22</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData><createData entity="SimpleProduct" stepKey="createSimpleProduct14"> + <field key="price">23</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData><createData entity="SimpleProduct" stepKey="createSimpleProduct15"> + <field key="price">24</field> + <field key="quantity">1000</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <!-- delete created entities --> + <deleteData createDataKey="createSimpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createSimpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="createSimpleProduct3" stepKey="deleteSimpleProduct3"/> + <deleteData createDataKey="createSimpleProduct4" stepKey="deleteSimpleProduct4"/> + <deleteData createDataKey="createSimpleProduct5" stepKey="deleteSimpleProduct5"/> + <deleteData createDataKey="createSimpleProduct6" stepKey="deleteSimpleProduct6"/> + <deleteData createDataKey="createSimpleProduct7" stepKey="deleteSimpleProduct7"/> + <deleteData createDataKey="createSimpleProduct8" stepKey="deleteSimpleProduct8"/> + <deleteData createDataKey="createSimpleProduct9" stepKey="deleteSimpleProduct9"/> + <deleteData createDataKey="createSimpleProduct10" stepKey="deleteSimpleProduct10"/> + <deleteData createDataKey="createSimpleProduct11" stepKey="deleteSimpleProduct11"/> + <deleteData createDataKey="createSimpleProduct12" stepKey="deleteSimpleProduct12"/> + <deleteData createDataKey="createSimpleProduct13" stepKey="deleteSimpleProduct13"/> + <deleteData createDataKey="createSimpleProduct14" stepKey="deleteSimpleProduct14"/> + <deleteData createDataKey="createSimpleProduct15" stepKey="deleteSimpleProduct15"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + </after> + <!-- Login to Admin page --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <!-- Open created category on Storefront --> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="openCategoryPage"> + <argument name="categoryName" value="$$createCategory.name$$"/> + </actionGroup> + <!-- Switch category view to List mode --> + <actionGroup ref="StorefrontSwitchCategoryViewToListModeActionGroup" stepKey="switchCategoryViewToListMode"/> + <!-- Sort products By Price --> + <actionGroup ref="StorefrontCategoryPageSortProductActionGroup" stepKey="sortProductByPrice"/> + <!-- Set Ascending Direction --> + <actionGroup ref="StorefrontCategoryPageSortAscendingActionGroup" stepKey="setAscendingDirection"/> + <!-- Sort products By ProductName --> + <selectOption selector="{{StorefrontCategoryTopToolbarSection.sortByDropdown}}" userInput="Product Name" stepKey="selectSortByProductName"/> + <!-- Set Descending Direction --> + <actionGroup ref="StorefrontCategoryPageSortDescendingActionGroup" stepKey="setDescendingDirection"/> + <selectOption selector="{{StorefrontCategoryTopToolbarSection.showDropdown}}" userInput="15" stepKey="selectShowProducts"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </test> +</tests> + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridCustomViewColumnDisplayTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridCustomViewColumnDisplayTest.xml new file mode 100644 index 0000000000000..bb6a1f1b20e7f --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridCustomViewColumnDisplayTest.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminProductGridCustomViewColumnDisplayTest"> + <annotations> + <stories value="Product grid columns visibility can be toggled by clicking on their names in the Columns drop-down menu, not just on their respective checkboxes."/> + <title value="Product grid columns visibility can be toggled by clicking on their names in the Columns drop-down menu, not just on their respective checkboxes."/> + <description value="Assert that after rearranging columns order on the product grid in Admin, it will still be possible to toggle the visibility of the columns by clicking on their names in the Columns dropdown menu, and not only on their respective checkboxes."/> + <severity value="AVERAGE"/> + <testCaseId value="AC-6229"/> + <useCaseId value="ACP2E-1073"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Log in as admin --> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="clearFilters"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="beginWithLogout"/> + </after> + <!-- Navigate to products list page --> + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="navigateToProductIndex"/> + <!-- Sort SKU and Name column position --> + <dragAndDrop selector1="{{AdminProductGridSection.columnHeader('SKU')}}" selector2="{{AdminProductGridSection.columnHeader('Name')}}" stepKey="dragAndDropColumnName"/> + <!-- Check if price column is visible in grid --> + <seeElement selector="{{AdminProductGridSection.columnHeader('Price')}}" stepKey="seeProductPriceColumn"/> + <!-- Click on columns, in dropdown uncheck price --> + <actionGroup ref="ToggleAdminProductGridColumnsDropdownActionGroup" stepKey="openColumnsDropdownMenuToTogglePrice"/> + <actionGroup ref="AdminToggleProductGridColumnByClickingItsNameActionGroup" stepKey="hidePriceColumn"> + <argument name="optionName" value="Price"/> + </actionGroup> + <actionGroup ref="ToggleAdminProductGridColumnsDropdownActionGroup" stepKey="closeColumnsDropdownMenuAfterTogglePrice"/> + <!-- Price column is not visible in product grid --> + <dontSeeElement selector="{{AdminProductGridSection.columnHeader('Price')}}" stepKey="dontSeeProductPriceColumn"/> + <!-- Check weight column is not visible in product grid --> + <dontSeeElement selector="{{AdminProductGridSection.columnHeader('Weight')}}" stepKey="dontSeeWeightColumn"/> + <!-- Click on columns, in dropdown click on column name weight --> + <actionGroup ref="ToggleAdminProductGridColumnsDropdownActionGroup" stepKey="openColumnsDropdownMenuToToggleWeight"/> + <actionGroup ref="AdminToggleProductGridColumnByClickingItsNameActionGroup" stepKey="showWeightColumn"> + <argument name="optionName" value="Weight"/> + </actionGroup> + <actionGroup ref="ToggleAdminProductGridColumnsDropdownActionGroup" stepKey="closeColumnsDropdownMenuAfterToggleWeight"/> + <seeElement selector="{{AdminProductGridSection.columnHeader('Weight')}}" stepKey="seeWeightColumn"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/composer.json b/app/code/Magento/Catalog/composer.json index 6597e88e9d995..4421b2991266b 100644 --- a/app/code/Magento/Catalog/composer.json +++ b/app/code/Magento/Catalog/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-asynchronous-operations": "*", diff --git a/app/code/Magento/CatalogAnalytics/composer.json b/app/code/Magento/CatalogAnalytics/composer.json index a41a47fa4764b..2710625d0f08d 100644 --- a/app/code/Magento/CatalogAnalytics/composer.json +++ b/app/code/Magento/CatalogAnalytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-catalog-analytics", "description": "N/A", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-analytics": "*" diff --git a/app/code/Magento/CatalogCmsGraphQl/composer.json b/app/code/Magento/CatalogCmsGraphQl/composer.json index cf9e76f3b2ea2..d1cff1a3e448f 100644 --- a/app/code/Magento/CatalogCmsGraphQl/composer.json +++ b/app/code/Magento/CatalogCmsGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-cms-graph-ql": "*" diff --git a/app/code/Magento/CatalogCustomerGraphQl/composer.json b/app/code/Magento/CatalogCustomerGraphQl/composer.json index b1743ae964966..5c4a301857c7e 100644 --- a/app/code/Magento/CatalogCustomerGraphQl/composer.json +++ b/app/code/Magento/CatalogCustomerGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilter.php b/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilter.php index 4350b6dd85266..d8b90b454b4a5 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilter.php +++ b/app/code/Magento/CatalogGraphQl/Model/Category/CategoryFilter.php @@ -7,13 +7,11 @@ namespace Magento\CatalogGraphQl\Model\Category; -use Magento\Catalog\Api\CategoryRepositoryInterface; -use Magento\Catalog\Api\Data\CategorySearchResultsInterface; -use Magento\Catalog\Api\Data\CategorySearchResultsInterfaceFactory; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; use Magento\CatalogGraphQl\Model\Resolver\Categories\DataProvider\Category\CollectionProcessorInterface; use Magento\CatalogGraphQl\Model\Category\Filter\SearchCriteria; use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface; +use Magento\Framework\DB\Select; use Magento\Framework\Exception\InputException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\GraphQl\Model\Query\ContextInterface; @@ -39,16 +37,6 @@ class CategoryFilter */ private $extensionAttributesJoinProcessor; - /** - * @var CategorySearchResultsInterfaceFactory - */ - private $categorySearchResultsFactory; - - /** - * @var CategoryRepositoryInterface - */ - private $categoryRepository; - /** * @var SearchCriteria */ @@ -58,23 +46,17 @@ class CategoryFilter * @param CollectionFactory $categoryCollectionFactory * @param CollectionProcessorInterface $collectionProcessor * @param JoinProcessorInterface $extensionAttributesJoinProcessor - * @param CategorySearchResultsInterfaceFactory $categorySearchResultsFactory - * @param CategoryRepositoryInterface $categoryRepository * @param SearchCriteria $searchCriteria */ public function __construct( CollectionFactory $categoryCollectionFactory, CollectionProcessorInterface $collectionProcessor, JoinProcessorInterface $extensionAttributesJoinProcessor, - CategorySearchResultsInterfaceFactory $categorySearchResultsFactory, - CategoryRepositoryInterface $categoryRepository, SearchCriteria $searchCriteria ) { $this->categoryCollectionFactory = $categoryCollectionFactory; $this->collectionProcessor = $collectionProcessor; $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor; - $this->categorySearchResultsFactory = $categorySearchResultsFactory; - $this->categoryRepository = $categoryRepository; $this->searchCriteria = $searchCriteria; } @@ -95,22 +77,21 @@ public function getResult(array $criteria, StoreInterface $store, array $attribu $this->extensionAttributesJoinProcessor->process($collection); $this->collectionProcessor->process($collection, $searchCriteria, $attributeNames, $context); - /** @var CategorySearchResultsInterface $searchResult */ - $categories = $this->categorySearchResultsFactory->create(); - $categories->setSearchCriteria($searchCriteria); - $categories->setItems($collection->getItems()); - $categories->setTotalCount($collection->getSize()); + // only fetch necessary category entity id + $collection + ->getSelect() + ->reset(Select::COLUMNS) + ->columns( + 'e.entity_id' + ); - $categoryIds = []; - foreach ($categories->getItems() as $category) { - $categoryIds[] = (int)$category->getId(); - } + $categoryIds = $collection->load()->getLoadedIds(); $totalPages = 0; - if ($categories->getTotalCount() > 0 && $searchCriteria->getPageSize() > 0) { - $totalPages = ceil($categories->getTotalCount() / $searchCriteria->getPageSize()); + if ($collection->getSize() > 0 && $searchCriteria->getPageSize() > 0) { + $totalPages = ceil($collection->getSize() / $searchCriteria->getPageSize()); } - if ($searchCriteria->getCurrentPage() > $totalPages && $categories->getTotalCount() > 0) { + if ($searchCriteria->getCurrentPage() > $totalPages && $collection->getSize() > 0) { throw new GraphQlInputException( __( 'currentPage value %1 specified is greater than the %2 page(s) available.', @@ -121,7 +102,7 @@ public function getResult(array $criteria, StoreInterface $store, array $attribu return [ 'category_ids' => $categoryIds, - 'total_count' => $categories->getTotalCount(), + 'total_count' => $collection->getSize(), 'page_info' => [ 'total_pages' => $totalPages, 'page_size' => $searchCriteria->getPageSize(), diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php b/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php index 675118b953102..fc234c1de4e4a 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php +++ b/app/code/Magento/CatalogGraphQl/Model/Category/Hydrator.php @@ -60,8 +60,12 @@ public function hydrateCategory(Category $category, $basicFieldsOnly = false) : if ($basicFieldsOnly) { $categoryData = $category->getData(); } else { - $categoryData = $this->dataObjectProcessor->buildOutputDataArray($category, CategoryInterface::class); + $categoryData = $this->dataObjectProcessor->buildOutputDataArray( + $category, + CategoryInterface::class + ); } + $categoryData['id'] = $category->getId(); $categoryData['uid'] = $this->uidEncoder->encode((string) $category->getId()); $categoryData['children'] = []; diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php index 86715623eb9fb..2cbfcafdb6746 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoriesQuery.php @@ -10,12 +10,15 @@ use Magento\CatalogGraphQl\Model\Category\CategoryFilter; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree; +use Magento\Framework\Api\Search\SearchCriteriaFactory; use Magento\Framework\Exception\InputException; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\Resolver\ArgumentsProcessorInterface; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\GraphQl\Model\Query\ContextInterface; +use Magento\Store\Api\Data\StoreInterface; /** * Categories resolver, used for GraphQL category data request processing. @@ -42,22 +45,30 @@ class CategoriesQuery implements ResolverInterface */ private $argsSelection; + /** + * @var SearchCriteriaFactory + */ + private $searchCriteriaFactory; + /** * @param CategoryTree $categoryTree * @param ExtractDataFromCategoryTree $extractDataFromCategoryTree * @param CategoryFilter $categoryFilter * @param ArgumentsProcessorInterface $argsSelection + * @param SearchCriteriaFactory $searchCriteriaFactory */ public function __construct( CategoryTree $categoryTree, ExtractDataFromCategoryTree $extractDataFromCategoryTree, CategoryFilter $categoryFilter, - ArgumentsProcessorInterface $argsSelection + ArgumentsProcessorInterface $argsSelection, + SearchCriteriaFactory $searchCriteriaFactory ) { $this->categoryTree = $categoryTree; $this->extractDataFromCategoryTree = $extractDataFromCategoryTree; $this->categoryFilter = $categoryFilter; $this->argsSelection = $argsSelection; + $this->searchCriteriaFactory = $searchCriteriaFactory; } /** @@ -87,7 +98,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $rootCategoryIds = $filterResult['category_ids'] ?? []; - $filterResult['items'] = $this->fetchCategories($rootCategoryIds, $info, (int) $store->getId()); + $filterResult['items'] = $this->fetchCategories($rootCategoryIds, $info, $store, $context); return $filterResult; } @@ -96,17 +107,28 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value * * @param array $categoryIds * @param ResolveInfo $info - * @param int $storeId + * @param StoreInterface $store + * @param ContextInterface $context * @return array */ - private function fetchCategories(array $categoryIds, ResolveInfo $info, int $storeId) - { + private function fetchCategories( + array $categoryIds, + ResolveInfo $info, + StoreInterface $store, + ContextInterface $context + ) { $fetchedCategories = []; foreach ($categoryIds as $categoryId) { - $categoryTree = $this->categoryTree->getTree($info, $categoryId, $storeId); - if (empty($categoryTree)) { - continue; - } + /* Search Criteria is created for compatibility */ + $searchCriteria = $this->searchCriteriaFactory->create(); + $categoryTree = $this->categoryTree->getFilteredTree( + $info, + $categoryId, + $searchCriteria, + $store, + [], + $context + ); $fetchedCategories[] = current($this->extractDataFromCategoryTree->execute($categoryTree)); } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php index e2b045c36f4d3..0d857604cd04a 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php @@ -7,6 +7,7 @@ namespace Magento\CatalogGraphQl\Model\Resolver; +use Magento\CatalogGraphQl\Model\Category\Filter\SearchCriteria; use Magento\Store\Api\Data\StoreInterface; use Magento\GraphQl\Model\Query\ContextInterface; use Magento\CatalogGraphQl\Model\Category\CategoryFilter; @@ -45,22 +46,30 @@ class CategoryList implements ResolverInterface */ private $argsSelection; + /** + * @var SearchCriteria + */ + private $searchCriteria; + /** * @param CategoryTree $categoryTree * @param ExtractDataFromCategoryTree $extractDataFromCategoryTree * @param CategoryFilter $categoryFilter * @param ArgumentsProcessorInterface $argsSelection + * @param SearchCriteria $searchCriteria */ public function __construct( CategoryTree $categoryTree, ExtractDataFromCategoryTree $extractDataFromCategoryTree, CategoryFilter $categoryFilter, - ArgumentsProcessorInterface $argsSelection + ArgumentsProcessorInterface $argsSelection, + SearchCriteria $searchCriteria ) { $this->categoryTree = $categoryTree; $this->extractDataFromCategoryTree = $extractDataFromCategoryTree; $this->categoryFilter = $categoryFilter; $this->argsSelection = $argsSelection; + $this->searchCriteria = $searchCriteria; } /** @@ -105,21 +114,19 @@ private function fetchCategories( array $criteria, StoreInterface $store, array $attributeNames, - $context + ContextInterface $context ) : array { $fetchedCategories = []; foreach ($categoryIds as $categoryId) { + $searchCriteria = $this->searchCriteria->buildCriteria($criteria, $store); $categoryTree = $this->categoryTree->getFilteredTree( $info, $categoryId, - $criteria, + $searchCriteria, $store, $attributeNames, $context ); - if (empty($categoryTree)) { - continue; - } $fetchedCategories[] = current($this->extractDataFromCategoryTree->execute($categoryTree)); } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php index 81ca0fe9d2c9f..a5cc522d7ccf0 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php @@ -17,9 +17,9 @@ use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; use Magento\CatalogGraphQl\Model\AttributesJoiner; use Magento\CatalogGraphQl\Model\Category\DepthCalculator; -use Magento\CatalogGraphQl\Model\Category\Filter\SearchCriteria; use Magento\CatalogGraphQl\Model\Category\LevelCalculator; use Magento\CatalogGraphQl\Model\Resolver\Categories\DataProvider\Category\CollectionProcessorInterface; +use Magento\Framework\Api\Search\SearchCriteria; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; @@ -68,11 +68,6 @@ class CategoryTree */ private $collectionProcessor; - /** - * @var SearchCriteria - */ - private $searchCriteria; - /** * @param CollectionFactory $collectionFactory * @param AttributesJoiner $attributesJoiner @@ -80,7 +75,6 @@ class CategoryTree * @param LevelCalculator $levelCalculator * @param MetadataPool $metadata * @param CollectionProcessorInterface $collectionProcessor - * @param SearchCriteria $searchCriteria */ public function __construct( CollectionFactory $collectionFactory, @@ -88,8 +82,7 @@ public function __construct( DepthCalculator $depthCalculator, LevelCalculator $levelCalculator, MetadataPool $metadata, - CollectionProcessorInterface $collectionProcessor, - SearchCriteria $searchCriteria + CollectionProcessorInterface $collectionProcessor ) { $this->collectionFactory = $collectionFactory; $this->attributesJoiner = $attributesJoiner; @@ -97,7 +90,6 @@ public function __construct( $this->levelCalculator = $levelCalculator; $this->metadata = $metadata; $this->collectionProcessor = $collectionProcessor; - $this->searchCriteria = $searchCriteria; } /** @@ -201,23 +193,23 @@ private function joinAttributesRecursively( * * @param ResolveInfo $resolveInfo * @param int $rootCategoryId - * @param array $criteria + * @param SearchCriteria $searchCriteria * @param StoreInterface $store * @param array $attributeNames * @param ContextInterface $context * @return Iterator * @throws LocalizedException * @throws Exception + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getFilteredTree( ResolveInfo $resolveInfo, int $rootCategoryId, - array $criteria, + SearchCriteria $searchCriteria, StoreInterface $store, array $attributeNames, ContextInterface $context ): Iterator { - $searchCriteria = $this->searchCriteria->buildCriteria($criteria, $store); $collection = $this->getCollection($resolveInfo, $rootCategoryId); $this->collectionProcessor->process($collection, $searchCriteria, $attributeNames, $context); return $collection->getIterator(); diff --git a/app/code/Magento/CatalogGraphQl/composer.json b/app/code/Magento/CatalogGraphQl/composer.json index c289f84a359ba..fbc4172226c58 100644 --- a/app/code/Magento/CatalogGraphQl/composer.json +++ b/app/code/Magento/CatalogGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-eav": "*", "magento/module-catalog": "*", "magento/module-catalog-inventory": "*", diff --git a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml index 369f4bfb23034..0e0fa9d95580f 100644 --- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml @@ -191,6 +191,11 @@ <type name="Magento\Catalog\Api\ProductRepositoryInterface"> <plugin name="availableProductsFilter" type="Magento\CatalogGraphQl\Plugin\AvailableProductsFilter" /> </type> + <type name="Magento\CatalogGraphQl\Model\Category\Hydrator"> + <arguments> + <argument name="dataObjectProcessor" xsi:type="object">Magento\CatalogGraphQl\Category\DataObjectProcessor</argument> + </arguments> + </type> <virtualType name="Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ChildProduct" type="Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product"> <arguments> @@ -207,4 +212,16 @@ </argument> </arguments> </virtualType> + <virtualType + name="Magento\CatalogGraphQl\Category\DataObjectProcessor" + type="Magento\Framework\Reflection\DataObjectProcessor" + > + <arguments> + <argument name="excludedMethodsClassMap" xsi:type="array"> + <item name="Magento\Catalog\Api\Data\CategoryInterface" xsi:type="array"> + <item name="getChildren" xsi:type="string">getChildren</item> + </item> + </argument> + </arguments> + </virtualType> </config> diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php index 84d36be94900c..bd64982c0f291 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php @@ -7,14 +7,25 @@ namespace Magento\CatalogImportExport\Model\Import\Product; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\ProductFactory; +use Magento\Catalog\Model\ResourceModel\Product\Option\CollectionFactory; use Magento\Catalog\Model\ResourceModel\Product\Option\Value\Collection as ProductOptionValueCollection; use Magento\Catalog\Model\ResourceModel\Product\Option\Value\CollectionFactory as ProductOptionValueCollectionFactory; use Magento\CatalogImportExport\Model\Import\Product; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\ImportExport\Model\Import; use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; use Magento\ImportExport\Model\ResourceModel\CollectionByPagesIterator; +use Magento\ImportExport\Model\ResourceModel\CollectionByPagesIteratorFactory; +use Magento\ImportExport\Model\ResourceModel\Helper; +use Magento\ImportExport\Model\ResourceModel\Import\Data; use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; /** * Entity class which provide possibility to import product custom options @@ -334,26 +345,40 @@ class Option extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity private $optionTypeTitles; /** + * @var TransactionManagerInterface|null + */ + private $transactionManager; + + /** + * Contains mapping between new assigned option ID and ID in DB + * + * @var array + */ + private $optionNewIdExistingIdMap = []; + + /** + * Contains mapping between new assigned option_type ID and ID in DB + * * @var array */ - private $lastOptionTitle; + private $optionTypeNewIdExistingIdMap = []; /** - * @param \Magento\ImportExport\Model\ResourceModel\Import\Data $importData + * @param Data $importData * @param ResourceConnection $resource - * @param \Magento\ImportExport\Model\ResourceModel\Helper $resourceHelper - * @param \Magento\Store\Model\StoreManagerInterface $_storeManager - * @param \Magento\Catalog\Model\ProductFactory $productFactory - * @param \Magento\Catalog\Model\ResourceModel\Product\Option\CollectionFactory $optionColFactory - * @param \Magento\ImportExport\Model\ResourceModel\CollectionByPagesIteratorFactory $colIteratorFactory + * @param Helper $resourceHelper + * @param StoreManagerInterface $_storeManager + * @param ProductFactory $productFactory + * @param CollectionFactory $optionColFactory + * @param CollectionByPagesIteratorFactory $colIteratorFactory * @param \Magento\Catalog\Helper\Data $catalogData - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig - * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $dateTime + * @param ScopeConfigInterface $scopeConfig + * @param TimezoneInterface $dateTime * @param ProcessingErrorAggregatorInterface $errorAggregator * @param array $data - * @param ProductOptionValueCollectionFactory $productOptionValueCollectionFactory - * @throws \Magento\Framework\Exception\LocalizedException - * + * @param ProductOptionValueCollectionFactory|null $productOptionValueCollectionFactory + * @param TransactionManagerInterface|null $transactionManager + * @throws LocalizedException * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -369,7 +394,8 @@ public function __construct( \Magento\Framework\Stdlib\DateTime\TimezoneInterface $dateTime, ProcessingErrorAggregatorInterface $errorAggregator, array $data = [], - ProductOptionValueCollectionFactory $productOptionValueCollectionFactory = null + ProductOptionValueCollectionFactory $productOptionValueCollectionFactory = null, + ?TransactionManagerInterface $transactionManager = null ) { $this->_resource = $resource; $this->_catalogData = $catalogData; @@ -381,7 +407,9 @@ public function __construct( $this->_scopeConfig = $scopeConfig; $this->dateTime = $dateTime; $this->productOptionValueCollectionFactory = $productOptionValueCollectionFactory - ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ProductOptionValueCollectionFactory::class); + ?: ObjectManager::getInstance()->get(ProductOptionValueCollectionFactory::class); + $this->transactionManager = $transactionManager + ?: ObjectManager::getInstance()->get(TransactionManagerInterface::class); if (isset($data['connection'])) { $this->_connection = $data['connection']; @@ -798,10 +826,12 @@ protected function _findExistingOptionId(array $newOptionData, array $newOptionT ksort($newOptionTitles); $existingOptions = $this->getOldCustomOptions()[$productId]; foreach ($existingOptions as $optionId => $optionData) { - if ($optionData['type'] == $newOptionData['type'] - && $optionData['titles'][Store::DEFAULT_STORE_ID] == $newOptionTitles[Store::DEFAULT_STORE_ID] - ) { - return $optionId; + if ($optionData['type'] == $newOptionData['type']) { + foreach ($newOptionTitles as $storeId => $title) { + if (isset($optionData['titles'][$storeId]) && $optionData['titles'][$storeId] === $title) { + return $optionId; + } + } } } } @@ -1249,13 +1279,16 @@ private function addFileOptions($result, $optionRow) protected function _importData() { $this->_initProductsSku(); - $nextOptionId = $this->_resourceHelper->getNextAutoincrement($this->_tables['catalog_product_option']); - $nextValueId = $this->_resourceHelper->getNextAutoincrement( + $nextOptionId = (int) $this->_resourceHelper->getNextAutoincrement($this->_tables['catalog_product_option']); + $nextValueId = (int) $this->_resourceHelper->getNextAutoincrement( $this->_tables['catalog_product_option_type_value'] ); $prevOptionId = 0; $optionId = null; $valueId = null; + $this->optionNewIdExistingIdMap = []; + $this->optionTypeNewIdExistingIdMap = []; + $prevRowSku = null; while ($bunch = $this->_dataSourceModel->getNextUniqueBunch($this->getIds())) { $products = []; $options = []; @@ -1268,11 +1301,14 @@ protected function _importData() $childCount = []; $optionsToRemove = []; foreach ($bunch as $rowNumber => $rowData) { - if (isset($optionId, $valueId) && - (empty($rowData[PRODUCT::COL_STORE_VIEW_CODE]) || empty($rowData['custom_options'])) - ) { - $nextOptionId = $optionId; - $nextValueId = $valueId; + $rowSku = !empty($rowData[self::COLUMN_SKU]) + ? mb_strtolower($rowData[self::COLUMN_SKU]) + : ''; + + if ($rowSku !== $prevRowSku) { + $nextOptionId = $optionId ?? $nextOptionId; + $nextValueId = $valueId ?? $nextValueId; + $prevRowSku = $rowSku; } $optionId = $nextOptionId; $valueId = $nextValueId; @@ -1306,8 +1342,8 @@ protected function _importData() $products, $prices ); - if ($optionData != null) { - $options[] = $optionData; + if ($optionData) { + $options[$optionData['option_id']] = $optionData; } $this->_collectOptionTypeData( $combinedData, @@ -1321,15 +1357,6 @@ protected function _importData() ); $this->_collectOptionTitle($combinedData, $prevOptionId, $titles); - $this->checkOptionTitles( - $options, - $titles, - $combinedData, - $prevOptionId, - $optionId, - $products, - $prices - ); } } $this->removeExistingOptions($products, $optionsToRemove); @@ -1338,76 +1365,20 @@ protected function _importData() 'prices' => $typePrices, 'titles' => $typeTitles, ]; - $this->setLastOptionTitle($titles); //Save prepared custom options data. $this->savePreparedCustomOptions( $products, - $options, + array_values($options), $titles, $prices, $types ); + $this->optionNewIdExistingIdMap = $this->markNewIdsAsExisting($this->optionNewIdExistingIdMap); + $this->optionTypeNewIdExistingIdMap = $this->markNewIdsAsExisting($this->optionTypeNewIdExistingIdMap); } return true; } - /** - * Check options titles. - * - * If products were split up between bunches, - * this function will add needed option for option titles - * - * @param array $options - * @param array $titles - * @param array $combinedData - * @param int $prevOptionId - * @param int $optionId - * @param array $products - * @param array $prices - * @return void - */ - private function checkOptionTitles( - array &$options, - array &$titles, - array $combinedData, - int &$prevOptionId, - int &$optionId, - array $products, - array $prices - ) : void { - $titlesCount = count($titles); - if ($titlesCount > 0 && count($options) !== $titlesCount) { - $combinedData[Product::COL_STORE_VIEW_CODE] = ''; - $optionId--; - $option = $this->_collectOptionMainData( - $combinedData, - $prevOptionId, - $optionId, - $products, - $prices - ); - if ($option) { - $options[] = $option; - } - } - } - - /** - * Setting last Custom Option Title - * to use it later in _collectOptionTitle - * to set correct title for default store view - * - * @param array $titles - */ - private function setLastOptionTitle(array &$titles) : void - { - if (count($titles) > 0) { - end($titles); - $key = key($titles); - $this->lastOptionTitle[$key] = $titles[$key]; - } - } - /** * Remove existing options. * @@ -1469,9 +1440,7 @@ protected function _collectOptionMainData( $optionData = null; if ($this->_rowIsMain) { - $optionData = empty($rowData[Product::COL_STORE_VIEW_CODE]) - ? $this->_getOptionData($rowData, $this->_rowProductId, $nextOptionId, $this->_rowType) - : ''; + $optionData = $this->_getOptionData($rowData, $this->_rowProductId, $nextOptionId, $this->_rowType); if (!$this->_isRowHasSpecificType($this->_rowType) && ($priceData = $this->_getPriceData($rowData, $nextOptionId, $this->_rowType)) @@ -1519,38 +1488,21 @@ protected function _collectOptionTypeData( array &$childCount ) { if ($this->_isRowHasSpecificType($this->_rowType) && $prevOptionId) { - $specificTypeData = $this->_getSpecificTypeData($rowData, $nextValueId); - //For default store + $specificTypeData = $this->_getSpecificTypeData([self::COLUMN_STORE => null] + $rowData, $nextValueId); if ($specificTypeData) { - $typeValues[$prevOptionId][] = $specificTypeData['value']; + $typeValues[$prevOptionId][$nextValueId] = $specificTypeData['value']; + $typeTitles[$nextValueId][$this->_rowStoreId] = $specificTypeData['title']; - // ensure default title is set - if (!isset($typeTitles[$nextValueId][Store::DEFAULT_STORE_ID])) { - $typeTitles[$nextValueId][Store::DEFAULT_STORE_ID] = $specificTypeData['title']; - } - - if ($specificTypeData['price']) { + if (!empty($specificTypeData['price'])) { if ($this->_isPriceGlobal) { $typePrices[$nextValueId][Store::DEFAULT_STORE_ID] = $specificTypeData['price']; } else { - // ensure default price is set - if (!isset($typePrices[$nextValueId][Store::DEFAULT_STORE_ID])) { - $typePrices[$nextValueId][Store::DEFAULT_STORE_ID] = $specificTypeData['price']; - } $typePrices[$nextValueId][$this->_rowStoreId] = $specificTypeData['price']; } } $nextValueId++; } - $specificTypeData = $this->_getSpecificTypeData($rowData, 0, false); - //For others stores - if ($specificTypeData) { - if (isset($specificTypeData['price'])) { - $typePrices[$nextValueId][$this->_rowStoreId] = $specificTypeData['price']; - } - $typeTitles[$nextValueId++][$this->_rowStoreId] = $specificTypeData['title']; - } } } @@ -1564,16 +1516,7 @@ protected function _collectOptionTypeData( */ protected function _collectOptionTitle(array $rowData, $prevOptionId, array &$titles) { - $defaultStoreId = Store::DEFAULT_STORE_ID; if (!empty($rowData[self::COLUMN_TITLE])) { - if (!isset($titles[$prevOptionId][$defaultStoreId])) { - if (isset($this->lastOptionTitle[$prevOptionId])) { - $titles[$prevOptionId] = $this->lastOptionTitle[$prevOptionId]; - unset($this->lastOptionTitle); - } else { - $titles[$prevOptionId][$defaultStoreId] = $rowData[self::COLUMN_TITLE]; - } - } $titles[$prevOptionId][$this->_rowStoreId] = $rowData[self::COLUMN_TITLE]; } } @@ -1592,7 +1535,10 @@ protected function _compareOptionsWithExisting(array &$options, array &$titles, { foreach ($options as &$optionData) { $newOptionId = $optionData['option_id']; - if ($optionId = $this->_findExistingOptionId($optionData, $titles[$newOptionId])) { + $optionId = $this->optionNewIdExistingIdMap[$newOptionId] + ?? $this->_findExistingOptionId($optionData, $titles[$newOptionId]); + $this->optionNewIdExistingIdMap[$newOptionId] = $optionId ?: null; + if ($optionId && (int) $optionId !== (int) $newOptionId) { $optionData['option_id'] = $optionId; $titles[$optionId] = $titles[$newOptionId]; unset($titles[$newOptionId]); @@ -1600,6 +1546,8 @@ protected function _compareOptionsWithExisting(array &$options, array &$titles, foreach ($prices[$newOptionId] as $storeId => $priceStoreData) { $prices[$newOptionId][$storeId]['option_id'] = $optionId; } + $prices[$optionId] = $prices[$newOptionId]; + unset($prices[$newOptionId]); } if (isset($typeValues[$newOptionId])) { $typeValues[$optionId] = $typeValues[$newOptionId]; @@ -1627,8 +1575,10 @@ private function restoreOriginalOptionTypeIds(array &$typeValues, array &$typePr foreach ($optionTypes as &$optionType) { $optionTypeId = $optionType['option_type_id']; foreach ($typeTitles[$optionTypeId] as $storeId => $optionTypeTitle) { - $existingTypeId = $this->getExistingOptionTypeId($optionId, $storeId, $optionTypeTitle); - if ($existingTypeId) { + $existingTypeId = $this->optionTypeNewIdExistingIdMap[$optionTypeId] + ?? $this->getExistingOptionTypeId($optionId, $storeId, $optionTypeTitle); + $this->optionTypeNewIdExistingIdMap[$optionTypeId] = $existingTypeId ?: null; + if ($existingTypeId && (int) $existingTypeId !== (int) $optionTypeId) { $optionType['option_type_id'] = $existingTypeId; $typeTitles[$existingTypeId] = $typeTitles[$optionTypeId]; unset($typeTitles[$optionTypeId]); @@ -1812,7 +1762,7 @@ protected function _getPriceData(array $rowData, $optionId, $type) ) { $priceData = [ 'option_id' => $optionId, - 'store_id' => $this->_rowStoreId, + 'store_id' => $this->_isPriceGlobal ? Store::DEFAULT_STORE_ID : $this->_rowStoreId, 'price_type' => 'fixed', ]; @@ -1920,7 +1870,12 @@ protected function _saveOptions(array $options) protected function _saveTitles(array $titles) { $titleRows = []; + $existingOptionIds = array_flip(array_filter($this->optionNewIdExistingIdMap)); foreach ($titles as $optionId => $storeInfo) { + // Check that if it is a new option, then make sure a record for default store will be created + if (!isset($existingOptionIds[$optionId]) && count($storeInfo) > 0) { + $storeInfo = [Store::DEFAULT_STORE_ID => reset($storeInfo)] + $storeInfo; + } //for use default $uniqStoreInfo = array_unique($storeInfo); foreach ($uniqStoreInfo as $storeId => $title) { @@ -1948,7 +1903,12 @@ protected function _savePrices(array $prices) { if ($prices) { $optionPriceRows = []; - foreach ($prices as $storesData) { + $existingOptionIds = array_flip(array_filter($this->optionNewIdExistingIdMap)); + foreach ($prices as $optionId => $storesData) { + // Check that if it is a new option, then make sure a record for default store will be created + if (!isset($existingOptionIds[$optionId]) && count($storesData) > 0) { + $storesData = [Store::DEFAULT_STORE_ID => reset($storesData)] + $storesData; + } foreach ($storesData as $row) { $optionPriceRows[] = $row; } @@ -1973,8 +1933,6 @@ protected function _savePrices(array $prices) */ protected function _saveSpecificTypeValues(array $typeValues) { - $this->_deleteSpecificTypeValues(array_keys($typeValues)); - $typeValueRows = []; foreach ($typeValues as $optionId => $optionInfo) { foreach ($optionInfo as $row) { @@ -1983,7 +1941,7 @@ protected function _saveSpecificTypeValues(array $typeValues) } } if ($typeValueRows) { - $this->_connection->insertMultiple($this->_tables['catalog_product_option_type_value'], $typeValueRows); + $this->_connection->insertOnDuplicate($this->_tables['catalog_product_option_type_value'], $typeValueRows); } return $this; @@ -1998,7 +1956,12 @@ protected function _saveSpecificTypeValues(array $typeValues) protected function _saveSpecificTypePrices(array $typePrices) { $optionTypePriceRows = []; + $existingOptionTypeIds = array_flip(array_filter($this->optionTypeNewIdExistingIdMap)); foreach ($typePrices as $optionTypeId => $storesData) { + // Check that if it is a new option value, then make sure a record for default store will be created + if (!isset($existingOptionTypeIds[$optionTypeId]) && count($storesData) > 0) { + $storesData = [Store::DEFAULT_STORE_ID => reset($storesData)] + $storesData; + } foreach ($storesData as $storeId => $row) { $row['option_type_id'] = $optionTypeId; $row['store_id'] = $storeId; @@ -2025,7 +1988,12 @@ protected function _saveSpecificTypePrices(array $typePrices) protected function _saveSpecificTypeTitles(array $typeTitles) { $optionTypeTitleRows = []; + $existingOptionTypeIds = array_flip(array_filter($this->optionTypeNewIdExistingIdMap)); foreach ($typeTitles as $optionTypeId => $storesData) { + // Check that if it is a new option value, then make sure a record for default store will be created + if (!isset($existingOptionTypeIds[$optionTypeId]) && count($storesData) > 0) { + $storesData = [Store::DEFAULT_STORE_ID => reset($storesData)] + $storesData; + } //for use default $uniqStoresData = array_unique($storesData); foreach ($uniqStoresData as $storeId => $title) { @@ -2180,14 +2148,35 @@ private function savePreparedCustomOptions( $this->_compareOptionsWithExisting($options, $titles, $prices, $types['values']); $this->restoreOriginalOptionTypeIds($types['values'], $types['prices'], $types['titles']); } - - $this->_saveOptions($options) - ->_saveTitles($titles) - ->_savePrices($prices) - ->_saveSpecificTypeValues($types['values']) - ->_saveSpecificTypePrices($types['prices']) - ->_saveSpecificTypeTitles($types['titles']) - ->_updateProducts($products); + $this->transactionManager->start($this->_connection); + try { + $this->_saveOptions($options) + ->_saveTitles($titles) + ->_savePrices($prices) + ->_saveSpecificTypeValues($types['values']) + ->_saveSpecificTypePrices($types['prices']) + ->_saveSpecificTypeTitles($types['titles']) + ->_updateProducts($products); + $this->transactionManager->commit(); + } catch (\Throwable $exception) { + $this->transactionManager->rollBack(); + throw $exception; + } } } + + /** + * Mark new IDs as existing IDs + * + * @param array $idsMap + * @return array + */ + private function markNewIdsAsExisting(array $idsMap): array + { + $newIds = array_keys(array_filter($idsMap, 'is_null')); + return array_replace( + $idsMap, + array_combine($newIds, $newIds) + ); + } } diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php index d60a18057a42e..8268f52271299 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php @@ -17,6 +17,7 @@ use Magento\Framework\Data\Collection\AbstractDb; use Magento\Framework\Data\Collection\Db\FetchStrategyInterface; use Magento\Framework\Data\Collection\EntityFactory; +use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\EntityMetadata; use Magento\Framework\EntityManager\MetadataPool; @@ -141,7 +142,7 @@ class OptionTest extends AbstractImportTestCase */ protected $_expectedOptions = [ [ - 'option_id' => 1, + 'option_id' => 2, 'sku' => '1-text', 'max_characters' => '100', 'file_extension' => null, @@ -150,10 +151,10 @@ class OptionTest extends AbstractImportTestCase 'product_id' => 1, 'type' => 'field', 'is_require' => 1, - 'sort_order' => 0 + 'sort_order' => 1 ], [ - 'option_id' => 2, + 'option_id' => 3, 'sku' => '2-date', 'max_characters' => 0, 'file_extension' => null, @@ -162,10 +163,10 @@ class OptionTest extends AbstractImportTestCase 'product_id' => 1, 'type' => 'date_time', 'is_require' => 1, - 'sort_order' => 0 + 'sort_order' => 2 ], [ - 'option_id' => 3, + 'option_id' => 4, 'sku' => '', 'max_characters' => 0, 'file_extension' => null, @@ -174,10 +175,10 @@ class OptionTest extends AbstractImportTestCase 'product_id' => 1, 'type' => 'drop_down', 'is_require' => 1, - 'sort_order' => 0 + 'sort_order' => 3 ], [ - 'option_id' => 4, + 'option_id' => 5, 'sku' => '', 'max_characters' => 0, 'file_extension' => null, @@ -186,7 +187,7 @@ class OptionTest extends AbstractImportTestCase 'product_id' => 1, 'type' => 'radio', 'is_require' => 1, - 'sort_order' => 0 + 'sort_order' => 4 ] ]; @@ -297,7 +298,8 @@ protected function setUp(): void ProcessingErrorAggregatorInterface::class ), $this->_getModelDependencies($addExpectations, $deleteBehavior, $doubleOptions), - $optionValueCollectionFactoryMock + $optionValueCollectionFactoryMock, + $this->createMock(\Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface::class), ]; $modelClassName = Option::class; @@ -337,22 +339,20 @@ protected function _getModelDependencies( bool $deleteBehavior = false, bool $doubleOptions = false ): array { - $connection = $this->getMockBuilder(\stdClass::class)->addMethods( - ['delete', 'quoteInto', 'insertMultiple', 'insertOnDuplicate'] - ) + $connection = $this->getMockBuilder(AdapterInterface::class) ->disableOriginalConstructor() - ->getMock(); + ->getMockForAbstractClass(); if ($addExpectations) { if ($deleteBehavior) { $connection->expects( - $this->exactly(2) + $this->exactly(1) )->method( 'quoteInto' )->willReturnCallback( [$this, 'stubQuoteInto'] ); $connection->expects( - $this->exactly(2) + $this->exactly(1) )->method( 'delete' )->willReturnCallback( @@ -360,14 +360,7 @@ protected function _getModelDependencies( ); } else { $connection->expects( - $this->once() - )->method( - 'insertMultiple' - )->willReturnCallback( - [$this, 'verifyInsertMultiple'] - ); - $connection->expects( - $this->exactly(6) + $this->exactly(7) )->method( 'insertOnDuplicate' )->willReturnCallback( @@ -618,6 +611,12 @@ public function verifyInsertMultiple(string $table, array $data): void public function verifyInsertOnDuplicate(string $table, array $data, array $fields = []): void { switch ($table) { + case $this->_tables['catalog_product_option']: + $this->assertEquals($this->_expectedOptions, $data); + break; + case $this->_tables['catalog_product_option_type_value']: + $this->assertEquals($this->_expectedTypeValues, $data); + break; case $this->_tables['catalog_product_option_title']: $this->assertEquals($this->_expectedTitles, $data); $this->assertEquals(['title'], $fields); diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/_files/product_with_custom_options.csv b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/_files/product_with_custom_options.csv index 92cad357803c7..02cc55ef3de69 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/_files/product_with_custom_options.csv +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/_files/product_with_custom_options.csv @@ -1,2 +1,2 @@ -sku,website_code,store_view_code,attribute_set_code,product_type,name,description,short_description,weight,product_online,visibility,product_websites,categories,price,special_price,special_price_from_date,special_price_to_date,tax_class_name,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,additional_images,additional_image_labels,configurable_variation_labels,configurable_variations,bundle_price_type,bundle_sku_type,bundle_weight_type,bundle_values,downloadble_samples,downloadble_links,associated_skus,related_skus,crosssell_skus,upsell_skus,custom_options,additional_attributes,manage_stock,is_in_stock,qty,out_of_stock_qty,is_qty_decimal,allow_backorders,min_cart_qty,max_cart_qty,notify_on_stock_below,qty_increments,enable_qty_increments,is_decimal_divided,new_from_date,new_to_date,gift_message_available,created_at,updated_at,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_price,msrp_display_actual_price_type,map_enabled -simple,base,,Default,simple,New Product,,,,,,,,10,,,,,new-product,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title,type=field,required=1;sku=1-text,price=0,price_type=fixed|name=Test Date and Time Title,type=date_time,required=1,price=2,option_title=custom option 1,sku=2-date|name=Test Select,type=drop_down,required=1,price=3,option_title=Option 1,sku=3-1-select|name=Test Select,type=drop_down,required=1,price=3,option_title=Option 2,sku=3-2-select|name=Test Radio,type=radio,required=1,price=3,option_title=Option 1,sku=4-1-radio|name=Test Radio,type=radio,required=1,price=3,option_title=Option 2,sku=4-2-radio",,,,,,,,,,,,,,,,,,,,,,,,Block after Info Column,,, +sku,website_code,store_view_code,attribute_set_code,product_type,name,description,short_description,weight,product_online,visibility,product_websites,categories,price,special_price,special_price_from_date,special_price_to_date,tax_class_name,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,additional_images,additional_image_labels,configurable_variation_labels,configurable_variations,bundle_price_type,bundle_sku_type,bundle_weight_type,bundle_values,downloadble_samples,downloadble_links,associated_skus,related_skus,crosssell_skus,upsell_skus,custom_options,additional_attributes,manage_stock,is_in_stock,qty,out_of_stock_qty,is_qty_decimal,allow_backorders,min_cart_qty,max_cart_qty,notify_on_stock_below,qty_increments,enable_qty_increments,is_decimal_divided,new_from_date,new_to_date,gift_message_available,created_at,updated_at,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_price,msrp_display_actual_price_type,map_enabled +simple,base,,Default,simple,New Product,,,,,,,,10,,,,,new-product,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title,type=field,required=1;sku=1-text,price=0,price_type=fixed,max_characters=100|name=Test Date and Time Title,type=date_time,required=1,price=2,option_title=custom option 1,sku=2-date|name=Test Select,type=drop_down,required=1,price=3,option_title=Option 1,sku=3-1-select|name=Test Select,type=drop_down,required=1,price=3,option_title=Option 2,sku=3-2-select|name=Test Radio,type=radio,required=1,price=3,option_title=Option 1,sku=4-1-radio|name=Test Radio,type=radio,required=1,price=3,option_title=Option 2,sku=4-2-radio",,,,,,,,,,,,,,,,,,,,,,,,Block after Info Column,,, diff --git a/app/code/Magento/CatalogImportExport/composer.json b/app/code/Magento/CatalogImportExport/composer.json index dac8624086df0..41b2b5f72ce7b 100644 --- a/app/code/Magento/CatalogImportExport/composer.json +++ b/app/code/Magento/CatalogImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "ext-ctype": "*", "magento/framework": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CatalogInventory/Observer/ReindexQuoteInventoryObserver.php b/app/code/Magento/CatalogInventory/Observer/ReindexQuoteInventoryObserver.php index e22ec886474c4..a2a981009437f 100644 --- a/app/code/Magento/CatalogInventory/Observer/ReindexQuoteInventoryObserver.php +++ b/app/code/Magento/CatalogInventory/Observer/ReindexQuoteInventoryObserver.php @@ -4,41 +4,58 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogInventory\Observer; +use Magento\CatalogInventory\Model\Indexer\Stock\Processor as StockProcessor; +use Magento\Catalog\Model\Indexer\Product\Price\Processor as PriceProcessor; use Magento\Framework\Event\Observer as EventObserver; use Magento\Framework\Event\ObserverInterface; +use Magento\Framework\Exception\LocalizedException; +use Psr\Log\LoggerInterface; +/** + * Responsible for re-indexing stock items after a successful order. + */ class ReindexQuoteInventoryObserver implements ObserverInterface { /** - * @var \Magento\CatalogInventory\Model\Indexer\Stock\Processor + * @var StockProcessor */ - protected $stockIndexerProcessor; + private StockProcessor $stockIndexerProcessor; /** - * @var \Magento\Catalog\Model\Indexer\Product\Price\Processor + * @var PriceProcessor */ - protected $priceIndexer; + private PriceProcessor $priceIndexer; /** - * @var \Magento\CatalogInventory\Observer\ItemsForReindex + * @var ItemsForReindex */ - protected $itemsForReindex; + private ItemsForReindex $itemsForReindex; /** - * @param \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexerProcessor - * @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $priceIndexer + * @var LoggerInterface + */ + private LoggerInterface $logger; + + /** + * @param StockProcessor $stockIndexerProcessor + * @param PriceProcessor $priceIndexer * @param ItemsForReindex $itemsForReindex + * @param LoggerInterface $logger */ public function __construct( - \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexerProcessor, - \Magento\Catalog\Model\Indexer\Product\Price\Processor $priceIndexer, - ItemsForReindex $itemsForReindex + StockProcessor $stockIndexerProcessor, + PriceProcessor $priceIndexer, + ItemsForReindex $itemsForReindex, + LoggerInterface $logger ) { $this->stockIndexerProcessor = $stockIndexerProcessor; $this->priceIndexer = $priceIndexer; $this->itemsForReindex = $itemsForReindex; + $this->logger = $logger; } /** @@ -47,37 +64,43 @@ public function __construct( * @param EventObserver $observer * @return void */ - public function execute(EventObserver $observer) + public function execute(EventObserver $observer): void { - // Reindex quote ids - $quote = $observer->getEvent()->getQuote(); - $productIds = []; - foreach ($quote->getAllItems() as $item) { - $productIds[$item->getProductId()] = $item->getProductId(); - $children = $item->getChildrenItems(); - if ($children) { - foreach ($children as $childItem) { - $productIds[$childItem->getProductId()] = $childItem->getProductId(); + try { + // Reindex quote ids + $quote = $observer->getEvent()->getData('quote'); + $productIds = []; + foreach ($quote->getAllItems() as $item) { + $productIds[$item->getData('product_id')] = $item->getData('product_id'); + $children = $item->getData('children_items'); + if ($children) { + foreach ($children as $childItem) { + $productIds[$childItem->getData('product_id')] = $childItem->getData('product_id'); + } } } - } - if ($productIds) { - $this->stockIndexerProcessor->reindexList($productIds); - } + if ($productIds) { + $this->stockIndexerProcessor->reindexList($productIds); + } - // Reindex previously remembered items - $productIds = []; - foreach ($this->itemsForReindex->getItems() as $item) { - $item->save(); - $productIds[] = $item->getProductId(); - } + // Reindex previously remembered items + $productIds = []; + foreach ($this->itemsForReindex->getItems() as $item) { + $item->save(); + $productIds[] = $item->getData('product_id'); + } - if (!empty($productIds)) { - $this->priceIndexer->reindexList($productIds); - } + if (!empty($productIds)) { + $this->priceIndexer->reindexList($productIds); + } - $this->itemsForReindex->clear(); - // Clear list of remembered items - we don't need it anymore + $this->itemsForReindex->clear(); + // Clear list of remembered items - we don't need it anymore + } catch (LocalizedException $exception) { + $this->logger->error('Error while re-indexing order items: ' . $exception->getLogMessage()); + $this->stockIndexerProcessor->markIndexerAsInvalid(); + $this->priceIndexer->markIndexerAsInvalid(); + } } } diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/StoreFrontAddOutOfStockProductToShoppingCartTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/StoreFrontAddOutOfStockProductToShoppingCartTest.xml new file mode 100644 index 0000000000000..951ca2b0ee80b --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/StoreFrontAddOutOfStockProductToShoppingCartTest.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreFrontAddOutOfStockProductToShoppingCartTest"> + <annotations> + <features value="[Disabled Inventory Check] Add Out of Stock Product to the Shopping Cart"/> + <stories value="[Disabled Inventory Check] Add Out of Stock Product to the Shopping Cart"/> + <title value="Checking Add Out of Stock Products to the Shopping Cart"/> + <description value="Placing the order for out of stock products and zero quantity"/> + <severity value="CRITICAL"/> + <testCaseId value="AC-5262"/> + </annotations> + + <before> + <!-- Set Enable Inventory Check On Cart Load = No --> + <magentoCLI command="config:set {{DisableInventoryCheckOnCartLoad.path}} {{DisableInventoryCheckOnCartLoad.value}}" stepKey="disableCartLoad"/> + <!-- Create default category with subcategory --> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="SubCategoryWithParent" stepKey="createSubcategory"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!-- Create SimpleProductwithPrice100 --> + <createData entity="SimpleProductQty100" stepKey="simpleProductOne"> + <requiredEntity createDataKey="createSubcategory"/> + </createData> + <!-- Go To Subcategory Page--> + <actionGroup ref="StorefrontGoToSubCategoryPageActionGroup" stepKey="goToCategoryC"> + <argument name="categoryName" value="$$createCategory.name$$"/> + <argument name="subCategoryName" value="$$createSubcategory.name$$"/> + </actionGroup> + </before> + <!-- Delete the Data after execution--> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteProduct"/> + <deleteData createDataKey="simpleProductOne" stepKey="deleteCategory"/> + <magentoCLI command="config:set {{EnableInventoryCheckOnCartLoad.path}} {{EnableInventoryCheckOnCartLoad.value}}" stepKey="enableCartLoad"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> + </after> + <!-- Open New tab--> + <openNewTab stepKey="openNewTab"/> + <!-- Open Product From AdminPage--> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="openProductEditPageinNewTab"> + <argument name="productId" value="$simpleProductOne.id$"/> + </actionGroup> + <!-- Set Stock Status of Product to Out Of Stock--> + <actionGroup ref="AdminSetStockStatusActionGroup" stepKey="outOfStockStatus"> + <argument name="stockStatus" value="Out of Stock"/> + </actionGroup> + <!-- Save Product--> + <actionGroup ref="SaveProductFormActionGroup" stepKey="clicksaveProduct"/> + <!-- Switch to Previous tab and Check Error message Product that you are trying to add is not available --> + <switchToPreviousTab stepKey="switchToPreviousTab"/> + <!-- Mouse Hover Product On Category Page--> + <actionGroup ref="StorefrontHoverProductOnCategoryPageActionGroup" stepKey="hoverProduct"/> + <!-- Select Add to cart--> + <click selector="{{StorefrontCategoryMainSection.addToCartButtonProductInfoHover}}" stepKey="toCategory"/> + <waitForPageLoad stepKey="wait"/> + <!-- Assert the Error Message--> + <see selector="{{StorefrontProductPageSection.errorMsg}}" userInput="Product that you are trying to add is not available." stepKey="seeErrorMessage"/> + </test> + +</tests> diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Observer/ReindexQuoteInventoryObserverTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Observer/ReindexQuoteInventoryObserverTest.php new file mode 100644 index 0000000000000..97156eb4f1784 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Unit/Observer/ReindexQuoteInventoryObserverTest.php @@ -0,0 +1,193 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\CatalogInventory\Test\Unit\Observer; + +use Magento\Catalog\Model\Indexer\Product\Price\Processor as PriceProcessor; +use Magento\CatalogInventory\Model\Indexer\Stock\Processor as StockProcessor; +use Magento\CatalogInventory\Observer\ItemsForReindex; +use Magento\CatalogInventory\Observer\ReindexQuoteInventoryObserver; +use Magento\Framework\Event; +use Magento\Framework\Event\Observer; +use Magento\Framework\Exception\LocalizedException; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Item; +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; + +/** + * Test class for ReindexQuoteInventoryObserver + */ +class ReindexQuoteInventoryObserverTest extends TestCase +{ + /** + * @var StockProcessor + */ + private StockProcessor $stockIndexerProcessor; + + /** + * @var PriceProcessor + */ + private PriceProcessor $priceIndexer; + + /** + * @var ItemsForReindex + */ + private ItemsForReindex $itemsForReindex; + + /** + * @var LoggerInterface + */ + private LoggerInterface $logger; + + /** + * @var Observer + */ + private Observer $observedObject; + + /** + * @var Event + */ + private Event $event; + + /** + * @var Quote + */ + private Quote $quote; + + /** + * @var Item + */ + private Item $quoteItem; + + /** + * @var ReindexQuoteInventoryObserver + */ + private ReindexQuoteInventoryObserver $sut; + + /** + * @inheritDoc + */ + public function setUp(): void + { + $this->stockIndexerProcessor = $this->createMock(StockProcessor::class); + $this->priceIndexer = $this->createMock(PriceProcessor::class); + $this->itemsForReindex = $this->createMock(ItemsForReindex::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->observedObject = $this->createMock(Observer::class); + $this->event = $this->createMock(Event::class); + $this->quote = $this->createMock(Quote::class); + $this->quoteItem = $this->createMock(Item::class); + + $this->sut = new ReindexQuoteInventoryObserver( + $this->stockIndexerProcessor, + $this->priceIndexer, + $this->itemsForReindex, + $this->logger + ); + } + + /** + * Test execute should re-index quote stock items. + * + * @test + * + * @return void + */ + public function execute(): void + { + $this->observedObject->expects($this->once()) + ->method('getEvent') + ->willReturn($this->event); + + $this->event->expects($this->once()) + ->method('getData') + ->with('quote') + ->willReturn($this->quote); + + $this->quote->expects($this->once()) + ->method('getAllItems') + ->willReturn([$this->quoteItem]); + + $this->quoteItem->expects($this->exactly(6)) + ->method('getData') + ->withConsecutive( + ['product_id'], + ['product_id'], + ['children_items'], + ['product_id'], + ['product_id'], + ['product_id'] + )->willReturnOnConsecutiveCalls(1, 1, [$this->quoteItem], 1, 1, 1); + + $this->stockIndexerProcessor->expects($this->once()) + ->method('reindexList') + ->with([1 => 1]); + + $this->itemsForReindex->expects($this->once()) + ->method('getItems') + ->willReturn([$this->quoteItem]); + + $this->priceIndexer->expects($this->once()) + ->method('reindexList') + ->with([1]); + + $this->itemsForReindex->expects($this->once()) + ->method('clear'); + + $this->sut->execute($this->observedObject); + } + + /** + * Test execute should log error on exception. + * + * @test + * + * @return void + */ + public function executeShouldLogOnException(): void + { + $this->observedObject->expects($this->once()) + ->method('getEvent') + ->willReturn($this->event); + + $this->event->expects($this->once()) + ->method('getData') + ->with('quote') + ->willReturn($this->quote); + + $this->quote->expects($this->once()) + ->method('getAllItems') + ->willReturn([$this->quoteItem]); + + $this->quoteItem->expects($this->exactly(3)) + ->method('getData') + ->withConsecutive( + ['product_id'], + ['product_id'], + ['children_items'] + )->willReturnOnConsecutiveCalls(1, 1, []); + + $this->stockIndexerProcessor->expects($this->once()) + ->method('reindexList') + ->with([1 => 1]) + ->willThrowException(new LocalizedException(__('error'))); + + $this->logger->expects($this->once()) + ->method('error') + ->with('Error while re-indexing order items: error'); + + $this->stockIndexerProcessor->expects($this->once()) + ->method('markIndexerAsInvalid'); + + $this->priceIndexer->expects($this->once()) + ->method('markIndexerAsInvalid'); + + $this->sut->execute($this->observedObject); + } +} diff --git a/app/code/Magento/CatalogInventory/composer.json b/app/code/Magento/CatalogInventory/composer.json index 893de329628fa..7ea00923ac715 100644 --- a/app/code/Magento/CatalogInventory/composer.json +++ b/app/code/Magento/CatalogInventory/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-config": "*", diff --git a/app/code/Magento/CatalogInventoryGraphQl/composer.json b/app/code/Magento/CatalogInventoryGraphQl/composer.json index 38685524d5346..58d567bc0706e 100644 --- a/app/code/Magento/CatalogInventoryGraphQl/composer.json +++ b/app/code/Magento/CatalogInventoryGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-store": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CatalogRule/Setup/Patch/Schema/CleanUpProductPriceReplicaTable.php b/app/code/Magento/CatalogRule/Setup/Patch/Schema/CleanUpProductPriceReplicaTable.php new file mode 100644 index 0000000000000..476c7fe277db1 --- /dev/null +++ b/app/code/Magento/CatalogRule/Setup/Patch/Schema/CleanUpProductPriceReplicaTable.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogRule\Setup\Patch\Schema; + +use Magento\Framework\Setup\SchemaSetupInterface; +use Magento\Framework\Setup\Patch\SchemaPatchInterface; + +class CleanUpProductPriceReplicaTable implements SchemaPatchInterface +{ + /** + * @var SchemaSetupInterface + */ + private SchemaSetupInterface $schemaSetup; + + /** + * CleanUpProductPriceReplicaTable constructor. + * @param SchemaSetupInterface $schemaSetup + */ + public function __construct( + SchemaSetupInterface $schemaSetup + ) { + $this->schemaSetup = $schemaSetup; + } + + /** + * @inheritDoc + */ + public function apply(): void + { + $connection = $this->schemaSetup->startSetup(); + $connection = $this->schemaSetup->getConnection(); + + // There was a bug which caused the catalogrule_product_price_replica + // table to become unnecessarily large. The bug causing the growth has + // been resolved. This schema patch cleans up the damage done by that + // bug on existing websites. Deleting all entries from the replica table + // is safe. + // See https://github.com/magento/magento2/issues/31752 for details. + + $tableName = $connection->getTableName('catalogrule_product_price_replica'); + + if ($connection->isTableExists($tableName)) { + $connection->truncateTable($tableName); + } + + $connection->endSetup(); + } + + /** + * @inheritDoc + */ + public static function getDependencies(): array + { + return []; + } + + /** + * @inheritDoc + */ + public function getAliases(): array + { + return []; + } +} diff --git a/app/code/Magento/CatalogRule/composer.json b/app/code/Magento/CatalogRule/composer.json index 531a12ac017ed..dc9c51dade87f 100644 --- a/app/code/Magento/CatalogRule/composer.json +++ b/app/code/Magento/CatalogRule/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CatalogRuleConfigurable/composer.json b/app/code/Magento/CatalogRuleConfigurable/composer.json index 68da972ae94f9..8b6569ba9fec4 100644 --- a/app/code/Magento/CatalogRuleConfigurable/composer.json +++ b/app/code/Magento/CatalogRuleConfigurable/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/magento-composer-installer": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CatalogRuleGraphQl/composer.json b/app/code/Magento/CatalogRuleGraphQl/composer.json index 2c8c3ef20c96a..c22ba277d57d9 100644 --- a/app/code/Magento/CatalogRuleGraphQl/composer.json +++ b/app/code/Magento/CatalogRuleGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "suggest": { diff --git a/app/code/Magento/CatalogSearch/composer.json b/app/code/Magento/CatalogSearch/composer.json index 465d7daeebe18..7ccdb99d2c9d1 100644 --- a/app/code/Magento/CatalogSearch/composer.json +++ b/app/code/Magento/CatalogSearch/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CatalogUrlRewrite/composer.json b/app/code/Magento/CatalogUrlRewrite/composer.json index ce409e2186faa..6df0042d40648 100644 --- a/app/code/Magento/CatalogUrlRewrite/composer.json +++ b/app/code/Magento/CatalogUrlRewrite/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json b/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json index 025234af6f865..c3917a517a68d 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-store": "*", "magento/module-catalog": "*", "magento/module-catalog-graph-ql": "*", diff --git a/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php b/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php index 2ac5e5d1aeff8..a8cafb034c0b0 100644 --- a/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php +++ b/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php @@ -9,9 +9,9 @@ */ namespace Magento\CatalogWidget\Model\Rule\Condition; -use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\ProductCategoryList; use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Framework\DB\Select; use Magento\Store\Model\Store; /** @@ -201,32 +201,63 @@ protected function addNotGlobalAttribute( \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute, Collection $collection ) { - $storeId = $this->storeManager->getStore()->getId(); - $values = $collection->getAllAttributeValues($attribute); - $validEntities = []; - if ($values) { - foreach ($values as $entityId => $storeValues) { - if (isset($storeValues[$storeId])) { - if ($this->validateAttribute($storeValues[$storeId])) { - $validEntities[] = $entityId; - } - } else { - if (isset($storeValues[Store::DEFAULT_STORE_ID]) && - $this->validateAttribute($storeValues[Store::DEFAULT_STORE_ID]) - ) { - $validEntities[] = $entityId; - } - } - } + $connection = $this->_productResource->getConnection(); + switch ($attribute->getBackendType()) { + case 'decimal': + case 'datetime': + case 'int': + case 'varchar': + case 'text': + $aliasDefault = 'at_' . $attribute->getAttributeCode() . '_default'; + $aliasStore = 'at_' . $attribute->getAttributeCode(); + $collection->addAttributeToSelect($attribute->getAttributeCode(), 'left'); + break; + default: + $aliasDefault = 'at_' . sha1($this->getId()) . $attribute->getAttributeCode() . '_default'; + $aliasStore = 'at_' . sha1($this->getId()) . $attribute->getAttributeCode(); + + $storeDefaultId = $connection->getIfNullSql( + $aliasDefault . '.store_id', + Store::DEFAULT_STORE_ID + ); + $storeId = $connection->getIfNullSql( + $aliasStore . '.store_id', + $this->storeManager->getStore()->getId() + ); + $linkField = $attribute->getEntity()->getLinkField(); + + $collection->getSelect()->joinLeft( + [$aliasDefault => $collection->getTable($attribute->getBackendTable())], + "($aliasDefault.$linkField = e.$linkField) AND ($aliasDefault.store_id = $storeDefaultId)" . + " AND ($aliasDefault.attribute_id = {$attribute->getId()})", + [] + ); + $collection->getSelect()->joinLeft( + [$aliasStore => $collection->getTable($attribute->getBackendTable())], + "($aliasStore.$linkField = e.$linkField) AND ($aliasStore.store_id = $storeId)" . + " AND ($aliasStore.attribute_id = {$attribute->getId()})", + [] + ); } - $this->setOperator('()'); - $this->unsetData('value_parsed'); - if ($validEntities) { - $this->setData('value', implode(',', $validEntities)); + + $fromPart = $collection->getSelect()->getPart(Select::FROM); + if (isset($fromPart[$aliasStore]['joinType']) + && isset($fromPart[$aliasDefault]['joinType']) + ) { + $conditionCheck = $connection->quoteIdentifier($aliasStore . '.value_id') . " > 0"; + $conditionTrue = $connection->quoteIdentifier($aliasStore . '.value'); + $conditionFalse = $connection->quoteIdentifier($aliasDefault . '.value'); + $joinedAttribute = $collection->getSelect()->getConnection()->getCheckSql( + $conditionCheck, + $conditionTrue, + $conditionFalse + ); } else { - $this->unsetData('value'); + $joinedAttribute = $aliasStore . '.' . 'value'; } + $this->joinedAttributes[$attribute->getAttributeCode()] = $joinedAttribute; + return $this; } diff --git a/app/code/Magento/CatalogWidget/Test/Unit/Model/Rule/Condition/ProductTest.php b/app/code/Magento/CatalogWidget/Test/Unit/Model/Rule/Condition/ProductTest.php index d683912d09fb8..49b4c1a7978ad 100644 --- a/app/code/Magento/CatalogWidget/Test/Unit/Model/Rule/Condition/ProductTest.php +++ b/app/code/Magento/CatalogWidget/Test/Unit/Model/Rule/Condition/ProductTest.php @@ -189,7 +189,12 @@ public function getMappedSqlFieldPriceDataProvider(): array [ false, false, - 'e.entity_id' + 'at_price.value' + ], + [ + false, + true, + 'price_index.min_price' ], ]; } @@ -248,7 +253,7 @@ public function getBindArgumentValueDataProvider(): array 1 => 2 ] ], - new \Zend_Db_Expr('1, 3') + '2' ], [ [ diff --git a/app/code/Magento/CatalogWidget/composer.json b/app/code/Magento/CatalogWidget/composer.json index 33c5e3b3ba3ee..b54b27474787b 100644 --- a/app/code/Magento/CatalogWidget/composer.json +++ b/app/code/Magento/CatalogWidget/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniCartEmptyActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniCartEmptyActionGroup.xml index ca891a1dc263b..2119e5c43f7bb 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniCartEmptyActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniCartEmptyActionGroup.xml @@ -16,6 +16,6 @@ <wait stepKey="waitForMinicartAjaxCallToComplete" time="15"/> <dontSeeElement selector="{{StorefrontMinicartSection.productCount}}" stepKey="dontSeeMinicartProductCount"/> <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="expandMinicart"/> - <see selector="{{StorefrontMinicartSection.minicartContent}}" userInput="You have no items in your shopping cart." stepKey="seeEmptyCartMessage"/> + <see selector="{{StorefrontMinicartSection.messageEmptyCart}}" userInput="You have no items in your shopping cart." stepKey="seeEmptyCartMessage"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/AdminCheckZeroSubtotalOrderIsInProcessingStatusTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/AdminCheckZeroSubtotalOrderIsInProcessingStatusTest.xml index 6b43077873fcb..12a524d2d6ad8 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/AdminCheckZeroSubtotalOrderIsInProcessingStatusTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/AdminCheckZeroSubtotalOrderIsInProcessingStatusTest.xml @@ -11,11 +11,12 @@ <test name="AdminCheckZeroSubtotalOrderIsInProcessingStatusTest"> <annotations> <features value="Checkout"/> - <stories value="MAGETWO-71375: Zero Subtotal Orders have incorrect status"/> + <stories value="Zero Subtotal Orders have incorrect status"/> <title value="Zero Subtotal Orders should have the 'Processing' status on creating"/> <description value="Created order should be in Processing status"/> <severity value="MAJOR"/> <testCaseId value="MAGETWO-94178"/> + <useCaseId value="MAGETWO-71375"/> <group value="checkout"/> </annotations> <before> @@ -37,7 +38,6 @@ </createData> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - </before> <after> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/AdminCheckZeroSubtotalOrderWithCustomStatus.xml b/app/code/Magento/Checkout/Test/Mftf/Test/AdminCheckZeroSubtotalOrderWithCustomStatus.xml new file mode 100644 index 0000000000000..f08640d895b6b --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/AdminCheckZeroSubtotalOrderWithCustomStatus.xml @@ -0,0 +1,128 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCheckZeroSubtotalOrderWithCustomStatus"> + <annotations> + <features value="Checkout"/> + <stories value="Zero Subtotal Checkout Order"/> + <title value="Zero Subtotal Checkout Order must have the new order status set in configuration." /> + <description value="Order placed with Zero Subtotal Checkout payment must have the new order status set in configuration."/> + <testCaseId value="AC-6723"/> + <useCaseId value="ACP2E-1120"/> + <severity value="AVERAGE"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="simplecategory"/> + <createData entity="SimpleProduct" stepKey="simpleproduct"> + <requiredEntity createDataKey="simplecategory"/> + </createData> + <createData entity="PaymentMethodsSettingConfig" stepKey="paymentMethodsSettingConfig"/> + <createData entity="FreeShippingMethodsSettingConfig" stepKey="freeShippingMethodsSettingConfig"/> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + + <createData entity="ApiSalesRule" stepKey="createCartPriceRule"> + <field key="discount_amount">100</field> + </createData> + <createData entity="ApiSalesRuleCoupon" stepKey="createCartPriceRuleCoupon"> + <requiredEntity createDataKey="createCartPriceRule"/> + </createData> + + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + + <after> + <deleteData createDataKey="simplecategory" stepKey="deleteCategory"/> + <deleteData createDataKey="simpleproduct" stepKey="deleteProduct"/> + <createData entity="DisablePaymentMethodsSettingConfig" stepKey="disablePaymentMethodsSettingConfig"/> + <createData entity="DefaultShippingMethodsConfig" stepKey="defaultShippingMethodsConfig"/> + <createData entity="DisableFreeShippingConfig" stepKey="disableFreeShippingConfig"/> + <deleteData createDataKey="createCartPriceRule" stepKey="deleteSalesRule"/> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> + <argument name="indices" value=""/> + </actionGroup> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <!-- Go to new order status page --> + <actionGroup ref="AdminGoToOrderStatusPageActionGroup" stepKey="goToOrderStatusPage"/> + <actionGroup ref="AdminClickCreateNewStatusButtonOnOrderStatusPageActionGroup" stepKey="clickCreateNewStatus"/> + + <!-- Fill the form and validate message --> + <actionGroup ref="AdminOrderStatusFormFillAndSave" stepKey="fillFormAndClickSave"> + <argument name="status" value="{{EnableFreeOrderStatusCustom.value}}"/> + <argument name="label" value="{{EnableFreeOrderStatusCustom.label}}"/> + </actionGroup> + <actionGroup ref="AssertOrderStatusFormSaveSuccess" stepKey="seeFormSaveSuccess"/> + + <!-- Verify the order status grid page shows the order status we just created --> + <actionGroup ref="AssertOrderStatusExistsInGrid" stepKey="searchCreatedOrderStatus"> + <argument name="status" value="{{EnableFreeOrderStatusCustom.value}}"/> + <argument name="label" value="{{EnableFreeOrderStatusCustom.label}}"/> + </actionGroup> + + <!-- Assign status to state --> + <click selector="{{AdminOrderStatusGridSection.assignStatusToStateBtn}}" stepKey="clickAssignStatusBtn"/> + <selectOption selector="{{AdminAssignOrderStatusToStateSection.orderStatus}}" userInput="{{EnableFreeOrderStatusCustom.value}}" stepKey="selectOrderStatus"/> + <selectOption selector="{{AdminAssignOrderStatusToStateSection.orderState}}" userInput="{{OrderState.new}}" stepKey="selectOrderState"/> + <checkOption selector="{{AdminAssignOrderStatusToStateSection.orderStatusAsDefault}}" stepKey="orderStatusAsDefault"/> + <uncheckOption selector="{{AdminAssignOrderStatusToStateSection.visibleOnStorefront}}" stepKey="visibleOnStorefront"/> + <click selector="{{AdminAssignOrderStatusToStateSection.saveStatusAssignment}}" stepKey="clickSaveStatus"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You assigned the order status." stepKey="seeSuccess"/> + + <!-- Prepare data for constraints --> + <magentoCLI command="config:set {{EnableFreeOrderStatusCustom.path}} {{EnableFreeOrderStatusCustom.value}}" stepKey="enableNewOrderStatus"/> + <magentoCLI command="config:set {{EnableFreeOrderPaymentAction.path}} {{EnableFreeOrderPaymentAction.value}}" stepKey="enableNewOrderPaymentAction"/> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + + <!-- Add product to cart and place order --> + <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="AddProductToCard"> + <argument name="product" value="$$simpleproduct$$"/> + </actionGroup> + + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="clickToProceedToCheckout"/> + + <actionGroup ref="ShipmentFormFreeShippingActionGroup" stepKey="shipmentFormFreeShippingActionGroup"/> + + <actionGroup ref="StorefrontApplyDiscountCodeActionGroup" stepKey="applyDiscountCoupon"> + <argument name="discountCode" value="$createCartPriceRuleCoupon.code$"/> + </actionGroup> + + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickPlaceOrder"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> + + <!-- Go to admin and check order status --> + <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="navigateToSalesOrderPage"/> + <actionGroup ref="SearchAdminDataGridByKeywordActionGroup" stepKey="searchForOrder"> + <argument name="keyword" value="{$grabOrderNumber}"/> + </actionGroup> + + <actionGroup ref="AdminCheckOrderStatusInGridActionGroup" stepKey="seeOrderStatusInGrid"> + <argument name="orderId" value="$grabOrderNumber"/> + <argument name="status" value="{{EnableFreeOrderStatusCustom.label}}"/> + </actionGroup> + + <!-- Open order --> + <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrder"> + <argument name="orderId" value="{$grabOrderNumber}"/> + </actionGroup> + + <!-- Assert invoice button --> + <seeElement selector="{{AdminOrderDetailsMainActionsSection.invoiceBtn}}" stepKey="seeInvoiceBtn"/> + + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/AdminCheckZeroSubtotalOrderWithGeneratedInvoiceTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/AdminCheckZeroSubtotalOrderWithGeneratedInvoiceTest.xml new file mode 100644 index 0000000000000..81de8664f98e2 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/AdminCheckZeroSubtotalOrderWithGeneratedInvoiceTest.xml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCheckZeroSubtotalOrderWithGeneratedInvoiceTest"> + <annotations> + <features value="Checkout"/> + <stories value="Zero Subtotal Checkout Order"/> + <title value="Zero Subtotal Order should have invoice generated when setting 'Automatically Invoice All Items' to Yes." /> + <description value="Created order with automatically invoice generated."/> + <severity value="AVERAGE"/> + <testCaseId value="AC-6722"/> + <useCaseId value="ENGCOM-5443"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="simplecategory"/> + <createData entity="SimpleProduct" stepKey="simpleproduct"> + <requiredEntity createDataKey="simplecategory"/> + </createData> + <createData entity="PaymentMethodsSettingConfig" stepKey="paymentMethodsSettingConfig"/> + <createData entity="FreeShippingMethodsSettingConfig" stepKey="freeShippingMethodsSettingConfig"/> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + + <createData entity="ApiSalesRule" stepKey="createCartPriceRule"> + <field key="discount_amount">100</field> + </createData> + <createData entity="ApiSalesRuleCoupon" stepKey="createCartPriceRuleCoupon"> + <requiredEntity createDataKey="createCartPriceRule"/> + </createData> + + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + + </before> + + <after> + <deleteData createDataKey="simplecategory" stepKey="deleteCategory"/> + <deleteData createDataKey="simpleproduct" stepKey="deleteProduct"/> + <createData entity="DisablePaymentMethodsSettingConfig" stepKey="disablePaymentMethodsSettingConfig"/> + <createData entity="DefaultShippingMethodsConfig" stepKey="defaultShippingMethodsConfig"/> + <createData entity="DisableFreeShippingConfig" stepKey="disableFreeShippingConfig"/> + <deleteData createDataKey="createCartPriceRule" stepKey="deleteSalesRule"/> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> + <argument name="indices" value=""/> + </actionGroup> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="AddProductToCard"> + <argument name="product" value="$$simpleproduct$$"/> + </actionGroup> + + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="clickToProceedToCheckout"/> + + <actionGroup ref="ShipmentFormFreeShippingActionGroup" stepKey="shipmentFormFreeShippingActionGroup"/> + + <actionGroup ref="StorefrontApplyDiscountCodeActionGroup" stepKey="applyDiscountCoupon"> + <argument name="discountCode" value="$createCartPriceRuleCoupon.code$"/> + </actionGroup> + + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="clickPlaceOrder"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> + + <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="navigateToSalesOrderPage"/> + <actionGroup ref="SearchAdminDataGridByKeywordActionGroup" stepKey="searchForOrder"> + <argument name="keyword" value="{$grabOrderNumber}"/> + </actionGroup> + + <actionGroup ref="AdminCheckOrderStatusInGridActionGroup" stepKey="seeOrderStatusInGrid"> + <argument name="orderId" value="$grabOrderNumber"/> + <argument name="status" value="Processing"/> + </actionGroup> + + <!-- Assert invoice in invoices grid --> + <actionGroup ref="FilterInvoiceGridByOrderIdWithCleanFiltersActionGroup" stepKey="filterInvoiceGridByOrderId"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + <click selector="{{AdminInvoicesGridSection.firstRow}}" stepKey="opeCreatedInvoice"/> + <waitForPageLoad stepKey="waitForInvoiceDetailsPageToLoad"/> + <grabFromCurrentUrl regex="~/invoice_id/(\d+)/~" stepKey="grabInvoiceId"/> + + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontValidateItemQtyShouldNotBeNegativeValueWhenPressKeyboardDownKeyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontValidateItemQtyShouldNotBeNegativeValueWhenPressKeyboardDownKeyTest.xml new file mode 100644 index 0000000000000..b61e533d42dfd --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontValidateItemQtyShouldNotBeNegativeValueWhenPressKeyboardDownKeyTest.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontValidateItemQtyShouldNotBeNegativeValueWhenPressKeyboardDownKeyTest"> + <annotations> + <features value="Checkout"/> + <stories value="Item Qty Goes Negative When Using The Down Arrow Keyboard Key"/> + <title value="Validate item quantity value should not be negative value when pressing keyboard down."/> + <description value="Validate item quantity value should not be negative value when pressing keyboard down."/> + <testCaseId value="AC-6927"/> + <useCaseId value="ACP2E-1276"/> + <severity value="AVERAGE"/> + <group value="checkout"/> + </annotations> + <before> + <!-- Create simple products and category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + <!-- Open created product and add to cart --> + <actionGroup ref="StorefrontOpenProductEntityPageActionGroup" stepKey="openProductPage"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontAddToTheCartActionGroup" stepKey="productAddToCart"/> + <!--Click on mini cart--> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickOnMiniCart"/> + <waitForElementVisible selector="{{StorefrontMinicartSection.quantity}}" stepKey="waitForElementQty"/> + <pressKey selector="{{StorefrontMinicartSection.itemQuantity($$createProduct.name$$)}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::DOWN]" stepKey="pressKeyDown1"/> + <pressKey selector="{{StorefrontMinicartSection.itemQuantity($$createProduct.name$$)}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::DOWN]" stepKey="pressKeyDown2"/> + <grabValueFrom selector="{{StorefrontMinicartSection.itemQuantity($$createProduct.name$$)}}" stepKey="grabProductQtyInMinicart"/> + <assertGreaterThanOrEqual stepKey="validateMiniCartQtyValueGreaterThanZero"> + <actualResult type="const">$grabProductQtyInMinicart</actualResult> + <expectedResult type="const">0</expectedResult> + </assertGreaterThanOrEqual> + <!--Click on view and edit cart link--> + <actionGroup ref="ClickViewAndEditCartFromMiniCartActionGroup" stepKey="goToShoppingCartFromMiniCart"/> + <waitForPageLoad stepKey="waitForViewAndEditCartToOpen"/> + <pressKey selector="{{CheckoutCartProductSection.ProductQuantityByName($$createProduct.name$$)}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::DOWN]" stepKey="pressCartQuantityKeyDown1"/> + <pressKey selector="{{CheckoutCartProductSection.ProductQuantityByName($$createProduct.name$$)}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::DOWN]" stepKey="pressCartQuantityKeyDown2"/> + <grabValueFrom selector="{{CheckoutCartProductSection.ProductQuantityByName($$createProduct.name$$)}}" stepKey="grabProductQtyInCart"/> + <assertGreaterThanOrEqual stepKey="validateCartQtyValueGreaterThanZero"> + <actualResult type="const">$grabProductQtyInCart</actualResult> + <expectedResult type="const">0</expectedResult> + </assertGreaterThanOrEqual> + </test> +</tests> diff --git a/app/code/Magento/Checkout/composer.json b/app/code/Magento/Checkout/composer.json index f277184d8986b..4d24d27e676ee 100644 --- a/app/code/Magento/Checkout/composer.json +++ b/app/code/Magento/Checkout/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-captcha": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CheckoutAgreements/composer.json b/app/code/Magento/CheckoutAgreements/composer.json index 753bef25e3e64..44d0e86bd78f2 100644 --- a/app/code/Magento/CheckoutAgreements/composer.json +++ b/app/code/Magento/CheckoutAgreements/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-checkout": "*", diff --git a/app/code/Magento/CheckoutAgreementsGraphQl/composer.json b/app/code/Magento/CheckoutAgreementsGraphQl/composer.json index de6bc855e7847..c0c1853eb4324 100644 --- a/app/code/Magento/CheckoutAgreementsGraphQl/composer.json +++ b/app/code/Magento/CheckoutAgreementsGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-store": "*", "magento/module-checkout-agreements": "*" diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml index f3389072f1776..fa2f45d83cc37 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml @@ -34,5 +34,7 @@ <element name="massActionsOption" type="button" selector="//div[@class='admin__data-grid-header'][(not(ancestor::*[@class='sticky-header']) and not(contains(@style,'visibility: hidden'))) or (ancestor::*[@class='sticky-header' and not(contains(@style,'display: none'))])]//span[contains(@class, 'action-menu-item') and .= '{{action}}']" parameterized="true"/> <element name="gridDataRow" type="input" selector=".data-row .data-grid-cell-content"/> <element name="pagesGridRowByTitle" type="input" selector="//tbody//tr//td//div[contains(., '{{var1}}')]" parameterized="true" timeout="30"/> + <element name="saveFormRow" type="button" selector="//td//button//span[contains(text(),'Save')]/.."/> + <element name="selectLayout" type="select" selector="//tr[@class='data-grid-editable-row _odd-row']//select[@name='page_layout']" /> </section> </sections> diff --git a/app/code/Magento/Cms/composer.json b/app/code/Magento/Cms/composer.json index b3b2ba31db37b..aa972d0a711a7 100644 --- a/app/code/Magento/Cms/composer.json +++ b/app/code/Magento/Cms/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/CmsGraphQl/composer.json b/app/code/Magento/CmsGraphQl/composer.json index b2550344299fa..07b7261823d92 100644 --- a/app/code/Magento/CmsGraphQl/composer.json +++ b/app/code/Magento/CmsGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-cms": "*", "magento/module-widget": "*", diff --git a/app/code/Magento/CmsUrlRewrite/composer.json b/app/code/Magento/CmsUrlRewrite/composer.json index 8fb9bbfff22e2..0521f77f9bb7f 100644 --- a/app/code/Magento/CmsUrlRewrite/composer.json +++ b/app/code/Magento/CmsUrlRewrite/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-cms": "*", "magento/module-store": "*", diff --git a/app/code/Magento/CmsUrlRewriteGraphQl/composer.json b/app/code/Magento/CmsUrlRewriteGraphQl/composer.json index 70a598d26d574..2687ad032e000 100644 --- a/app/code/Magento/CmsUrlRewriteGraphQl/composer.json +++ b/app/code/Magento/CmsUrlRewriteGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-cms": "*", "magento/module-store": "*", diff --git a/app/code/Magento/CompareListGraphQl/composer.json b/app/code/Magento/CompareListGraphQl/composer.json index e8fb5d588852e..9193e30061619 100644 --- a/app/code/Magento/CompareListGraphQl/composer.json +++ b/app/code/Magento/CompareListGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-customer": "*" diff --git a/app/code/Magento/Config/composer.json b/app/code/Magento/Config/composer.json index 61100e6336c27..8ad286bd667b5 100644 --- a/app/code/Magento/Config/composer.json +++ b/app/code/Magento/Config/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-cron": "*", diff --git a/app/code/Magento/ConfigurableImportExport/composer.json b/app/code/Magento/ConfigurableImportExport/composer.json index 98205def6a799..f56cfd6299ad2 100644 --- a/app/code/Magento/ConfigurableImportExport/composer.json +++ b/app/code/Magento/ConfigurableImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-catalog-import-export": "*", diff --git a/app/code/Magento/ConfigurableProduct/composer.json b/app/code/Magento/ConfigurableProduct/composer.json index 67b1ad2b2ed33..8a9e4e50ad194 100644 --- a/app/code/Magento/ConfigurableProduct/composer.json +++ b/app/code/Magento/ConfigurableProduct/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js index 6e82fd42692fc..6b00402809500 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js @@ -130,9 +130,14 @@ define([ * @return {String|Number|Array} */ getProductValue: function (name) { - name = name.split('/').join(']['); + var value; - return $('[name="product[' + name + ']"]:enabled:not(.ignore-validate)', this.productForm).val(); + name = name.split('/').join(']['); + value = $('[name="product[' + name + ']"]:enabled:not(.ignore-validate)', this.productForm).val(); + if (value === undefined) { + value = this.source.get('data.product.' + name); + } + return value; }, /** diff --git a/app/code/Magento/ConfigurableProductGraphQl/composer.json b/app/code/Magento/ConfigurableProductGraphQl/composer.json index b839227511d88..43c297de656c5 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/composer.json +++ b/app/code/Magento/ConfigurableProductGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-catalog": "*", "magento/module-configurable-product": "*", "magento/module-graph-ql": "*", diff --git a/app/code/Magento/ConfigurableProductSales/composer.json b/app/code/Magento/ConfigurableProductSales/composer.json index 55b2e78bd24d2..909c2ff967f41 100644 --- a/app/code/Magento/ConfigurableProductSales/composer.json +++ b/app/code/Magento/ConfigurableProductSales/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-sales": "*", diff --git a/app/code/Magento/Contact/composer.json b/app/code/Magento/Contact/composer.json index 00ea8f865928d..68b5bb4c9a6da 100644 --- a/app/code/Magento/Contact/composer.json +++ b/app/code/Magento/Contact/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-cms": "*", "magento/module-config": "*", diff --git a/app/code/Magento/Cookie/Test/Mftf/Section/AdminDefaultCookieSettingsSection.xml b/app/code/Magento/Cookie/Test/Mftf/Section/AdminDefaultCookieSettingsSection.xml index 977db4a8bbf74..0e51354583d6b 100644 --- a/app/code/Magento/Cookie/Test/Mftf/Section/AdminDefaultCookieSettingsSection.xml +++ b/app/code/Magento/Cookie/Test/Mftf/Section/AdminDefaultCookieSettingsSection.xml @@ -11,5 +11,7 @@ <section name="AdminDefaultCookieSettingsSection"> <element name="DefaultCookieSettingsTab" type="button" selector="#web_cookie-head"/> <element name="DefaultCookieLifetime" type="input" selector="#web_cookie_cookie_lifetime"/> + <element name="DefaultCookieLifetimeSystemValueCheckbox" type="input" selector="#web_cookie_cookie_lifetime_inherit"/> + <element name="Save" type="button" selector="#save"/> </section> </sections> diff --git a/app/code/Magento/Cookie/composer.json b/app/code/Magento/Cookie/composer.json index 6a5752792f7fb..d2f1a87594a3c 100644 --- a/app/code/Magento/Cookie/composer.json +++ b/app/code/Magento/Cookie/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-store": "*" }, diff --git a/app/code/Magento/Cron/composer.json b/app/code/Magento/Cron/composer.json index 0468a95b457c0..1696588920015 100644 --- a/app/code/Magento/Cron/composer.json +++ b/app/code/Magento/Cron/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-store": "*" }, diff --git a/app/code/Magento/Csp/composer.json b/app/code/Magento/Csp/composer.json index 2079a30d92068..f2e69e7a7ec63 100644 --- a/app/code/Magento/Csp/composer.json +++ b/app/code/Magento/Csp/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-store": "*" }, diff --git a/app/code/Magento/CurrencySymbol/composer.json b/app/code/Magento/CurrencySymbol/composer.json index 4f6854cbee185..8c2325b39d508 100644 --- a/app/code/Magento/CurrencySymbol/composer.json +++ b/app/code/Magento/CurrencySymbol/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-config": "*", diff --git a/app/code/Magento/Customer/Block/Address/Renderer/DefaultRenderer.php b/app/code/Magento/Customer/Block/Address/Renderer/DefaultRenderer.php index 703d9b2d0154a..2e5051cc86010 100644 --- a/app/code/Magento/Customer/Block/Address/Renderer/DefaultRenderer.php +++ b/app/code/Magento/Customer/Block/Address/Renderer/DefaultRenderer.php @@ -189,6 +189,9 @@ public function renderArray($addressAttributes, $format = null) $data[$key] = $v; } } + if (in_array($attributeCode, ['prefix','suffix'])) { + $value = __($value); + } $data[$attributeCode] = $value; } } diff --git a/app/code/Magento/Customer/Helper/View.php b/app/code/Magento/Customer/Helper/View.php index dcd4ae01940a6..560abd335d2fa 100644 --- a/app/code/Magento/Customer/Helper/View.php +++ b/app/code/Magento/Customer/Helper/View.php @@ -51,7 +51,7 @@ public function getCustomerName(CustomerInterface $customerData) $name = ''; $prefixMetadata = $this->_customerMetadataService->getAttributeMetadata('prefix'); if ($prefixMetadata->isVisible() && $customerData->getPrefix()) { - $name .= $customerData->getPrefix() . ' '; + $name .= __($customerData->getPrefix()) . ' '; } $name .= $customerData->getFirstname(); @@ -65,7 +65,7 @@ public function getCustomerName(CustomerInterface $customerData) $suffixMetadata = $this->_customerMetadataService->getAttributeMetadata('suffix'); if ($suffixMetadata->isVisible() && $customerData->getSuffix()) { - $name .= ' ' . $customerData->getSuffix(); + $name .= ' ' . __($customerData->getSuffix()); } return $this->escaper->escapeHtml($name); diff --git a/app/code/Magento/Customer/Model/Address/AbstractAddress.php b/app/code/Magento/Customer/Model/Address/AbstractAddress.php index 0ec87066d67c4..f710ef6846fd6 100644 --- a/app/code/Magento/Customer/Model/Address/AbstractAddress.php +++ b/app/code/Magento/Customer/Model/Address/AbstractAddress.php @@ -197,7 +197,7 @@ public function getName() { $name = ''; if ($this->_eavConfig->getAttribute('customer_address', 'prefix')->getIsVisible() && $this->getPrefix()) { - $name .= $this->getPrefix() . ' '; + $name .= __($this->getPrefix()) . ' '; } $name .= $this->getFirstname(); $middleName = $this->_eavConfig->getAttribute('customer_address', 'middlename'); @@ -206,7 +206,7 @@ public function getName() } $name .= ' ' . $this->getLastname(); if ($this->_eavConfig->getAttribute('customer_address', 'suffix')->getIsVisible() && $this->getSuffix()) { - $name .= ' ' . $this->getSuffix(); + $name .= ' ' . __($this->getSuffix()); } return $name; } diff --git a/app/code/Magento/Customer/Model/Options.php b/app/code/Magento/Customer/Model/Options.php index ec995a12e2bc2..c407cd616b6df 100644 --- a/app/code/Magento/Customer/Model/Options.php +++ b/app/code/Magento/Customer/Model/Options.php @@ -100,7 +100,7 @@ private function prepareNamePrefixSuffixOptions($options, $isOptional = false) $options = explode(';', trim($options)); foreach ($options as $value) { - $result[] = $this->escaper->escapeHtml(trim($value)) ?: ' '; + $result[] = $this->escaper->escapeHtml(trim(__($value))) ?: ' '; } if ($isOptional && trim(current($options))) { diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontVerifyCustomerDefaultCookieExpiryDateActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontVerifyCustomerDefaultCookieExpiryDateActionGroup.xml new file mode 100644 index 0000000000000..f5c40e5261414 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontVerifyCustomerDefaultCookieExpiryDateActionGroup.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontVerifyCustomerDefaultCookieExpiryDateActionGroup"> + <annotations> + <description>Verify a customer's cookies expiry date on browser's local storage in storefront</description> + </annotations> + <arguments> + <argument name="timezoneOffset" type="string" defaultValue="0"/> + <argument name="timeUnit" type="string" defaultValue="minute"/> + </arguments> + + <!--Verify that there are cookies exists with the given name `section_data_ids`, `mage-cache-sessid`, `mage-cache-storage`--> + <seeCookie userInput="section_data_ids" stepKey="seeCookieForMagentoSectionDataIds"/> + <seeCookie userInput="mage-cache-sessid" stepKey="seeCookieForMagentoCacheSessionId"/> + <seeCookie userInput="mage-cache-storage" stepKey="seeCookieForMagentoCacheStorage"/> + + <!--Grab the cookies attribute with the given names `section_data_ids`, `mage-cache-sessid`, `mage-cache-storage--> + <grabCookieAttributes userInput="section_data_ids" stepKey="grabCookieForMagentoDataIds"/> + <grabCookieAttributes userInput="mage-cache-sessid" stepKey="grabCookieForMagentoCacheSessionId"/> + <grabCookieAttributes userInput="mage-cache-storage" stepKey="grabCookieForMagentoCacheStorage"/> + + <!--Grab expected date--> + <generateDate date="{{timezoneOffset}} {{timeUnit}}" format="d/m/Y" timezone="UTC" stepKey="generateExpireDate"/> + + <!--Assert cookies `section_data_ids`, `mage-cache-sessid`, `mage-cache-storage` having expiry date equal to expected date--> + <assertEquals stepKey="validateExpiryDateForMagentoDataIds"> + <actualResult type="string">{{$grabCookieForMagentoDataIds['expiry']}}</actualResult> + <expectedResult type="string">{{$generateExpireDate}}</expectedResult> + </assertEquals> + <assertEquals stepKey="validateExpiryDateForMagentoCacheSessionId"> + <actualResult type="string">{{$grabCookieForMagentoCacheSessionId['expiry']}}</actualResult> + <expectedResult type="string">{{$generateExpireDate}}</expectedResult> + </assertEquals> + <assertEquals stepKey="validateExpiryDateForMagentoCacheStorage"> + <actualResult type="string">{{$grabCookieForMagentoCacheStorage['expiry']}}</actualResult> + <expectedResult type="string">{{$generateExpireDate}}</expectedResult> + </assertEquals> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontRetainLocalCacheStorageTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontRetainLocalCacheStorageTest.xml new file mode 100644 index 0000000000000..7661b1222fb57 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontRetainLocalCacheStorageTest.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontRetainLocalCacheStorageTest"> + <annotations> + <features value="Customer"/> + <stories value="Local cache storage is not retained for the expected period."/> + <title value="Verify that Local cache storage is retained for the expected period."/> + <description value="Verify that Local cache storage is retained for the expected period."/> + <severity value="AVERAGE"/> + <testCaseId value="AC-3635"/> + <group value="customer"/> + </annotations> + <before> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <magentoCLI command="config:set general/locale/timezone UTC" stepKey="setTimezone"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <magentoCLI command="config:set general/locale/timezone America/Los_Angeles" stepKey="setTimezone"/> + <!--Restore default configuration settings.--> + <magentoCLI command="config:set {{DefaultWebCookieLifetimeConfigData.path}} {{DefaultWebCookieLifetimeConfigData.value}}" stepKey="setDefaultCookieLifetime"/> + <!--Clear cache and perform reindex--> + <magentoCLI command="indexer:reindex" stepKey="performReindex"/> + <magentoCLI command="cache:clean" stepKey="cleanCache"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </after> + <!--Login to storefront from customer--> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <see userInput="Welcome, $$createCustomer.firstname$$ $$createCustomer.lastname$$!" selector="{{StorefrontPanelHeaderSection.welcomeMessage}}" stepKey="checkWelcomeMessage"/> + + <!--Verify default expiry date for cookies--> + <actionGroup ref="StorefrontVerifyCustomerDefaultCookieExpiryDateActionGroup" stepKey="VerifyCookiesExpiryDate"/> + + <!--Logout customer before in case of it logged in from previous test--> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogoutStorefront"/> + + <!--Login as admin--> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + + <!--Clear browser locale storage for magento site--> + <resetCookie userInput="section_data_ids" stepKey="resetCookieForMagentoCacheSectionDataIds"/> + <resetCookie userInput="mage-cache-sessid" stepKey="resetCookieForMagentoCacheSessionId"/> + <resetCookie userInput="mage-cache-storage" stepKey="resetCookieForMagentoCacheStorage"/> + + <!--Set-Cookie Lifetime to 30 days (2592000) under Stores > Configuration > General > Web > Default Cookie Settings--> + <actionGroup ref="AdminNavigateToDefaultCookieSettingsActionGroup" stepKey="goToCurrencySetupPage"/> + <!--Ensure the checkbox `use system value` is unchecked.--> + <uncheckOption selector="{{AdminDefaultCookieSettingsSection.DefaultCookieLifetimeSystemValueCheckbox}}" stepKey="uncheckCheckboxForSystemValue"/> + <fillField userInput="2592000" selector="{{AdminDefaultCookieSettingsSection.DefaultCookieLifetime}}" stepKey="fillDefaultLabel"/> + <click selector="{{AdminDefaultCookieSettingsSection.Save}}" stepKey="clickSaveConfig"/> + + <!--Clear cache and perform reindex--> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> + <argument name="indices" value=""/> + </actionGroup> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value=""/> + </actionGroup> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + + <!--Login storefront again using registered customer credentials--> + <actionGroup ref="StorefrontOpenCustomerLoginPageActionGroup" stepKey="goToSignInPage"/> + <actionGroup ref="StorefrontFillCustomerLoginFormActionGroup" stepKey="fillLoginFormWithCustomerData"> + <argument name="customer" value="$$createCustomer$$"/> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonFirstAttempt"/> + + <!--Grab current timezone offset after 30 days--> + <executeJS function="return (30*24*60);" stepKey="getTimezoneOffsetAfterReset"/> + <actionGroup ref="StorefrontVerifyCustomerDefaultCookieExpiryDateActionGroup" stepKey="VerifyCookiesExpiryDateAfterReset"> + <argument name="timezoneOffset" value="{$getTimezoneOffsetAfterReset}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/composer.json b/app/code/Magento/Customer/composer.json index 2d76da56bff7d..ef2047644759b 100644 --- a/app/code/Magento/Customer/composer.json +++ b/app/code/Magento/Customer/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Customer/view/frontend/templates/widget/name.phtml b/app/code/Magento/Customer/view/frontend/templates/widget/name.phtml index 00c1f124bd263..c725665547721 100644 --- a/app/code/Magento/Customer/view/frontend/templates/widget/name.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/widget/name.phtml @@ -24,31 +24,40 @@ $prefix = $block->showPrefix(); $middle = $block->showMiddlename(); $suffix = $block->showSuffix(); ?> -<?php if (($prefix || $middle || $suffix) && !$block->getNoWrap()) : ?> +<?php if (($prefix || $middle || $suffix) && !$block->getNoWrap()): ?> <div class="field required fullname <?= $block->escapeHtmlAttr($block->getContainerClassName()) ?>"> - <label for="<?= $block->escapeHtmlAttr($block->getFieldId('firstname')) ?>" class="label"><span><?= $block->escapeHtml(__('Name')) ?></span></label> + <label for="<?= $block->escapeHtmlAttr($block->getFieldId('firstname')) ?>" class="label"> + <span><?= $block->escapeHtml(__('Name')) ?></span> + </label> <div class="control"> <fieldset class="fieldset fieldset-fullname"> <div class="fields"> <?php endif; ?> - <?php if ($prefix) : ?> + <?php if ($prefix): ?> <div class="field field-name-prefix<?= $block->isPrefixRequired() ? ' required' : '' ?>"> - <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId('prefix')) ?>"><span><?= $block->escapeHtml($block->getStoreLabel('prefix')) ?></span></label> + <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId('prefix')) ?>"> + <span><?= $block->escapeHtml($block->getStoreLabel('prefix')) ?></span> + </label> <div class="control"> - <?php if ($block->getPrefixOptions() === false) : ?> + <?php if ($block->getPrefixOptions() === false): ?> <input type="text" id="<?= $block->escapeHtmlAttr($block->getFieldId('prefix')) ?>" name="<?= $block->escapeHtmlAttr($block->getFieldName('prefix')) ?>" value="<?= $block->escapeHtmlAttr($block->getObject()->getPrefix()) ?>" title="<?= $block->escapeHtmlAttr($block->getStoreLabel('prefix')) ?>" - class="input-text <?= $block->escapeHtmlAttr($block->getAttributeValidationClass('prefix')) ?>" <?= $block->isPrefixRequired() ? ' data-validate="{required:true}"' : '' ?>> - <?php else : ?> + class="input-text + <?= $block->escapeHtmlAttr($block->getAttributeValidationClass('prefix')) ?>" + <?= $block->isPrefixRequired() ? ' data-validate="{required:true}"' : '' ?>> + <?php else: ?> <select id="<?= $block->escapeHtmlAttr($block->getFieldId('prefix')) ?>" name="<?= $block->escapeHtmlAttr($block->getFieldName('prefix')) ?>" title="<?= $block->escapeHtmlAttr($block->getStoreLabel('prefix')) ?>" - class="<?= $block->escapeHtmlAttr($block->getAttributeValidationClass('prefix')) ?>" <?= $block->isPrefixRequired() ? ' data-validate="{required:true}"' : '' ?> > - <?php foreach ($block->getPrefixOptions() as $_option) : ?> - <option value="<?= $block->escapeHtmlAttr($_option) ?>"<?php if ($block->getObject()->getPrefix() == $_option) : ?> selected="selected"<?php endif; ?>> + class="<?= $block->escapeHtmlAttr($block->getAttributeValidationClass('prefix')) ?>" + <?= $block->isPrefixRequired() ? ' data-validate="{required:true}"' : '' ?> > + <?php foreach ($block->getPrefixOptions() as $_option): ?> + <option value="<?= $block->escapeHtmlAttr(__($_option)) ?>" + <?php if ($block->getObject()->getPrefix() == $_option): ?> + selected="selected"<?php endif; ?>> <?= $block->escapeHtml(__($_option)) ?> </option> <?php endforeach; ?> @@ -58,55 +67,76 @@ $suffix = $block->showSuffix(); </div> <?php endif; ?> <div class="field field-name-firstname required"> - <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId('firstname')) ?>"><span><?= $block->escapeHtml($block->getStoreLabel('firstname')) ?></span></label> + <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId('firstname')) ?>"> + <span><?= $block->escapeHtml($block->getStoreLabel('firstname')) ?></span> + </label> <div class="control"> <input type="text" id="<?= $block->escapeHtmlAttr($block->getFieldId('firstname')) ?>" name="<?= $block->escapeHtmlAttr($block->getFieldName('firstname')) ?>" value="<?= $block->escapeHtmlAttr($block->getObject()->getFirstname()) ?>" title="<?= $block->escapeHtmlAttr($block->getStoreLabel('firstname')) ?>" - class="input-text <?= $block->escapeHtmlAttr($block->getAttributeValidationClass('firstname')) ?>" <?= ($block->getAttributeValidationClass('firstname') == 'required-entry') ? ' data-validate="{required:true}"' : '' ?>> + class="input-text + <?= $block->escapeHtmlAttr($block->getAttributeValidationClass('firstname')) ?>" + <?= ($block->getAttributeValidationClass('firstname') == 'required-entry') ? ' + data-validate="{required:true}"' : '' ?>> </div> </div> - <?php if ($middle) : ?> + <?php if ($middle): ?> <?php $isMiddlenameRequired = $block->isMiddlenameRequired(); ?> <div class="field field-name-middlename<?= $isMiddlenameRequired ? ' required' : '' ?>"> - <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId('middlename')) ?>"><span><?= $block->escapeHtml($block->getStoreLabel('middlename')) ?></span></label> + <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId('middlename')) ?>"> + <span><?= $block->escapeHtml($block->getStoreLabel('middlename')) ?></span> + </label> <div class="control"> <input type="text" id="<?= $block->escapeHtmlAttr($block->getFieldId('middlename')) ?>" name="<?= $block->escapeHtmlAttr($block->getFieldName('middlename')) ?>" value="<?= $block->escapeHtmlAttr($block->getObject()->getMiddlename()) ?>" title="<?= $block->escapeHtmlAttr($block->getStoreLabel('middlename')) ?>" - class="input-text <?= $block->escapeHtmlAttr($block->getAttributeValidationClass('middlename')) ?>" <?= $isMiddlenameRequired ? ' data-validate="{required:true}"' : '' ?>> + class="input-text + <?= $block->escapeHtmlAttr($block->getAttributeValidationClass('middlename')) ?>" + <?= $isMiddlenameRequired ? ' data-validate="{required:true}"' : '' ?>> </div> </div> <?php endif; ?> <div class="field field-name-lastname required"> - <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId('lastname')) ?>"><span><?= $block->escapeHtml($block->getStoreLabel('lastname')) ?></span></label> + <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId('lastname')) ?>"> + <span><?= $block->escapeHtml($block->getStoreLabel('lastname')) ?></span> + </label> <div class="control"> <input type="text" id="<?= $block->escapeHtmlAttr($block->getFieldId('lastname')) ?>" name="<?= $block->escapeHtmlAttr($block->getFieldName('lastname')) ?>" value="<?= $block->escapeHtmlAttr($block->getObject()->getLastname()) ?>" title="<?= $block->escapeHtmlAttr($block->getStoreLabel('lastname')) ?>" - class="input-text <?= $block->escapeHtmlAttr($block->getAttributeValidationClass('lastname')) ?>" <?= ($block->getAttributeValidationClass('lastname') == 'required-entry') ? ' data-validate="{required:true}"' : '' ?>> + class="input-text + <?= $block->escapeHtmlAttr($block->getAttributeValidationClass('lastname')) ?>" + <?= ($block->getAttributeValidationClass('lastname') == 'required-entry') ? ' + data-validate="{required:true}"' : '' ?>> </div> </div> - <?php if ($suffix) : ?> + <?php if ($suffix): ?> <div class="field field-name-suffix<?= $block->isSuffixRequired() ? ' required' : '' ?>"> - <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId('suffix')) ?>"><span><?= $block->escapeHtml($block->getStoreLabel('suffix')) ?></span></label> + <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId('suffix')) ?>"> + <span><?= $block->escapeHtml($block->getStoreLabel('suffix')) ?></span> + </label> <div class="control"> - <?php if ($block->getSuffixOptions() === false) : ?> + <?php if ($block->getSuffixOptions() === false): ?> <input type="text" id="<?= $block->escapeHtmlAttr($block->getFieldId('suffix')) ?>" name="<?= $block->escapeHtmlAttr($block->getFieldName('suffix')) ?>" value="<?= $block->escapeHtmlAttr($block->getObject()->getSuffix()) ?>" title="<?= $block->escapeHtmlAttr($block->getStoreLabel('suffix')) ?>" - class="input-text <?= $block->escapeHtmlAttr($block->getAttributeValidationClass('suffix')) ?>" <?= $block->isSuffixRequired() ? ' data-validate="{required:true}"' : '' ?>> - <?php else : ?> + class="input-text + <?= $block->escapeHtmlAttr($block->getAttributeValidationClass('suffix')) ?>" + <?= $block->isSuffixRequired() ? ' data-validate="{required:true}"' : '' ?>> + <?php else: ?> <select id="<?= $block->escapeHtmlAttr($block->getFieldId('suffix')) ?>" name="<?= $block->escapeHtmlAttr($block->getFieldName('suffix')) ?>" title="<?= $block->escapeHtmlAttr($block->getStoreLabel('suffix')) ?>" - class="<?= $block->escapeHtmlAttr($block->getAttributeValidationClass('suffix')) ?>" <?= $block->isSuffixRequired() ? ' data-validate="{required:true}"' : '' ?>> - <?php foreach ($block->getSuffixOptions() as $_option) : ?> - <option value="<?= $block->escapeHtmlAttr($_option) ?>"<?php if ($block->getObject()->getSuffix() == $_option) : ?> selected="selected"<?php endif; ?>> + class="<?= $block->escapeHtmlAttr($block->getAttributeValidationClass('suffix')) ?>" + <?= $block->isSuffixRequired() ? ' data-validate="{required:true}"' : '' ?>> + <?php foreach ($block->getSuffixOptions() as $_option): ?> + <option value="<?= $block->escapeHtmlAttr(__($_option)) ?>" + <?php if ($block->getObject()->getSuffix() == $_option): ?> + selected="selected"<?php endif; ?>> <?= $block->escapeHtml(__($_option)) ?> </option> <?php endforeach; ?> @@ -116,7 +146,7 @@ $suffix = $block->showSuffix(); </div> <?php endif; ?> - <?php if (($prefix || $middle || $suffix) && !$block->getNoWrap()) : ?> + <?php if (($prefix || $middle || $suffix) && !$block->getNoWrap()): ?> </div> </fieldset> </div> diff --git a/app/code/Magento/CustomerAnalytics/composer.json b/app/code/Magento/CustomerAnalytics/composer.json index 396c7d4ca3364..d02051d5148cd 100644 --- a/app/code/Magento/CustomerAnalytics/composer.json +++ b/app/code/Magento/CustomerAnalytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-customer-analytics", "description": "N/A", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-customer": "*", "magento/module-analytics": "*" diff --git a/app/code/Magento/CustomerDownloadableGraphQl/composer.json b/app/code/Magento/CustomerDownloadableGraphQl/composer.json index f33d05e18568a..99e2f94da4081 100644 --- a/app/code/Magento/CustomerDownloadableGraphQl/composer.json +++ b/app/code/Magento/CustomerDownloadableGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-downloadable-graph-ql": "*", "magento/module-graph-ql": "*", "magento/framework": "*" diff --git a/app/code/Magento/CustomerGraphQl/composer.json b/app/code/Magento/CustomerGraphQl/composer.json index 30d94c20acc98..5967d2e9f8ac7 100644 --- a/app/code/Magento/CustomerGraphQl/composer.json +++ b/app/code/Magento/CustomerGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-authorization": "*", "magento/module-customer": "*", "magento/module-eav": "*", diff --git a/app/code/Magento/CustomerImportExport/composer.json b/app/code/Magento/CustomerImportExport/composer.json index 2f5c74020e602..09eb16c1d8a01 100644 --- a/app/code/Magento/CustomerImportExport/composer.json +++ b/app/code/Magento/CustomerImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/Deploy/Package/Processor/PreProcessor/Less.php b/app/code/Magento/Deploy/Package/Processor/PreProcessor/Less.php index f4339b40f5be8..29283c1c26914 100644 --- a/app/code/Magento/Deploy/Package/Processor/PreProcessor/Less.php +++ b/app/code/Magento/Deploy/Package/Processor/PreProcessor/Less.php @@ -100,7 +100,7 @@ public function process(Package $package, array $options) if ($packageFile && $packageFile->getOrigPackage() === $package) { continue; } - $deployFileName = $this->fileNameResolver->resolve($file->getFileName()); + $deployFileName = $this->fileNameResolver->resolve($file->getFileName() ?? ''); if ($deployFileName !== $file->getFileName()) { if ($this->hasOverrides($file, $package)) { $file = clone $file; diff --git a/app/code/Magento/Deploy/Service/DeployStaticFile.php b/app/code/Magento/Deploy/Service/DeployStaticFile.php index a0cd91bfe684f..594cb6120be94 100644 --- a/app/code/Magento/Deploy/Service/DeployStaticFile.php +++ b/app/code/Magento/Deploy/Service/DeployStaticFile.php @@ -6,6 +6,8 @@ namespace Magento\Deploy\Service; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\File\WriteInterface; use Magento\Framework\View\Asset\Minification; @@ -80,11 +82,14 @@ public function __construct( } /** + * Deploy static file + * * @param string $fileName * @param array $params ['area' =>, 'theme' =>, 'locale' =>, 'module' =>] * @return string + * @throws LocalizedException */ - public function deployFile($fileName, array $params = []) + public function deployFile(string $fileName, array $params = []): string { $params['publish'] = true; $asset = $this->assetRepo->createAsset($this->resolveFile($fileName), $params); @@ -95,10 +100,14 @@ public function deployFile($fileName, array $params = []) } /** + * Delete static file + * * @param string $path * @return void + * @throws FileSystemException + * phpcs:disable */ - public function deleteFile($path) + public function deleteFile(string $path) { if ($this->pubStaticDir->isExist($path)) { $absolutePath = $this->pubStaticDir->getAbsolutePath($path); @@ -120,8 +129,10 @@ public function deleteFile($path) * @param string $fileName * @param string $filePath * @return string|false + * @throws FileSystemException + * phpcs:enable */ - public function readFile($fileName, $filePath) + public function readFile(string $fileName, string $filePath): bool|string { $fileName = $this->minification->addMinifiedSign($fileName); $relativePath = $filePath . DIRECTORY_SEPARATOR . $this->resolveFile($fileName); @@ -133,11 +144,13 @@ public function readFile($fileName, $filePath) } /** + * Open static file + * * @param string $fileName * @param string $filePath * @return WriteInterface */ - public function openFile($fileName, $filePath) + public function openFile(string $fileName, string $filePath): WriteInterface { $relativePath = $filePath . DIRECTORY_SEPARATOR . $this->resolveFile($fileName); return $this->pubStaticDir->openFile($relativePath, 'w+'); @@ -150,8 +163,9 @@ public function openFile($fileName, $filePath) * @param string $filePath * @param string $content * @return int The number of bytes that were written. + * @throws FileSystemException */ - public function writeFile($fileName, $filePath, $content) + public function writeFile(string $fileName, string $filePath, string $content): int { $relativePath = $filePath . DIRECTORY_SEPARATOR . $this->resolveFile($fileName); return $this->pubStaticDir->writeFile($relativePath, $content); @@ -164,8 +178,9 @@ public function writeFile($fileName, $filePath, $content) * @param string $sourcePath * @param string $targetPath * @return bool + * @throws FileSystemException */ - public function copyFile($fileName, $sourcePath, $targetPath) + public function copyFile(string $fileName, string $sourcePath, string $targetPath): bool { $fileName = $this->minification->addMinifiedSign($fileName); return $this->pubStaticDir->copyFile( @@ -179,9 +194,10 @@ public function copyFile($fileName, $sourcePath, $targetPath) * * @param string $fileName * @param string $filePath - * @return string + * @return bool|string + * @throws FileSystemException */ - public function readTmpFile($fileName, $filePath) + public function readTmpFile(string $fileName, string $filePath): bool|string { $relativePath = $filePath . DIRECTORY_SEPARATOR . $fileName; return $this->tmpDir->isFile($relativePath) ? $this->tmpDir->readFile($relativePath) : false; @@ -194,10 +210,12 @@ public function readTmpFile($fileName, $filePath) * @param string $filePath * @param string $content * @return int The number of bytes that were written. + * @throws FileSystemException */ - public function writeTmpFile($fileName, $filePath, $content) + public function writeTmpFile(string $fileName, string $filePath, string $content): int { $relativePath = $filePath . DIRECTORY_SEPARATOR . $this->resolveFile($fileName); + return $this->tmpDir->writeFile($relativePath, $content); } @@ -207,14 +225,12 @@ public function writeTmpFile($fileName, $filePath, $content) * @param string $fileName * @return string */ - private function resolveFile($fileName) + private function resolveFile(string $fileName): string { - $compiledFile = str_replace( + return str_replace( Repository::FILE_ID_SEPARATOR, '/', $this->fileNameResolver->resolve($fileName) ); - - return $compiledFile; } } diff --git a/app/code/Magento/Deploy/Test/Unit/Service/DeployTranslationsDictionaryTest.php b/app/code/Magento/Deploy/Test/Unit/Service/DeployTranslationsDictionaryTest.php index 9890de3458a9c..a875ce357f31c 100644 --- a/app/code/Magento/Deploy/Test/Unit/Service/DeployTranslationsDictionaryTest.php +++ b/app/code/Magento/Deploy/Test/Unit/Service/DeployTranslationsDictionaryTest.php @@ -76,6 +76,8 @@ function ($checkDictionary, $params) use ($dictionary, $area, $theme, $locale) { $this->assertEquals($area, $params['area']); $this->assertEquals($theme, $params['theme']); $this->assertEquals($locale, $params['locale']); + + return $dictionary; } ); diff --git a/app/code/Magento/Deploy/composer.json b/app/code/Magento/Deploy/composer.json index e965b6222e375..c90a64299e8e5 100644 --- a/app/code/Magento/Deploy/composer.json +++ b/app/code/Magento/Deploy/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-config": "*", "magento/module-require-js": "*", diff --git a/app/code/Magento/Developer/composer.json b/app/code/Magento/Developer/composer.json index 49b9d324f0d11..3f75c5bef7d44 100644 --- a/app/code/Magento/Developer/composer.json +++ b/app/code/Magento/Developer/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-config": "*", "magento/module-store": "*" diff --git a/app/code/Magento/Dhl/composer.json b/app/code/Magento/Dhl/composer.json index 9596f789be5fc..26b8546164965 100644 --- a/app/code/Magento/Dhl/composer.json +++ b/app/code/Magento/Dhl/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "lib-libxml": "*", "magento/framework": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Directory/Model/Currency/Import/FixerIoApiLayer.php b/app/code/Magento/Directory/Model/Currency/Import/FixerIoApiLayer.php new file mode 100644 index 0000000000000..439ab4599cd97 --- /dev/null +++ b/app/code/Magento/Directory/Model/Currency/Import/FixerIoApiLayer.php @@ -0,0 +1,261 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Directory\Model\Currency\Import; + +use Exception; +use Laminas\Http\Request; +use Magento\Directory\Model\CurrencyFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\HTTP\LaminasClientFactory as HttpClientFactory; +use Magento\Store\Model\ScopeInterface; +use Magento\Framework\HTTP\LaminasClient; + +/** + * Currency rate import model (https://apilayer.com/marketplace/fixer-api) + */ +class FixerIoApiLayer implements ImportInterface +{ + private const CURRENCY_CONVERTER_HOST = 'https://api.apilayer.com'; + private const CURRENCY_CONVERTER_URL_PATH = '/fixer/latest?' + . 'apikey={{ACCESS_KEY}}&base={{CURRENCY_FROM}}&symbols={{CURRENCY_TO}}'; + + /** + * @var array + */ + private $messages = []; + + /** + * @var HttpClientFactory + */ + private $httpClientFactory; + + /** + * @var CurrencyFactory + */ + private $currencyFactory; + + /** + * Core scope config + * + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * Initialize dependencies + * + * @param CurrencyFactory $currencyFactory + * @param ScopeConfigInterface $scopeConfig + * @param HttpClientFactory $httpClientFactory + */ + public function __construct( + CurrencyFactory $currencyFactory, + ScopeConfigInterface $scopeConfig, + HttpClientFactory $httpClientFactory + ) { + $this->currencyFactory = $currencyFactory; + $this->scopeConfig = $scopeConfig; + $this->httpClientFactory = $httpClientFactory; + } + + /** + * Import rates + * + * @return $this + */ + public function importRates() + { + $data = $this->fetchRates(); + $this->saveRates($data); + return $this; + } + + /** + * @inheritdoc + */ + public function fetchRates() + { + $data = []; + $currencies = $this->getCurrencyCodes(); + $defaultCurrencies = $this->getDefaultCurrencyCodes(); + + foreach ($defaultCurrencies as $currencyFrom) { + if (!isset($data[$currencyFrom])) { + $data[$currencyFrom] = []; + } + $data = $this->convertBatch($data, $currencyFrom, $currencies); + ksort($data[$currencyFrom]); + } + return $data; + } + + /** + * @inheritdoc + */ + public function getMessages() + { + return $this->messages; + } + + /** + * Return currencies convert rates in batch mode + * + * @param array $data + * @param string $currencyFrom + * @param array $currenciesTo + * @return array + */ + private function convertBatch(array $data, string $currencyFrom, array $currenciesTo): array + { + $accessKey = $this->scopeConfig->getValue('currency/fixerio_apilayer/api_key', ScopeInterface::SCOPE_STORE); + if (empty($accessKey)) { + $this->messages[] = __('No API Key was specified or an invalid API Key was specified.'); + $data[$currencyFrom] = $this->makeEmptyResponse($currenciesTo); + return $data; + } + + $currenciesStr = implode(',', $currenciesTo); + $url = str_replace( + ['{{ACCESS_KEY}}', '{{CURRENCY_FROM}}', '{{CURRENCY_TO}}'], + [$accessKey, $currencyFrom, $currenciesStr], + self::CURRENCY_CONVERTER_HOST . self::CURRENCY_CONVERTER_URL_PATH + ); + // phpcs:ignore Magento2.Functions.DiscouragedFunction + set_time_limit(0); + try { + $response = $this->getServiceResponse($url); + } finally { + ini_restore('max_execution_time'); + } + + if (!$this->validateResponse($response, $currencyFrom)) { + $data[$currencyFrom] = $this->makeEmptyResponse($currenciesTo); + return $data; + } + + foreach ($currenciesTo as $currencyTo) { + if ($currencyFrom == $currencyTo) { + $data[$currencyFrom][$currencyTo] = 1; + } else { + if (empty($response['rates'][$currencyTo])) { + $message = 'We can\'t retrieve a rate from %1 for %2.'; + $this->messages[] = __($message, self::CURRENCY_CONVERTER_HOST, $currencyTo); + $data[$currencyFrom][$currencyTo] = null; + } else { + $data[$currencyFrom][$currencyTo] = (double)$response['rates'][$currencyTo]; + } + } + } + return $data; + } + + /** + * Saving currency rates + * + * @param array $rates + * @return \Magento\Directory\Model\Currency\Import\FixerIoApiLayer + */ + private function saveRates(array $rates) + { + foreach ($rates as $currencyCode => $currencyRates) { + $this->currencyFactory->create()->setId($currencyCode)->setRates($currencyRates)->save(); + } + return $this; + } + + /** + * Get apilayer.com service response + * + * @param string $url + * @param int $retry + * @return array + */ + private function getServiceResponse(string $url, int $retry = 0): array + { + /** @var LaminasClient $httpClient */ + $httpClient = $this->httpClientFactory->create(); + $response = []; + + try { + $httpClient->setUri($url); + $httpClient->setOptions( + [ + 'timeout' => $this->scopeConfig->getValue( + 'currency/fixerio_apilayer/timeout', + ScopeInterface::SCOPE_STORE + ), + ] + ); + $httpClient->setMethod(Request::METHOD_GET); + $jsonResponse = $httpClient->send()->getBody(); + + $response = json_decode($jsonResponse, true); + } catch (Exception $e) { + if ($retry == 0) { + $response = $this->getServiceResponse($url, 1); + } + } + return $response; + } + + /** + * Creates array for provided currencies with empty rates. + * + * @param array $currenciesTo + * @return array + */ + private function makeEmptyResponse(array $currenciesTo): array + { + return array_fill_keys($currenciesTo, null); + } + + /** + * Validates rates response. + * + * @param array $response + * @param string $baseCurrency + * @return bool + */ + private function validateResponse(array $response, string $baseCurrency): bool + { + if ($response['success']) { + return true; + } + + $errorCodes = [ + 101 => __('No API Key was specified or an invalid API Key was specified.'), + 102 => __('The account this API request is coming from is inactive.'), + 105 => __('The "%1" is not allowed as base currency for your subscription plan.', $baseCurrency), + 201 => __('An invalid base currency has been entered.'), + ]; + + $this->messages[] = $errorCodes[$response['error']['code']] ?? __('Currency rates can\'t be retrieved.'); + + return false; + } + + /** + * Retrieve currency codes + * + * @return array + */ + private function getCurrencyCodes() + { + return $this->currencyFactory->create()->getConfigAllowCurrencies(); + } + + /** + * Retrieve default currency codes + * + * @return array + */ + private function getDefaultCurrencyCodes() + { + return $this->currencyFactory->create()->getConfigBaseCurrencies(); + } +} diff --git a/app/code/Magento/Directory/Test/Mftf/Test/CustomCurrencySymbolWithSpaceTest.xml b/app/code/Magento/Directory/Test/Mftf/Test/CustomCurrencySymbolWithSpaceTest.xml index 50fb0fe48ac4c..62cf00e7735b6 100644 --- a/app/code/Magento/Directory/Test/Mftf/Test/CustomCurrencySymbolWithSpaceTest.xml +++ b/app/code/Magento/Directory/Test/Mftf/Test/CustomCurrencySymbolWithSpaceTest.xml @@ -18,6 +18,9 @@ <testCaseId value="AC-6031" /> <useCaseId value="ACP2E-1012"/> <group value="currency"/> + <skip> + <issueId value="ACQE-4266"/> + </skip> </annotations> <before> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> diff --git a/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/FixerIoApiLayerTest.php b/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/FixerIoApiLayerTest.php new file mode 100644 index 0000000000000..8081cc3ddfefc --- /dev/null +++ b/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/FixerIoApiLayerTest.php @@ -0,0 +1,125 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Directory\Test\Unit\Model\Currency\Import; + +use Magento\Directory\Model\Currency; +use Magento\Directory\Model\Currency\Import\FixerIoApiLayer; +use Magento\Directory\Model\CurrencyFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\DataObject; +use Magento\Framework\HTTP\LaminasClient; +use Magento\Framework\HTTP\LaminasClientFactory; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class FixerIoApiLayerTest extends TestCase +{ + /** + * @var FixerIoApiLayer + */ + private $model; + + /** + * @var CurrencyFactory|MockObject + */ + private $currencyFactory; + + /** + * @var LaminasClientFactory|MockObject + */ + private $httpClientFactory; + + /** + * @var ScopeConfigInterface|MockObject + */ + private $scopeConfig; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->currencyFactory = $this->getMockBuilder(CurrencyFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->httpClientFactory = $this->getMockBuilder(LaminasClientFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->setMethods([]) + ->getMockForAbstractClass(); + + $this->model = new FixerIoApiLayer($this->currencyFactory, $this->scopeConfig, $this->httpClientFactory); + } + + /** + * Test Fetch Rates + * + * @return void + */ + public function testFetchRates(): void + { + $currencyFromList = ['USD']; + $currencyToList = ['EUR', 'UAH']; + $responseBody = '{"success":"true","base":"USD","date":"2015-10-07","rates":{"EUR":0.9022}}'; + $expectedCurrencyRateList = ['USD' => ['EUR' => 0.9022, 'UAH' => null]]; + $message = "We can't retrieve a rate from " + . "https://api.apilayer.com for UAH."; + + $this->scopeConfig->method('getValue') + ->withConsecutive( + ['currency/fixerio_apilayer/api_key', 'store'], + ['currency/fixerio_apilayer/timeout', 'store'] + ) + ->willReturnOnConsecutiveCalls('api_key', 100); + + /** @var Currency|MockObject $currency */ + $currency = $this->getMockBuilder(Currency::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var LaminasClient|MockObject $httpClient */ + $httpClient = $this->getMockBuilder(LaminasClient::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var DataObject|MockObject $currencyMock */ + $httpResponse = $this->getMockBuilder(DataObject::class) + ->disableOriginalConstructor() + ->setMethods(['getBody']) + ->getMock(); + + $this->currencyFactory->method('create') + ->willReturn($currency); + $currency->method('getConfigBaseCurrencies') + ->willReturn($currencyFromList); + $currency->method('getConfigAllowCurrencies') + ->willReturn($currencyToList); + + $this->httpClientFactory->method('create') + ->willReturn($httpClient); + $httpClient->method('setUri') + ->willReturnSelf(); + $httpClient->method('setOptions') + ->willReturnSelf(); + $httpClient->method('setMethod') + ->willReturnSelf(); + $httpClient->method('send') + ->willReturn($httpResponse); + $httpResponse->method('getBody') + ->willReturn($responseBody); + + self::assertEquals($expectedCurrencyRateList, $this->model->fetchRates()); + + $messages = $this->model->getMessages(); + self::assertNotEmpty($messages); + self::assertIsArray($messages); + self::assertEquals($message, (string)$messages[0]); + } +} diff --git a/app/code/Magento/Directory/composer.json b/app/code/Magento/Directory/composer.json index c3973b9cee0c9..a2538a6378d5b 100644 --- a/app/code/Magento/Directory/composer.json +++ b/app/code/Magento/Directory/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "lib-libxml": "*", "magento/framework": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Directory/etc/adminhtml/system.xml b/app/code/Magento/Directory/etc/adminhtml/system.xml index 8b67f07e98202..e38bc5cec0caf 100644 --- a/app/code/Magento/Directory/etc/adminhtml/system.xml +++ b/app/code/Magento/Directory/etc/adminhtml/system.xml @@ -35,11 +35,24 @@ </field> </group> <group id="fixerio" translate="label" sortOrder="35" showInDefault="1"> - <label>Fixer.io</label> + <label>Fixer.io (legacy)</label> <field id="api_key" translate="label" type="obscure" sortOrder="5" showInDefault="1"> <label>API Key</label> <config_path>currency/fixerio/api_key</config_path> <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> + <comment>Use this field if your API Key was generated at Fixer.io. If your key was generated via ApiLayer then use "Setting > General > Currency setup > Fixer Api via APILayer" configuration.</comment> + </field> + <field id="timeout" translate="label" type="text" sortOrder="10" showInDefault="1"> + <label>Connection Timeout in Seconds</label> + <validate>validate-zero-or-greater validate-number</validate> + </field> + </group> + <group id="fixerio_apilayer" translate="label" sortOrder="35" showInDefault="1"> + <label>Fixer Api (APILayer)</label> + <field id="api_key" translate="label" type="obscure" sortOrder="5" showInDefault="1"> + <label>API Key</label> + <config_path>currency/fixerio_apilayer/api_key</config_path> + <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> </field> <field id="timeout" translate="label" type="text" sortOrder="10" showInDefault="1"> <label>Connection Timeout in Seconds</label> diff --git a/app/code/Magento/Directory/etc/config.xml b/app/code/Magento/Directory/etc/config.xml index 32099ff9d8af5..508cebc974b05 100644 --- a/app/code/Magento/Directory/etc/config.xml +++ b/app/code/Magento/Directory/etc/config.xml @@ -22,6 +22,10 @@ <timeout>100</timeout> <api_key backend_model="Magento\Config\Model\Config\Backend\Encrypted" /> </fixerio> + <fixerio_apilayer> + <timeout>100</timeout> + <api_key backend_model="Magento\Config\Model\Config\Backend\Encrypted" /> + </fixerio_apilayer> <currencyconverterapi> <timeout>100</timeout> <api_key backend_model="Magento\Config\Model\Config\Backend\Encrypted" /> diff --git a/app/code/Magento/Directory/etc/di.xml b/app/code/Magento/Directory/etc/di.xml index fb2c526ac730b..157af13afb53f 100644 --- a/app/code/Magento/Directory/etc/di.xml +++ b/app/code/Magento/Directory/etc/di.xml @@ -11,9 +11,13 @@ <arguments> <argument name="servicesConfig" xsi:type="array"> <item name="fixerio" xsi:type="array"> - <item name="label" xsi:type="string" translatable="true">Fixer.io</item> + <item name="label" xsi:type="string" translatable="true">Fixer.io (legacy)</item> <item name="class" xsi:type="string">Magento\Directory\Model\Currency\Import\FixerIo</item> </item> + <item name="fixerio_apilayer" xsi:type="array"> + <item name="label" xsi:type="string" translatable="true">Fixer Api (APILayer)</item> + <item name="class" xsi:type="string">Magento\Directory\Model\Currency\Import\FixerIoApiLayer</item> + </item> <item name="currencyconverterapi" xsi:type="array"> <item name="label" xsi:type="string" translatable="true">Currency Converter API</item> <item name="class" xsi:type="string">Magento\Directory\Model\Currency\Import\CurrencyConverterApi</item> diff --git a/app/code/Magento/DirectoryGraphQl/composer.json b/app/code/Magento/DirectoryGraphQl/composer.json index 6acbef5c5534c..082fa8ae742c1 100644 --- a/app/code/Magento/DirectoryGraphQl/composer.json +++ b/app/code/Magento/DirectoryGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-directory": "*", "magento/module-store": "*", "magento/module-graph-ql": "*", diff --git a/app/code/Magento/Downloadable/composer.json b/app/code/Magento/Downloadable/composer.json index a9487f8c430d3..abd6479f10e2d 100644 --- a/app/code/Magento/Downloadable/composer.json +++ b/app/code/Magento/Downloadable/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/DownloadableGraphQl/composer.json b/app/code/Magento/DownloadableGraphQl/composer.json index 214b857bcd6f9..c286438b49356 100644 --- a/app/code/Magento/DownloadableGraphQl/composer.json +++ b/app/code/Magento/DownloadableGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-store": "*", "magento/module-catalog": "*", "magento/module-downloadable": "*", diff --git a/app/code/Magento/DownloadableImportExport/composer.json b/app/code/Magento/DownloadableImportExport/composer.json index d6daea4b2ac17..bc35e44944c91 100644 --- a/app/code/Magento/DownloadableImportExport/composer.json +++ b/app/code/Magento/DownloadableImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-catalog-import-export": "*", diff --git a/app/code/Magento/Eav/composer.json b/app/code/Magento/Eav/composer.json index 60915bd4ba590..40d249ba472b9 100644 --- a/app/code/Magento/Eav/composer.json +++ b/app/code/Magento/Eav/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/EavGraphQl/composer.json b/app/code/Magento/EavGraphQl/composer.json index cfb8dc7ac9e11..a19a8bc3d5109 100644 --- a/app/code/Magento/EavGraphQl/composer.json +++ b/app/code/Magento/EavGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-eav": "*" }, diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php b/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php index b5151bbd578c5..c9f32c1fa584f 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php @@ -19,9 +19,11 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Stdlib\ArrayManager; use Psr\Log\LoggerInterface; +use Magento\AdvancedSearch\Helper\Data; /** * Elasticsearch adapter + * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Elasticsearch @@ -110,6 +112,11 @@ class Elasticsearch */ private $arrayManager; + /** + * @var Data + */ + protected $helper; + /** * @var array */ @@ -125,6 +132,7 @@ class Elasticsearch * @param LoggerInterface $logger * @param Index\IndexNameResolver $indexNameResolver * @param BatchDataMapperInterface $batchDocumentDataMapper + * @param Data $helper * @param array $options * @param ProductAttributeRepositoryInterface|null $productAttributeRepository * @param StaticField|null $staticFieldProvider @@ -141,6 +149,7 @@ public function __construct( LoggerInterface $logger, IndexNameResolver $indexNameResolver, BatchDataMapperInterface $batchDocumentDataMapper, + Data $helper, $options = [], ProductAttributeRepositoryInterface $productAttributeRepository = null, StaticField $staticFieldProvider = null, @@ -154,6 +163,7 @@ public function __construct( $this->logger = $logger; $this->indexNameResolver = $indexNameResolver; $this->batchDocumentDataMapper = $batchDocumentDataMapper; + $this->helper = $helper; $this->productAttributeRepository = $productAttributeRepository ?: ObjectManager::getInstance()->get(ProductAttributeRepositoryInterface::class); $this->staticFieldProvider = $staticFieldProvider ?: @@ -329,18 +339,30 @@ protected function getDocsArrayInBulkIndexFormat( ]; foreach ($documents as $id => $document) { - $bulkArray['body'][] = [ - $action => [ - '_id' => $id, - '_type' => $this->clientConfig->getEntityType(), - '_index' => $indexName - ] - ]; + if ($this->helper->isClientOpenSearchV2()) { + $bulkArray['body'][] = [ + $action => [ + '_id' => $id, + '_index' => $indexName + ] + ]; + } else { + $bulkArray['body'][] = [ + $action => [ + '_id' => $id, + '_type' => $this->clientConfig->getEntityType(), + '_index' => $indexName + ] + ]; + } if ($action == self::BULK_ACTION_INDEX) { $bulkArray['body'][] = $document; } } + if ($this->helper->isClientOpenSearchV2()) { + unset($bulkArray['type']); + } return $bulkArray; } diff --git a/app/code/Magento/Elasticsearch/composer.json b/app/code/Magento/Elasticsearch/composer.json index 30e0899981e6b..9e6d4ceaf16e3 100644 --- a/app/code/Magento/Elasticsearch/composer.json +++ b/app/code/Magento/Elasticsearch/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-elasticsearch", "description": "N/A", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-advanced-search": "*", "magento/module-catalog": "*", "magento/module-catalog-search": "*", diff --git a/app/code/Magento/Elasticsearch7/composer.json b/app/code/Magento/Elasticsearch7/composer.json index 1c3fbd98ecb59..89f41bf14b0dc 100644 --- a/app/code/Magento/Elasticsearch7/composer.json +++ b/app/code/Magento/Elasticsearch7/composer.json @@ -2,10 +2,10 @@ "name": "magento/module-elasticsearch-7", "description": "N/A", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-elasticsearch": "*", - "elasticsearch/elasticsearch": "~7.17.0", + "elasticsearch/elasticsearch": "^7.17", "magento/module-advanced-search": "*", "magento/module-catalog-search": "*", "magento/module-search": "*" diff --git a/app/code/Magento/Email/Block/Adminhtml/Template/Grid/Renderer/Action.php b/app/code/Magento/Email/Block/Adminhtml/Template/Grid/Renderer/Action.php index 65f9e41b074a3..1c0fc533d0880 100644 --- a/app/code/Magento/Email/Block/Adminhtml/Template/Grid/Renderer/Action.php +++ b/app/code/Magento/Email/Block/Adminhtml/Template/Grid/Renderer/Action.php @@ -24,8 +24,8 @@ public function render(\Magento\Framework\DataObject $row) $actions[] = [ 'url' => $this->getUrl('adminhtml/*/preview', ['id' => $row->getId()]), - 'popup' => true, 'caption' => __('Preview'), + 'target' => '_blank' ]; $this->getColumn()->setActions($actions); diff --git a/app/code/Magento/Email/Test/Mftf/Test/AdminEmailTemplatePreviewFromGridNewTabTest.xml b/app/code/Magento/Email/Test/Mftf/Test/AdminEmailTemplatePreviewFromGridNewTabTest.xml new file mode 100644 index 0000000000000..906c578325670 --- /dev/null +++ b/app/code/Magento/Email/Test/Mftf/Test/AdminEmailTemplatePreviewFromGridNewTabTest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminEmailTemplatePreviewFromGridNewTabTest" extends="AdminEmailTemplatePreviewFromGridTest"> + <annotations> + <features value="Email"/> + <stories value="Preview email template from grid opens in a new tab"/> + <title value="Check email template preview from grid"/> + <description value="Check if email template preview action in the grid opens in a new tab"/> + <severity value="AVERAGE"/> + <testCaseId value="AC-6988"/> + <useCaseId value="ACP2E-1323"/> + <group value="email"/> + <group value="WYSIWYGDisabled"/> + </annotations> + + <!-- Capture the parent and the preview window sizes for comparison --> + <executeJS function="return window.outerWidth + ',' + window.outerHeight;" after="createTemplate" stepKey="parentWindowSize"/> + <executeJS function="return window.outerWidth + ',' + window.outerHeight;" after="previewTemplateFromGrid" stepKey="previewWindowSize"/> + + <!-- Assert the preview window has the same dimensions as its parent --> + <assertEquals after="previewWindowSize" stepKey="assertThatPreviewWindowSizeIsTheSameAsParent"> + <actualResult type="const">$previewWindowSize</actualResult> + <expectedResult type="const">$parentWindowSize</expectedResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Email/composer.json b/app/code/Magento/Email/composer.json index 4499b1060a011..27b33acfe00db 100644 --- a/app/code/Magento/Email/composer.json +++ b/app/code/Magento/Email/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-cms": "*", diff --git a/app/code/Magento/EncryptionKey/composer.json b/app/code/Magento/EncryptionKey/composer.json index c20cd852d2377..43a61d210ed74 100644 --- a/app/code/Magento/EncryptionKey/composer.json +++ b/app/code/Magento/EncryptionKey/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-config": "*" diff --git a/app/code/Magento/Fedex/composer.json b/app/code/Magento/Fedex/composer.json index 1734040c2c487..c3e879ac31177 100644 --- a/app/code/Magento/Fedex/composer.json +++ b/app/code/Magento/Fedex/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "lib-libxml": "*", "magento/framework": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/GiftMessage/composer.json b/app/code/Magento/GiftMessage/composer.json index f205f2f4621d2..be0cdbbe22911 100644 --- a/app/code/Magento/GiftMessage/composer.json +++ b/app/code/Magento/GiftMessage/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/GiftMessageGraphQl/composer.json b/app/code/Magento/GiftMessageGraphQl/composer.json index f9b980d26fa78..143b02439966f 100644 --- a/app/code/Magento/GiftMessageGraphQl/composer.json +++ b/app/code/Magento/GiftMessageGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-gift-message": "*" }, diff --git a/app/code/Magento/GoogleAdwords/composer.json b/app/code/Magento/GoogleAdwords/composer.json index 3637b38f9ab61..a9d5b9178bb85 100644 --- a/app/code/Magento/GoogleAdwords/composer.json +++ b/app/code/Magento/GoogleAdwords/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-sales": "*", "magento/module-store": "*" diff --git a/app/code/Magento/GoogleAnalytics/composer.json b/app/code/Magento/GoogleAnalytics/composer.json index bb94435c9e9fd..09d9cadf97658 100644 --- a/app/code/Magento/GoogleAnalytics/composer.json +++ b/app/code/Magento/GoogleAnalytics/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-cookie": "*", "magento/module-sales": "*", diff --git a/app/code/Magento/GoogleGtag/composer.json b/app/code/Magento/GoogleGtag/composer.json index 13abce5dbf570..ed6e245b4e955 100644 --- a/app/code/Magento/GoogleGtag/composer.json +++ b/app/code/Magento/GoogleGtag/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-cookie": "*", "magento/module-sales": "*", diff --git a/app/code/Magento/GoogleOptimizer/composer.json b/app/code/Magento/GoogleOptimizer/composer.json index 7afe12358fa53..0192f363b61c2 100644 --- a/app/code/Magento/GoogleOptimizer/composer.json +++ b/app/code/Magento/GoogleOptimizer/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/GraphQl/composer.json b/app/code/Magento/GraphQl/composer.json index 1a962eedc5d5a..b81c3a924d4e5 100644 --- a/app/code/Magento/GraphQl/composer.json +++ b/app/code/Magento/GraphQl/composer.json @@ -3,13 +3,13 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-eav": "*", "magento/framework": "*", "magento/module-webapi": "*", "magento/module-new-relic-reporting": "*", "magento/module-authorization": "*", - "webonyx/graphql-php": "~14.11.5" + "webonyx/graphql-php": "^14.11" }, "suggest": { "magento/module-graph-ql-cache": "*" diff --git a/app/code/Magento/GraphQlCache/composer.json b/app/code/Magento/GraphQlCache/composer.json index 5be26cbf5990d..082534290d139 100644 --- a/app/code/Magento/GraphQlCache/composer.json +++ b/app/code/Magento/GraphQlCache/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-page-cache": "*", "magento/module-graph-ql": "*", diff --git a/app/code/Magento/GroupedCatalogInventory/composer.json b/app/code/Magento/GroupedCatalogInventory/composer.json index 1a5e6054130eb..487fdfe0cc828 100644 --- a/app/code/Magento/GroupedCatalogInventory/composer.json +++ b/app/code/Magento/GroupedCatalogInventory/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-catalog-inventory": "*", diff --git a/app/code/Magento/GroupedImportExport/composer.json b/app/code/Magento/GroupedImportExport/composer.json index e411f55d00f4e..21805741bca44 100644 --- a/app/code/Magento/GroupedImportExport/composer.json +++ b/app/code/Magento/GroupedImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-catalog-import-export": "*", diff --git a/app/code/Magento/GroupedProduct/composer.json b/app/code/Magento/GroupedProduct/composer.json index 105e711c75b41..8277efc44f06b 100644 --- a/app/code/Magento/GroupedProduct/composer.json +++ b/app/code/Magento/GroupedProduct/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/GroupedProductGraphQl/composer.json b/app/code/Magento/GroupedProductGraphQl/composer.json index bb0f79e208dcb..41254569da53b 100644 --- a/app/code/Magento/GroupedProductGraphQl/composer.json +++ b/app/code/Magento/GroupedProductGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-grouped-product": "*", "magento/module-catalog": "*", "magento/module-catalog-graph-ql": "*", diff --git a/app/code/Magento/ImportExport/composer.json b/app/code/Magento/ImportExport/composer.json index b85162e9bec76..8ea56d1011582 100644 --- a/app/code/Magento/ImportExport/composer.json +++ b/app/code/Magento/ImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "ext-ctype": "*", "magento/framework": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php index 285b06e95331e..376dabe00ac5a 100644 --- a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php +++ b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php @@ -101,7 +101,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->write($indexer->getTitle() . ' index '); - $startTime = microtime(true); + $startTime = new \DateTimeImmutable(); $indexerConfig = $this->getConfig()->getIndexer($indexer->getId()); $sharedIndex = $indexerConfig['shared_index'] ?? null; @@ -112,10 +112,15 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->sharedIndexesComplete[] = $sharedIndex; } } - $resultTime = microtime(true) - $startTime; + $endTime = new \DateTimeImmutable(); + $interval = $startTime->diff($endTime); + $days = $interval->format('%d'); + $hours = $days > 0 ? $days * 24 + $interval->format('%H') : $interval->format('%H'); + $minutes = $interval->format('%I'); + $seconds = $interval->format('%S'); $output->writeln( - __('has been rebuilt successfully in %time', ['time' => gmdate('H:i:s', (int) $resultTime)]) + __('has been rebuilt successfully in %1:%2:%3', $hours, $minutes, $seconds) ); } catch (\Throwable $e) { $output->writeln('process error during indexation process:'); @@ -238,7 +243,9 @@ private function validateIndexerStatus(IndexerInterface $indexer) * Get config * * @return ConfigInterface - * @deprecated 100.1.0 + * @deprecated 100.1.0 We don't recommend this approach anymore + * @see Add a new optional parameter to the constructor at the end of the arguments list instead + * and fetch the dependency using Magento\Framework\App\ObjectManager::getInstance() in the constructor body */ private function getConfig() { @@ -252,7 +259,9 @@ private function getConfig() * Get dependency info provider * * @return DependencyInfoProvider - * @deprecated 100.2.0 + * @deprecated 100.2.0 We don't recommend this approach anymore + * @see Add a new optional parameter to the constructor at the end of the arguments list instead + * and fetch the dependency using Magento\Framework\App\ObjectManager::getInstance() in the constructor body */ private function getDependencyInfoProvider() { diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php index 244798e7261b8..4db91dcfb7cb6 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php @@ -8,13 +8,11 @@ namespace Magento\Indexer\Test\Unit\Console\Command; use Magento\Framework\Console\Cli; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Indexer\Config\DependencyInfoProvider; use Magento\Framework\Indexer\ConfigInterface; use Magento\Framework\Indexer\IndexerInterface; use Magento\Framework\Indexer\IndexerRegistry; use Magento\Framework\Indexer\StateInterface; -use Magento\Framework\Phrase; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Indexer\Console\Command\IndexerReindexCommand; use Magento\Indexer\Model\Config; @@ -27,7 +25,7 @@ */ class IndexerReindexCommandTest extends AbstractIndexerCommandCommonSetup { - const STUB_INDEXER_NAME = 'Indexer Name'; + private const STUB_INDEXER_NAME = 'Indexer Name'; /** * Command being tested * @@ -130,6 +128,11 @@ public function testExecuteAll() self::STUB_INDEXER_NAME . ' index has been rebuilt successfully in', $actualValue ); + $this->assertMatchesRegularExpression( + '/' . self::STUB_INDEXER_NAME + . ' index has been rebuilt successfully in (?:(?:([01]?\d|2[0-3]):)?([0-5]?\d):)?([0-5]?\d)/m', + $actualValue + ); } /** diff --git a/app/code/Magento/Indexer/composer.json b/app/code/Magento/Indexer/composer.json index bdcd05d5a71e3..8cee48610c7ea 100644 --- a/app/code/Magento/Indexer/composer.json +++ b/app/code/Magento/Indexer/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*" }, diff --git a/app/code/Magento/InstantPurchase/composer.json b/app/code/Magento/InstantPurchase/composer.json index c399f60df1dbb..d64f757adfd3b 100644 --- a/app/code/Magento/InstantPurchase/composer.json +++ b/app/code/Magento/InstantPurchase/composer.json @@ -7,7 +7,7 @@ "AFL-3.0" ], "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-store": "*", "magento/module-catalog": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/Integration/composer.json b/app/code/Magento/Integration/composer.json index d3c226066226f..a6eea5321de74 100644 --- a/app/code/Magento/Integration/composer.json +++ b/app/code/Magento/Integration/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Integration/etc/webapi.xml b/app/code/Magento/Integration/etc/webapi.xml index 8814fe5bb0059..6c6dc1a9def91 100644 --- a/app/code/Magento/Integration/etc/webapi.xml +++ b/app/code/Magento/Integration/etc/webapi.xml @@ -19,4 +19,13 @@ <resource ref="anonymous"/> </resources> </route> + <route url="/V1/integration/customer/revoke-customer-token" method="POST"> + <service class="Magento\Integration\Api\CustomerTokenServiceInterface" method="revokeCustomerAccessToken"/> + <resources> + <resource ref="self"/> + </resources> + <data> + <parameter name="customerId" force="true">%customer_id%</parameter> + </data> + </route> </routes> diff --git a/app/code/Magento/JwtFrameworkAdapter/composer.json b/app/code/Magento/JwtFrameworkAdapter/composer.json index a375ed0b197a8..811dc1948c121 100644 --- a/app/code/Magento/JwtFrameworkAdapter/composer.json +++ b/app/code/Magento/JwtFrameworkAdapter/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "web-token/jwt-framework": "^v2.2.7" }, diff --git a/app/code/Magento/JwtUserToken/composer.json b/app/code/Magento/JwtUserToken/composer.json index d632d6e4a49b0..ff1ae2bda5261 100644 --- a/app/code/Magento/JwtUserToken/composer.json +++ b/app/code/Magento/JwtUserToken/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-integration": "*", "magento/module-authorization": "*" diff --git a/app/code/Magento/LayeredNavigation/composer.json b/app/code/Magento/LayeredNavigation/composer.json index d6285b4260f5f..c40f906eac3a0 100644 --- a/app/code/Magento/LayeredNavigation/composer.json +++ b/app/code/Magento/LayeredNavigation/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-config": "*" diff --git a/app/code/Magento/LoginAsCustomer/composer.json b/app/code/Magento/LoginAsCustomer/composer.json index 61a4e1c0dda96..6b2cbf7c1f3f7 100755 --- a/app/code/Magento/LoginAsCustomer/composer.json +++ b/app/code/Magento/LoginAsCustomer/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer", "description": "Allow for admin to enter a customer account", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/LoginAsCustomerAdminUi/composer.json b/app/code/Magento/LoginAsCustomerAdminUi/composer.json index 6841ee3790cb3..2a42d814be498 100644 --- a/app/code/Magento/LoginAsCustomerAdminUi/composer.json +++ b/app/code/Magento/LoginAsCustomerAdminUi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-admin-ui", "description": "", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-login-as-customer-api": "*", "magento/module-login-as-customer-frontend-ui": "*", diff --git a/app/code/Magento/LoginAsCustomerApi/composer.json b/app/code/Magento/LoginAsCustomerApi/composer.json index e4a0952ac0369..fed3ab5390597 100644 --- a/app/code/Magento/LoginAsCustomerApi/composer.json +++ b/app/code/Magento/LoginAsCustomerApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-api", "description": "Allow for admin to enter a customer account", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/LoginAsCustomerAssistance/composer.json b/app/code/Magento/LoginAsCustomerAssistance/composer.json index 58e48bddc7c0f..32e351bee5115 100644 --- a/app/code/Magento/LoginAsCustomerAssistance/composer.json +++ b/app/code/Magento/LoginAsCustomerAssistance/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-assistance", "description": "", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/LoginAsCustomerFrontendUi/composer.json b/app/code/Magento/LoginAsCustomerFrontendUi/composer.json index 8a5437dc42d28..7c7767e23c27a 100644 --- a/app/code/Magento/LoginAsCustomerFrontendUi/composer.json +++ b/app/code/Magento/LoginAsCustomerFrontendUi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-frontend-ui", "description": "", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-login-as-customer-api": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/LoginAsCustomerGraphQl/composer.json b/app/code/Magento/LoginAsCustomerGraphQl/composer.json index 25a5ef8ff8b6c..ee97cd320115e 100755 --- a/app/code/Magento/LoginAsCustomerGraphQl/composer.json +++ b/app/code/Magento/LoginAsCustomerGraphQl/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-graph-ql", "description": "Flexible login as a customer so a merchant or merchant admin can log into an end customer's account to assist them with their account.", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-login-as-customer-api": "*", "magento/module-login-as-customer-assistance": "*", diff --git a/app/code/Magento/LoginAsCustomerLog/composer.json b/app/code/Magento/LoginAsCustomerLog/composer.json index 404511f7315f4..7e39d22d23ef6 100644 --- a/app/code/Magento/LoginAsCustomerLog/composer.json +++ b/app/code/Magento/LoginAsCustomerLog/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-log", "description": "", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/LoginAsCustomerPageCache/composer.json b/app/code/Magento/LoginAsCustomerPageCache/composer.json index 93f74f29ef246..39b8217c89969 100644 --- a/app/code/Magento/LoginAsCustomerPageCache/composer.json +++ b/app/code/Magento/LoginAsCustomerPageCache/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-page-cache", "description": "", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-store": "*", "magento/module-login-as-customer-api": "*" diff --git a/app/code/Magento/LoginAsCustomerQuote/composer.json b/app/code/Magento/LoginAsCustomerQuote/composer.json index f852948ab757f..0ce4d008d1fd8 100644 --- a/app/code/Magento/LoginAsCustomerQuote/composer.json +++ b/app/code/Magento/LoginAsCustomerQuote/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-quote", "description": "", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-checkout": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/LoginAsCustomerSales/composer.json b/app/code/Magento/LoginAsCustomerSales/composer.json index ba24858b6f548..74f74eb34432e 100644 --- a/app/code/Magento/LoginAsCustomerSales/composer.json +++ b/app/code/Magento/LoginAsCustomerSales/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-login-as-customer-sales", "description": "", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-user": "*", diff --git a/app/code/Magento/Marketplace/composer.json b/app/code/Magento/Marketplace/composer.json index f468808298344..1827499160587 100644 --- a/app/code/Magento/Marketplace/composer.json +++ b/app/code/Magento/Marketplace/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*" }, diff --git a/app/code/Magento/MediaContent/composer.json b/app/code/Magento/MediaContent/composer.json index 7eb51b02f61eb..4e7fd39b9d0ae 100644 --- a/app/code/Magento/MediaContent/composer.json +++ b/app/code/Magento/MediaContent/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content", "description": "Magento module provides the implementation for managing relations between content and media files used in that content", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-media-content-api": "*", "magento/module-media-gallery-api": "*" diff --git a/app/code/Magento/MediaContentApi/composer.json b/app/code/Magento/MediaContentApi/composer.json index 86dc6408cd6fd..f7583a1f61a08 100644 --- a/app/code/Magento/MediaContentApi/composer.json +++ b/app/code/Magento/MediaContentApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content-api", "description": "Magento module provides the API interfaces for managing relations between content and media files used in that content", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-media-gallery-api": "*", "magento/framework": "*" }, diff --git a/app/code/Magento/MediaContentCatalog/composer.json b/app/code/Magento/MediaContentCatalog/composer.json index 822fd1ec73814..948cc9f05d3cd 100644 --- a/app/code/Magento/MediaContentCatalog/composer.json +++ b/app/code/Magento/MediaContentCatalog/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content-catalog", "description": "Magento module provides the implementation of MediaContent functionality for Magento_Catalog module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-media-content-api": "*", "magento/module-catalog": "*", "magento/module-eav": "*", diff --git a/app/code/Magento/MediaContentCms/composer.json b/app/code/Magento/MediaContentCms/composer.json index 6cd121d00d2a2..a0a6098993900 100644 --- a/app/code/Magento/MediaContentCms/composer.json +++ b/app/code/Magento/MediaContentCms/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content-cms", "description": "Magento module provides the implementation of MediaContent functionality for Magento_Cms module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-media-content-api": "*", "magento/module-cms": "*", "magento/framework": "*" diff --git a/app/code/Magento/MediaContentSynchronization/composer.json b/app/code/Magento/MediaContentSynchronization/composer.json index a3062c163b246..4520f1302a03f 100644 --- a/app/code/Magento/MediaContentSynchronization/composer.json +++ b/app/code/Magento/MediaContentSynchronization/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content-synchronization", "description": "Magento module provides implementation of the media content data synchronization.", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/framework-bulk": "*", "magento/module-media-content-synchronization-api": "*", diff --git a/app/code/Magento/MediaContentSynchronizationApi/composer.json b/app/code/Magento/MediaContentSynchronizationApi/composer.json index 953d665b79a4d..1e44b8079e29b 100644 --- a/app/code/Magento/MediaContentSynchronizationApi/composer.json +++ b/app/code/Magento/MediaContentSynchronizationApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content-synchronization-api", "description": "Magento module responsible for the media content synchronization implementation API", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-media-content-api": "*" }, diff --git a/app/code/Magento/MediaContentSynchronizationCatalog/composer.json b/app/code/Magento/MediaContentSynchronizationCatalog/composer.json index 7a0375e30c370..f3a2bbb4baeb1 100644 --- a/app/code/Magento/MediaContentSynchronizationCatalog/composer.json +++ b/app/code/Magento/MediaContentSynchronizationCatalog/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content-synchronization-catalog", "description": "Magento module provides the implementation of MediaContentSynchronization functionality for Magento_Catalog module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-media-content-synchronization-api": "*", "magento/module-media-gallery-synchronization-api": "*", diff --git a/app/code/Magento/MediaContentSynchronizationCms/composer.json b/app/code/Magento/MediaContentSynchronizationCms/composer.json index 9e1236bcb863d..9925cc9ae5387 100644 --- a/app/code/Magento/MediaContentSynchronizationCms/composer.json +++ b/app/code/Magento/MediaContentSynchronizationCms/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-content-synchronization-cms", "description": "Magento module provides the implementation of MediaContentSynchronization functionality for Magento_Cms module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-media-content-synchronization-api": "*", "magento/module-media-gallery-synchronization-api": "*", diff --git a/app/code/Magento/MediaGallery/composer.json b/app/code/Magento/MediaGallery/composer.json index ccea65f248c26..0076013351e22 100644 --- a/app/code/Magento/MediaGallery/composer.json +++ b/app/code/Magento/MediaGallery/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery", "description": "Magento module responsible for media handling", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-media-gallery-api": "*", "magento/module-cms": "*" diff --git a/app/code/Magento/MediaGalleryApi/composer.json b/app/code/Magento/MediaGalleryApi/composer.json index d4299f8ef5e8d..48ef4dbf076f3 100644 --- a/app/code/Magento/MediaGalleryApi/composer.json +++ b/app/code/Magento/MediaGalleryApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-api", "description": "Magento module responsible for media gallery asset attributes storage and management", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/MediaGalleryCatalog/composer.json b/app/code/Magento/MediaGalleryCatalog/composer.json index ce438f66fda19..7feea28221df4 100644 --- a/app/code/Magento/MediaGalleryCatalog/composer.json +++ b/app/code/Magento/MediaGalleryCatalog/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-catalog", "description": "Magento module responsible for catalog gallery processor delete operation handling", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-media-gallery-api": "*", "magento/module-catalog": "*" diff --git a/app/code/Magento/MediaGalleryCatalogIntegration/composer.json b/app/code/Magento/MediaGalleryCatalogIntegration/composer.json index 477312fd0e4fb..267c37e88b44e 100644 --- a/app/code/Magento/MediaGalleryCatalogIntegration/composer.json +++ b/app/code/Magento/MediaGalleryCatalogIntegration/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-catalog-integration", "description": "Magento module responsible for extending catalog image uploader functionality", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-cms": "*", "magento/module-media-gallery-api": "*", diff --git a/app/code/Magento/MediaGalleryCatalogUi/composer.json b/app/code/Magento/MediaGalleryCatalogUi/composer.json index 296de50df5189..46f0de7c6a51b 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/composer.json +++ b/app/code/Magento/MediaGalleryCatalogUi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-catalog-ui", "description": "Magento module that implement category grid for media gallery.", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-cms": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/MediaGalleryCmsUi/composer.json b/app/code/Magento/MediaGalleryCmsUi/composer.json index 01e65b4212322..04e7f24199775 100644 --- a/app/code/Magento/MediaGalleryCmsUi/composer.json +++ b/app/code/Magento/MediaGalleryCmsUi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-cms-ui", "description": "Cms related UI elements in the magento media gallery", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-cms": "*", "magento/module-backend": "*" diff --git a/app/code/Magento/MediaGalleryIntegration/composer.json b/app/code/Magento/MediaGalleryIntegration/composer.json index a29b109174369..3c0fd77facb76 100644 --- a/app/code/Magento/MediaGalleryIntegration/composer.json +++ b/app/code/Magento/MediaGalleryIntegration/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-integration", "description": "Magento module responsible for integration of enhanced media gallery", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-media-gallery-ui-api": "*", "magento/module-media-gallery-api": "*", diff --git a/app/code/Magento/MediaGalleryMetadata/composer.json b/app/code/Magento/MediaGalleryMetadata/composer.json index 88a54ffadab49..aede5537f058b 100644 --- a/app/code/Magento/MediaGalleryMetadata/composer.json +++ b/app/code/Magento/MediaGalleryMetadata/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-metadata", "description": "Magento module responsible for images metadata processing", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-media-gallery-metadata-api": "*" }, diff --git a/app/code/Magento/MediaGalleryMetadataApi/composer.json b/app/code/Magento/MediaGalleryMetadataApi/composer.json index ea8ec2763678b..41de71aeb5265 100644 --- a/app/code/Magento/MediaGalleryMetadataApi/composer.json +++ b/app/code/Magento/MediaGalleryMetadataApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-metadata-api", "description": "Magento module responsible for media gallery metadata implementation API", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/MediaGalleryRenditions/composer.json b/app/code/Magento/MediaGalleryRenditions/composer.json index e18f3ae6e78c3..ca05a594554a6 100644 --- a/app/code/Magento/MediaGalleryRenditions/composer.json +++ b/app/code/Magento/MediaGalleryRenditions/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-renditions", "description": "Magento module that implements height and width fields for for media gallery items.", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-media-gallery-renditions-api": "*", "magento/module-media-gallery-api": "*", diff --git a/app/code/Magento/MediaGalleryRenditionsApi/composer.json b/app/code/Magento/MediaGalleryRenditionsApi/composer.json index 589247e91f269..e6f9cf747690f 100644 --- a/app/code/Magento/MediaGalleryRenditionsApi/composer.json +++ b/app/code/Magento/MediaGalleryRenditionsApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-renditions-api", "description": "Magento module that is responsible for the API implementation of Media Gallery Renditions.", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/MediaGallerySynchronization/composer.json b/app/code/Magento/MediaGallerySynchronization/composer.json index 0a7b05a9f4fca..ee7b9b5be5b89 100644 --- a/app/code/Magento/MediaGallerySynchronization/composer.json +++ b/app/code/Magento/MediaGallerySynchronization/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-synchronization", "description": "Magento module provides implementation of the media gallery data synchronization.", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-media-gallery-api": "*", "magento/module-media-gallery-synchronization-api": "*", diff --git a/app/code/Magento/MediaGallerySynchronizationApi/composer.json b/app/code/Magento/MediaGallerySynchronizationApi/composer.json index e7b388d7f407d..7b62a0d7c680f 100644 --- a/app/code/Magento/MediaGallerySynchronizationApi/composer.json +++ b/app/code/Magento/MediaGallerySynchronizationApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-synchronization-api", "description": "Magento module responsible for the media gallery synchronization implementation API", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-media-gallery-api": "*" }, diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/composer.json b/app/code/Magento/MediaGallerySynchronizationMetadata/composer.json index 38088910e6a78..ba4cec8bd6da9 100644 --- a/app/code/Magento/MediaGallerySynchronizationMetadata/composer.json +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-synchronization-metadata", "description": "Magento module responsible for images metadata synchronization", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-media-gallery-api": "*", "magento/module-media-gallery-metadata-api": "*", diff --git a/app/code/Magento/MediaGalleryUi/Model/Directories/GetDirectoryTree.php b/app/code/Magento/MediaGalleryUi/Model/Directories/GetDirectoryTree.php index 897d0d34a5c84..bff9d9867dd03 100644 --- a/app/code/Magento/MediaGalleryUi/Model/Directories/GetDirectoryTree.php +++ b/app/code/Magento/MediaGalleryUi/Model/Directories/GetDirectoryTree.php @@ -7,7 +7,9 @@ namespace Magento\MediaGalleryUi\Model\Directories; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\ValidatorException; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\Read; @@ -18,6 +20,8 @@ */ class GetDirectoryTree { + private const XML_PATH_MEDIA_GALLERY_IMAGE_FOLDERS + = 'system/media_storage_configuration/allowed_resources/media_gallery_image_folders'; /** * @var Filesystem */ @@ -28,16 +32,24 @@ class GetDirectoryTree */ private $isPathExcluded; + /** + * @var ScopeConfigInterface + */ + private $coreConfig; + /** * @param Filesystem $filesystem * @param IsPathExcludedInterface $isPathExcluded + * @param ScopeConfigInterface|null $coreConfig */ public function __construct( Filesystem $filesystem, - IsPathExcludedInterface $isPathExcluded + IsPathExcludedInterface $isPathExcluded, + ?ScopeConfigInterface $coreConfig = null ) { $this->filesystem = $filesystem; $this->isPathExcluded = $isPathExcluded; + $this->coreConfig = $coreConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class); } /** @@ -74,30 +86,54 @@ private function getDirectories(): array { $directories = []; - /** @var Read $directory */ - $directory = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA); - - if (!$directory->isDirectory()) { - return $directories; - } - - foreach ($directory->readRecursively() as $path) { - if (!$directory->isDirectory($path) || $this->isPathExcluded->execute($path)) { - continue; + /** @var Read $mediaDirectory */ + $mediaDirectory = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA); + + if ($mediaDirectory->isDirectory()) { + $imageFolderPaths = $this->coreConfig->getValue( + self::XML_PATH_MEDIA_GALLERY_IMAGE_FOLDERS, + ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ); + sort($imageFolderPaths); + + foreach ($imageFolderPaths as $imageFolderPath) { + $imageDirectory = $this->filesystem->getDirectoryReadByPath( + $mediaDirectory->getAbsolutePath($imageFolderPath) + ); + if ($imageDirectory->isDirectory()) { + $directories[] = $this->getDirectoryData($imageFolderPath); + foreach ($imageDirectory->readRecursively() as $path) { + if ($imageDirectory->isDirectory($path)) { + $directories[] = $this->getDirectoryData( + $mediaDirectory->getRelativePath($imageDirectory->getAbsolutePath($path)) + ); + } + } + } } - - $pathArray = explode('/', $path); - $directories[] = [ - 'text' => count($pathArray) > 0 ? end($pathArray) : $path, - 'id' => $path, - 'li_attr' => ['data-id' => $path], - 'path' => $path, - 'path_array' => $pathArray - ]; } + return $directories; } + /** + * Return jstree data for given path + * + * @param string $path + * @return array + */ + private function getDirectoryData(string $path): array + { + $pathArray = explode('/', $path); + return [ + 'text' => count($pathArray) > 0 ? end($pathArray) : $path, + 'id' => $path, + 'li_attr' => ['data-id' => $path], + 'path' => $path, + 'path_array' => $pathArray + ]; + } + /** * Find parent directory * @@ -121,9 +157,9 @@ private function findParent(array &$node, array &$treeNode, int $level = 0): arr $tNodePathLength = count($tnode['path_array']); $found = false; while ($level < $tNodePathLength) { - if ($node['path_array'][$level] === $tnode['path_array'][$level]) { + $found = $node['path_array'][$level] === $tnode['path_array'][$level]; + if ($found) { $level ++; - $found = true; } else { break; } diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminMediaGalleryFolderSection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminMediaGalleryFolderSection.xml index 569487c47da1c..727d3293851fa 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminMediaGalleryFolderSection.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminMediaGalleryFolderSection.xml @@ -9,6 +9,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminMediaGalleryFolderSection"> + <element name="folderNewModalHeader" type="block" selector="//h1[contains(text(), 'New Folder Name')]"/> <element name="folderDeleteModalHeader" type="block" selector="//h1[contains(text(), 'Are you sure you want to delete this folder?')]"/> <element name="folderNewCreateButton" type="button" selector="#create_folder"/> @@ -28,5 +29,7 @@ <element name="activeDeleteFolderButton" type="button" selector="//h1[@class='modal-title' and contains(text(),'Insert File')]/../..//button[@id='delete_folder']"/> <element name="folderDeleteMessageTitle" type="block" selector="//h1[@class='modal-title' and contains(text(),'Are you sure you want to delete this folder?')]"/> <element name="folderDeleteMessageContent" type="block" selector="//div[@class='modal-content']//div[contains(text(),'The following folder is going to be deleted: wysiwyg/{{arg}}')]" parameterized="true"/> + </section> </sections> + diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/UserDeletesFolderFromMediaGalleryTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/UserDeletesFolderFromMediaGalleryTest.xml index f1db298764ee8..91478877cfe50 100755 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/UserDeletesFolderFromMediaGalleryTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/UserDeletesFolderFromMediaGalleryTest.xml @@ -89,3 +89,4 @@ <waitForPageLoad stepKey="waitForMediaGalleryPageLoadPostFinalFolderDelete"/> </test> </tests> + diff --git a/app/code/Magento/MediaGalleryUi/Test/Unit/Model/Model/Directories/GetDirectoryTreeTest.php b/app/code/Magento/MediaGalleryUi/Test/Unit/Model/Model/Directories/GetDirectoryTreeTest.php new file mode 100644 index 0000000000000..df7647a66da58 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Unit/Model/Model/Directories/GetDirectoryTreeTest.php @@ -0,0 +1,304 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryUi\Test\Unit\Model\Model\Directories; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Exception\ValidatorException; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\MediaGalleryApi\Api\IsPathExcludedInterface; +use Magento\MediaGalleryUi\Model\Directories\GetDirectoryTree; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class GetDirectoryTreeTest extends TestCase +{ + /** + * @var Filesystem|MockObject + */ + private $filesystem; + + /** + * @var IsPathExcludedInterface|MockObject + */ + private $isPathExcluded; + + /** + * @var ScopeConfigInterface|MockObject + */ + private $coreConfig; + + /** + * @var GetDirectoryTree + */ + private $model; + + /** + * @var array + */ + private $foldersStruture = [ + 'dir1' => [ + 'dir1_1' => [ + + ], + 'dir1_2' => [ + + ], + 'dir1_3' => [ + + ] + ], + 'dir2' => [ + 'dir2_1' => [ + 'dir2_1_1' => [ + + ] + ], + 'dir2_2' => [ + 'dir2_2_1' => [ + + ], + 'dir2_2_2' => [ + + ] + ] + ], + 'dir3' => [ + 'dir3_1' => [ + 'dir3_1_1' => [ + 'dir3_1_1_1' => [ + + ] + ] + ] + ], + 'dir4' => [ + + ], + ]; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + parent::setUp(); + $this->filesystem = $this->createMock(Filesystem::class); + $this->isPathExcluded = $this->getMockForAbstractClass(IsPathExcludedInterface::class); + $this->coreConfig = $this->getMockForAbstractClass(ScopeConfigInterface::class); + $this->model = new GetDirectoryTree( + $this->filesystem, + $this->isPathExcluded, + $this->coreConfig + ); + } + + /** + * @param array $allowedFolders + * @param array $expected + * @throws ValidatorException + * @dataProvider executeDataProvider + */ + public function testExecute(array $allowedFolders, array $expected): void + { + $directory = $this->getMockForAbstractClass(ReadInterface::class); + $directory->method('isDirectory')->willReturn(true); + $directory->method('getAbsolutePath')->willReturnArgument(0); + $directory->method('getRelativePath')->willReturnArgument(0); + $this->filesystem->method('getDirectoryRead')->willReturn($directory); + $this->filesystem->method('getDirectoryReadByPath') + ->willReturnCallback( + function (string $path) { + $directory = $this->getMockBuilder(ReadInterface::class) + ->addMethods(['readRecursively']) + ->getMockForAbstractClass(); + $directory->method('isDirectory')->willReturn(true); + $result = $this->foldersStruture; + $prefix = ''; + foreach (explode('/', $path) as $folder) { + $prefix .= $folder . '/'; + $result = $result[$folder] ?? []; + } + $directory->method('getAbsolutePath')->willReturnArgument(0); + $directory->method('readRecursively')->willReturn($this->flattenFoldersStructure($result, $prefix)); + return $directory; + } + ); + $this->coreConfig->method('getValue')->willReturn($allowedFolders); + $this->assertEquals($expected, $this->model->execute()); + } + + /** + * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function executeDataProvider(): array + { + return [ + [ + ['dir1/dir1_1', 'dir2/dir2_2', 'dir3'], + [ + [ + 'text' => 'dir1_1', + 'id' => 'dir1/dir1_1', + 'li_attr' => ['data-id' => 'dir1/dir1_1'], + 'path' => 'dir1/dir1_1', + 'path_array' => ['dir1', 'dir1_1'], + 'children' => [], + ], + [ + 'text' => 'dir2_2', + 'id' => 'dir2/dir2_2', + 'li_attr' => ['data-id' => 'dir2/dir2_2'], + 'path' => 'dir2/dir2_2', + 'path_array' => ['dir2', 'dir2_2'], + 'children' => + [ + [ + 'text' => 'dir2_2_1', + 'id' => 'dir2/dir2_2/dir2_2_1', + 'li_attr' => + [ + 'data-id' => 'dir2/dir2_2/dir2_2_1', + ], + 'path' => 'dir2/dir2_2/dir2_2_1', + 'path_array' => ['dir2', 'dir2_2', 'dir2_2_1'], + 'children' => [], + ], + [ + 'text' => 'dir2_2_2', + 'id' => 'dir2/dir2_2/dir2_2_2', + 'li_attr' => ['data-id' => 'dir2/dir2_2/dir2_2_2'], + 'path' => 'dir2/dir2_2/dir2_2_2', + 'path_array' => ['dir2', 'dir2_2', 'dir2_2_2'], + 'children' => [], + ], + ], + ], + [ + 'text' => 'dir3', + 'id' => 'dir3', + 'li_attr' => ['data-id' => 'dir3'], + 'path' => 'dir3', + 'path_array' => ['dir3'], + 'children' => + [ + [ + 'text' => 'dir3_1', + 'id' => 'dir3/dir3_1', + 'li_attr' => ['data-id' => 'dir3/dir3_1'], + 'path' => 'dir3/dir3_1', + 'path_array' => ['dir3', 'dir3_1'], + 'children' => + [ + [ + 'text' => 'dir3_1_1', + 'id' => 'dir3/dir3_1/dir3_1_1', + 'li_attr' => ['data-id' => 'dir3/dir3_1/dir3_1_1'], + 'path' => 'dir3/dir3_1/dir3_1_1', + 'path_array' => ['dir3', 'dir3_1', 'dir3_1_1'], + 'children' => + [ + [ + 'text' => 'dir3_1_1_1', + 'id' => 'dir3/dir3_1/dir3_1_1/dir3_1_1_1', + 'li_attr' => [ + 'data-id' => 'dir3/dir3_1/dir3_1_1/dir3_1_1_1', + ], + 'path' => 'dir3/dir3_1/dir3_1_1/dir3_1_1_1', + 'path_array' => [ + 'dir3', + 'dir3_1', + 'dir3_1_1', + 'dir3_1_1_1', + ], + 'children' => [], + ], + ], + ], + ], + ] + ], + ], + ] + + ], + [ + ['dir2/dir2_1', 'dir2/dir2_2'], + [ + [ + 'text' => 'dir2_1', + 'id' => 'dir2/dir2_1', + 'li_attr' => ['data-id' => 'dir2/dir2_1'], + 'path' => 'dir2/dir2_1', + 'path_array' => ['dir2', 'dir2_1'], + 'children' => + [ + [ + 'text' => 'dir2_1_1', + 'id' => 'dir2/dir2_1/dir2_1_1', + 'li_attr' => + [ + 'data-id' => 'dir2/dir2_1/dir2_1_1', + ], + 'path' => 'dir2/dir2_1/dir2_1_1', + 'path_array' => ['dir2', 'dir2_1', 'dir2_1_1'], + 'children' => [], + ] + ], + ], + [ + 'text' => 'dir2_2', + 'id' => 'dir2/dir2_2', + 'li_attr' => ['data-id' => 'dir2/dir2_2'], + 'path' => 'dir2/dir2_2', + 'path_array' => ['dir2', 'dir2_2'], + 'children' => + [ + [ + 'text' => 'dir2_2_1', + 'id' => 'dir2/dir2_2/dir2_2_1', + 'li_attr' => + [ + 'data-id' => 'dir2/dir2_2/dir2_2_1', + ], + 'path' => 'dir2/dir2_2/dir2_2_1', + 'path_array' => ['dir2', 'dir2_2', 'dir2_2_1'], + 'children' => [], + ], + [ + 'text' => 'dir2_2_2', + 'id' => 'dir2/dir2_2/dir2_2_2', + 'li_attr' => ['data-id' => 'dir2/dir2_2/dir2_2_2'], + 'path' => 'dir2/dir2_2/dir2_2_2', + 'path_array' => ['dir2', 'dir2_2', 'dir2_2_2'], + 'children' => [], + ], + ], + ] + ] + ] + ]; + } + + /** + * @param array $array + * @param string $prefix + * @return array + */ + private function flattenFoldersStructure(array $array, string $prefix = ''): array + { + $paths = []; + foreach ($array as $key => $value) { + $path = $prefix . $key; + $paths[] = [$path]; + $paths[] = $this->flattenFoldersStructure($value, $path . '/'); + } + return array_merge(...$paths); + } +} diff --git a/app/code/Magento/MediaGalleryUi/composer.json b/app/code/Magento/MediaGalleryUi/composer.json index c95c16cfc8ad2..d5caac3ff409e 100644 --- a/app/code/Magento/MediaGalleryUi/composer.json +++ b/app/code/Magento/MediaGalleryUi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-ui", "description": "Magento module responsible for the media gallery UI implementation", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-ui": "*", diff --git a/app/code/Magento/MediaGalleryUiApi/composer.json b/app/code/Magento/MediaGalleryUiApi/composer.json index b1078e8e3a4f7..9c6aa225fa058 100644 --- a/app/code/Magento/MediaGalleryUiApi/composer.json +++ b/app/code/Magento/MediaGalleryUiApi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-gallery-ui-api", "description": "Magento module responsible for the media gallery UI implementation API", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "suggest": { diff --git a/app/code/Magento/MediaStorage/composer.json b/app/code/Magento/MediaStorage/composer.json index 1654e1645e7ba..f58c5d9b808c3 100644 --- a/app/code/Magento/MediaStorage/composer.json +++ b/app/code/Magento/MediaStorage/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/framework-bulk": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/MessageQueue/composer.json b/app/code/Magento/MessageQueue/composer.json index 2038e14ad32ed..7a297574ec8b2 100644 --- a/app/code/Magento/MessageQueue/composer.json +++ b/app/code/Magento/MessageQueue/composer.json @@ -8,7 +8,7 @@ "magento/framework": "*", "magento/framework-message-queue": "*", "magento/magento-composer-installer": "*", - "php": "~7.4.0||~8.1.0" + "php": "~8.1.0||~8.2.0" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/Msrp/composer.json b/app/code/Magento/Msrp/composer.json index 926b35621be3d..1614f33d6c20c 100644 --- a/app/code/Magento/Msrp/composer.json +++ b/app/code/Magento/Msrp/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-downloadable": "*", diff --git a/app/code/Magento/MsrpConfigurableProduct/composer.json b/app/code/Magento/MsrpConfigurableProduct/composer.json index 067a89c0be42a..c58e77c047b2d 100644 --- a/app/code/Magento/MsrpConfigurableProduct/composer.json +++ b/app/code/Magento/MsrpConfigurableProduct/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-msrp": "*", diff --git a/app/code/Magento/MsrpGroupedProduct/composer.json b/app/code/Magento/MsrpGroupedProduct/composer.json index 0ea4a60098282..1dea4b9949058 100644 --- a/app/code/Magento/MsrpGroupedProduct/composer.json +++ b/app/code/Magento/MsrpGroupedProduct/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-msrp": "*", diff --git a/app/code/Magento/Multishipping/composer.json b/app/code/Magento/Multishipping/composer.json index e796d7fd01b11..3ea9380da0809 100644 --- a/app/code/Magento/Multishipping/composer.json +++ b/app/code/Magento/Multishipping/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-checkout": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/MysqlMq/composer.json b/app/code/Magento/MysqlMq/composer.json index 8b62c6daf183c..b164a3b63aad4 100644 --- a/app/code/Magento/MysqlMq/composer.json +++ b/app/code/Magento/MysqlMq/composer.json @@ -9,7 +9,7 @@ "magento/framework-message-queue": "*", "magento/magento-composer-installer": "*", "magento/module-store": "*", - "php": "~7.4.0||~8.1.0" + "php": "~8.1.0||~8.2.0" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/NewRelicReporting/composer.json b/app/code/Magento/NewRelicReporting/composer.json index b566a7117dc48..e98f914082fab 100644 --- a/app/code/Magento/NewRelicReporting/composer.json +++ b/app/code/Magento/NewRelicReporting/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/magento-composer-installer": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Newsletter/composer.json b/app/code/Magento/Newsletter/composer.json index 9c3e3627e4cea..c477f8ecb64e3 100644 --- a/app/code/Magento/Newsletter/composer.json +++ b/app/code/Magento/Newsletter/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-cms": "*", diff --git a/app/code/Magento/NewsletterGraphQl/composer.json b/app/code/Magento/NewsletterGraphQl/composer.json index 03fa7650257fb..3fe7f7aaf289a 100644 --- a/app/code/Magento/NewsletterGraphQl/composer.json +++ b/app/code/Magento/NewsletterGraphQl/composer.json @@ -6,7 +6,7 @@ }, "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-customer": "*", "magento/module-newsletter": "*", diff --git a/app/code/Magento/OfflinePayments/composer.json b/app/code/Magento/OfflinePayments/composer.json index cdd383aee71e5..09de8b66996ad 100644 --- a/app/code/Magento/OfflinePayments/composer.json +++ b/app/code/Magento/OfflinePayments/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-checkout": "*", "magento/module-payment": "*", diff --git a/app/code/Magento/OfflinePayments/etc/config.xml b/app/code/Magento/OfflinePayments/etc/config.xml index 94a0a45f00ef7..64606a07ce8ba 100644 --- a/app/code/Magento/OfflinePayments/etc/config.xml +++ b/app/code/Magento/OfflinePayments/etc/config.xml @@ -40,10 +40,6 @@ <allowspecific>0</allowspecific> <group>offline</group> </cashondelivery> - <free> - <group>offline</group> - <payment_action>authorize_capture</payment_action> - </free> </payment> </default> </config> diff --git a/app/code/Magento/OfflineShipping/composer.json b/app/code/Magento/OfflineShipping/composer.json index e58f678e47770..9e75d64075f84 100644 --- a/app/code/Magento/OfflineShipping/composer.json +++ b/app/code/Magento/OfflineShipping/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/OpenSearch/Model/OpenSearch.php b/app/code/Magento/OpenSearch/Model/OpenSearch.php new file mode 100644 index 0000000000000..b658e0012a26d --- /dev/null +++ b/app/code/Magento/OpenSearch/Model/OpenSearch.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\OpenSearch\Model; + +/** + * The purpose of this class is adding the support for opensearch version 2 + */ + +class OpenSearch extends SearchClient +{ + + /** + * Add mapping to OpenSearch index + * + * @param array $fields + * @param string $index + * @param string $entityType + * @return void + */ + public function addFieldsMapping(array $fields, string $index, string $entityType) + { + $params = [ + 'index' => $index, + 'body' => [ + 'properties' => [], + 'dynamic_templates' => $this->dynamicTemplatesProvider->getTemplates(), + ], + ]; + + foreach ($this->applyFieldsMappingPreprocessors($fields) as $field => $fieldInfo) { + $params['body']['properties'][$field] = $fieldInfo; + } + + $this->getOpenSearchClient()->indices()->putMapping($params); + } + + /** + * Execute search by $query + * + * @param array $query + * @return array + */ + public function query(array $query): array + { + unset($query['type']); + return $this->getOpenSearchClient()->search($query); + } +} diff --git a/app/code/Magento/OpenSearch/Model/SearchClient.php b/app/code/Magento/OpenSearch/Model/SearchClient.php index feb6f952a20f4..dbce79c2496bb 100644 --- a/app/code/Magento/OpenSearch/Model/SearchClient.php +++ b/app/code/Magento/OpenSearch/Model/SearchClient.php @@ -42,7 +42,7 @@ class SearchClient implements ClientInterface /** * @var DynamicTemplatesProvider|null */ - private $dynamicTemplatesProvider; + public $dynamicTemplatesProvider; /** * Initialize Client @@ -93,7 +93,7 @@ public function suggest(array $query): array * * @return Client */ - private function getOpenSearchClient(): Client + public function getOpenSearchClient(): Client { $pid = getmypid(); if (!isset($this->client[$pid])) { @@ -371,7 +371,7 @@ public function deleteMapping(string $index, string $entityType) * @param array $properties * @return array */ - private function applyFieldsMappingPreprocessors(array $properties): array + public function applyFieldsMappingPreprocessors(array $properties): array { foreach ($this->fieldsMappingPreprocessors as $preprocessor) { $properties = $preprocessor->process($properties); diff --git a/app/code/Magento/OpenSearch/Test/Mftf/Test/OpenSearchUpgradeVersion2xTest.xml b/app/code/Magento/OpenSearch/Test/Mftf/Test/OpenSearchUpgradeVersion2xTest.xml new file mode 100644 index 0000000000000..7f694a1168f6c --- /dev/null +++ b/app/code/Magento/OpenSearch/Test/Mftf/Test/OpenSearchUpgradeVersion2xTest.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="OpenSearchUpgradeVersion2xTest"> + <annotations> + <stories value="Update OpenSearch to the v2.x"/> + <title value="Upgrading the version of opensearch to 2x"/> + <description value="Upgrading the version of opensearch to 2x"/> + <severity value="CRITICAL"/> + <testCaseId value="AC-6631"/> + <group value="catalog_search"/> + </annotations> + <before> + <magentoCLI command="config:set catalog/search/engine opensearch" stepKey="setSearchEngine"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> + + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createProduct01"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createProduct02"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <createData entity="AssignProductToCategory" stepKey="assignTestCategoryToTestProduct"> + <requiredEntity createDataKey="createCategory"/> + <requiredEntity createDataKey="createProduct01"/> + <requiredEntity createDataKey="createProduct02"/> + </createData> + <magentoCLI stepKey="reindex" command="indexer:reindex"/> + </before> + <after> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct01" stepKey="deleteProduct1"/> + <deleteData createDataKey="createProduct02" stepKey="deleteProduct2"/> + </after> + + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.custom_attributes[url_key]$$)}}" stepKey="goToStorefrontCreatedCategoryPage"/> + <wait time="2" stepKey="waitForLoad"/> + </test> +</tests> diff --git a/app/code/Magento/OpenSearch/Test/Unit/Model/OpenSearchTest.php b/app/code/Magento/OpenSearch/Test/Unit/Model/OpenSearchTest.php new file mode 100644 index 0000000000000..5fa55dcab9ca5 --- /dev/null +++ b/app/code/Magento/OpenSearch/Test/Unit/Model/OpenSearchTest.php @@ -0,0 +1,201 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\OpenSearch\Test\Unit\Model; + +use OpenSearch\Client; +use OpenSearch\Namespaces\IndicesNamespace; + +use Magento\Elasticsearch\Model\Adapter\FieldMapper\AddDefaultSearchField; +use Magento\OpenSearch\Model\Adapter\DynamicTemplates\IntegerMapper; +use Magento\OpenSearch\Model\Adapter\DynamicTemplates\PositionMapper; +use Magento\OpenSearch\Model\Adapter\DynamicTemplates\PriceMapper; +use Magento\OpenSearch\Model\Adapter\DynamicTemplates\StringMapper; +use Magento\OpenSearch\Model\Adapter\DynamicTemplatesProvider; +use Magento\OpenSearch\Model\OpenSearch; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Class OpensearchTest to test OpensearchTest2x + */ +class OpenSearchTest extends TestCase +{ + /** + * @var OpenSearch + */ + private $model; + + /** + * @var Client|MockObject + */ + private $opensearchV2ClientMock; + + /** + * @var IndicesNamespace|MockObject + */ + private $indicesMock; + + /** + * @var ObjectManagerHelper + */ + private $objectManager; + + /** + * Setup + * + * @return void + */ + protected function setUp(): void + { + $this->opensearchV2ClientMock = $this->getMockBuilder(Client::class) + ->setMethods( + [ + 'indices', + 'search' + ] + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->indicesMock = $this->getMockBuilder(IndicesNamespace::class) + ->setMethods( + [ + 'putMapping' + ] + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->opensearchV2ClientMock->expects($this->any()) + ->method('indices') + ->willReturn($this->indicesMock); + + $this->objectManager = new ObjectManagerHelper($this); + $dynamicTemplatesProvider = new DynamicTemplatesProvider( + [ new PriceMapper(), new PositionMapper(), new StringMapper(), new IntegerMapper()] + ); + $this->model = $this->objectManager->getObject( + OpenSearch::class, + [ + 'options' => $this->getOptions(), + 'openSearchClient' => $this->opensearchV2ClientMock, + 'fieldsMappingPreprocessors' => [new AddDefaultSearchField()], + 'dynamicTemplatesProvider' => $dynamicTemplatesProvider, + ] + ); + } + + /** + * Test query() method + * + * @return void + */ + public function testQuery() + { + $query = ['test phrase query']; + $this->opensearchV2ClientMock->expects($this->once()) + ->method('search') + ->with($query) + ->willReturn([]); + $this->assertEquals([], $this->model->query($query)); + } + /** + * Get client options + * + * @return array + */ + protected function getOptions() + { + return [ + 'hostname' => 'localhost', + 'port' => '9200', + 'timeout' => 15, + 'index' => 'magento2', + 'enableAuth' => 1, + 'username' => 'user', + 'password' => 'passwd', + ]; + } + + /** + * Test testAddFieldsMapping() method + */ + public function testAddFieldsMapping() + { + $this->indicesMock->expects($this->once()) + ->method('putMapping') + ->with( + [ + 'index' => 'indexName', + 'body' => [ + 'properties' => [ + '_search' => [ + 'type' => 'text', + ], + 'name' => [ + 'type' => 'text', + ], + ], + 'dynamic_templates' => [ + [ + 'price_mapping' => [ + 'match' => 'price_*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'double', + 'store' => true, + ], + ], + ], + [ + 'position_mapping' => [ + 'match' => 'position_*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'integer', + 'index' => true, + ], + ], + ], + [ + 'string_mapping' => [ + 'match' => '*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'text', + 'index' => true, + 'copy_to' => '_search', + ], + ], + ], + [ + 'integer_mapping' => [ + 'match_mapping_type' => 'long', + 'mapping' => [ + 'type' => 'integer', + ], + ], + ], + ], + ], + ] + ); + $this->model->addFieldsMapping( + [ + 'name' => [ + 'type' => 'text', + ], + ], + 'indexName', + 'product' + ); + } +} diff --git a/app/code/Magento/OpenSearch/composer.json b/app/code/Magento/OpenSearch/composer.json index 7415ee9ef7066..1b9e006b9e9b1 100644 --- a/app/code/Magento/OpenSearch/composer.json +++ b/app/code/Magento/OpenSearch/composer.json @@ -2,14 +2,14 @@ "name": "magento/module-open-search", "description": "N/A", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-advanced-search": "*", "magento/module-catalog-search": "*", "magento/module-elasticsearch": "*", "magento/module-search": "*", "magento/module-config": "*", - "opensearch-project/opensearch-php": "~1.0.0" + "opensearch-project/opensearch-php": "^1.0 || ^2.0" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/OpenSearch/etc/di.xml b/app/code/Magento/OpenSearch/etc/di.xml index 65d4ddb7dc546..ad1072165ad48 100644 --- a/app/code/Magento/OpenSearch/etc/di.xml +++ b/app/code/Magento/OpenSearch/etc/di.xml @@ -75,6 +75,7 @@ <virtualType name="Magento\OpenSearch\Model\Client\OpenSearchFactory" type="Magento\AdvancedSearch\Model\Client\ClientFactory"> <arguments> <argument name="clientClass" xsi:type="string">Magento\OpenSearch\Model\SearchClient</argument> + <argument name="openSearch" xsi:type="string">Magento\OpenSearch\Model\OpenSearch</argument> </arguments> </virtualType> <type name="Magento\Elasticsearch\Elasticsearch5\Model\Client\ClientFactoryProxy"> diff --git a/app/code/Magento/PageCache/composer.json b/app/code/Magento/PageCache/composer.json index eef0e5edd3824..494b5918004d8 100644 --- a/app/code/Magento/PageCache/composer.json +++ b/app/code/Magento/PageCache/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-config": "*", diff --git a/app/code/Magento/Payment/composer.json b/app/code/Magento/Payment/composer.json index 2f09f9b0c237f..36cd77ea50d47 100644 --- a/app/code/Magento/Payment/composer.json +++ b/app/code/Magento/Payment/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-checkout": "*", "magento/module-config": "*", diff --git a/app/code/Magento/Payment/etc/adminhtml/system.xml b/app/code/Magento/Payment/etc/adminhtml/system.xml index 1e8b617d31326..47b5ea4626a0e 100644 --- a/app/code/Magento/Payment/etc/adminhtml/system.xml +++ b/app/code/Magento/Payment/etc/adminhtml/system.xml @@ -24,9 +24,7 @@ <field id="payment_action" translate="label" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Automatically Invoice All Items</label> <source_model>Magento\Payment\Model\Source\Invoice</source_model> - <depends> - <field id="order_status" separator=",">processing</field> - </depends> + <comment>If 'Automatically Invoice All Items' is set to 'Yes', the order is placed in 'Processing' state.</comment> </field> <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" canRestore="1"> <label>Sort Order</label> diff --git a/app/code/Magento/Payment/etc/config.xml b/app/code/Magento/Payment/etc/config.xml index 663734fb066c7..4846e5d7d5c3a 100644 --- a/app/code/Magento/Payment/etc/config.xml +++ b/app/code/Magento/Payment/etc/config.xml @@ -12,10 +12,11 @@ <active>1</active> <model>Magento\Payment\Model\Method\Free</model> <order_status>pending</order_status> + <payment_action>authorize_capture</payment_action> <title>No Payment Information Required - authorize 0 1 + offline 0 diff --git a/app/code/Magento/PaymentGraphQl/composer.json b/app/code/Magento/PaymentGraphQl/composer.json index 8332d7dee0a4a..e6ab6fc747768 100644 --- a/app/code/Magento/PaymentGraphQl/composer.json +++ b/app/code/Magento/PaymentGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-payment": "*", "magento/module-graph-ql": "*" diff --git a/app/code/Magento/Paypal/composer.json b/app/code/Magento/Paypal/composer.json index b157a63fefeb2..23ebf05f2f2bc 100644 --- a/app/code/Magento/Paypal/composer.json +++ b/app/code/Magento/Paypal/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "lib-libxml": "*", "magento/framework": "*", "magento/module-authorization": "*", diff --git a/app/code/Magento/PaypalCaptcha/composer.json b/app/code/Magento/PaypalCaptcha/composer.json index 3f1f5bad59c3b..8c9feff31e823 100644 --- a/app/code/Magento/PaypalCaptcha/composer.json +++ b/app/code/Magento/PaypalCaptcha/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-captcha": "*", "magento/module-checkout": "*", diff --git a/app/code/Magento/PaypalGraphQl/composer.json b/app/code/Magento/PaypalGraphQl/composer.json index ea8a43c64257d..ce916276dac97 100644 --- a/app/code/Magento/PaypalGraphQl/composer.json +++ b/app/code/Magento/PaypalGraphQl/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-quote": "*", "magento/module-checkout": "*", diff --git a/app/code/Magento/Persistent/composer.json b/app/code/Magento/Persistent/composer.json index 3e4b24c38b92b..5a8ff5d7f3d5f 100644 --- a/app/code/Magento/Persistent/composer.json +++ b/app/code/Magento/Persistent/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-checkout": "*", "magento/module-cron": "*", diff --git a/app/code/Magento/ProductAlert/composer.json b/app/code/Magento/ProductAlert/composer.json index 8533a0e37443e..aee755e6a00b0 100644 --- a/app/code/Magento/ProductAlert/composer.json +++ b/app/code/Magento/ProductAlert/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/framework-bulk": "*", "magento/module-asynchronous-operations": "*", diff --git a/app/code/Magento/ProductVideo/composer.json b/app/code/Magento/ProductVideo/composer.json index b6c7a51914295..55b8cb5efa14b 100644 --- a/app/code/Magento/ProductVideo/composer.json +++ b/app/code/Magento/ProductVideo/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/magento-composer-installer": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Quote/Model/Cart/AddProductsToCart.php b/app/code/Magento/Quote/Model/Cart/AddProductsToCart.php index 2086646d855f8..9be1e9d32e379 100644 --- a/app/code/Magento/Quote/Model/Cart/AddProductsToCart.php +++ b/app/code/Magento/Quote/Model/Cart/AddProductsToCart.php @@ -7,8 +7,6 @@ namespace Magento\Quote\Model\Cart; -use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Model\Cart\BuyRequest\BuyRequestBuilder; @@ -23,29 +21,6 @@ */ class AddProductsToCart { - /**#@+ - * Error message codes - */ - private const ERROR_PRODUCT_NOT_FOUND = 'PRODUCT_NOT_FOUND'; - private const ERROR_INSUFFICIENT_STOCK = 'INSUFFICIENT_STOCK'; - private const ERROR_NOT_SALABLE = 'NOT_SALABLE'; - private const ERROR_UNDEFINED = 'UNDEFINED'; - /**#@-*/ - - /** - * List of error messages and codes. - */ - private const MESSAGE_CODES = [ - 'Could not find a product with SKU' => self::ERROR_PRODUCT_NOT_FOUND, - 'The required options you selected are not available' => self::ERROR_NOT_SALABLE, - 'Product that you are trying to add is not available.' => self::ERROR_NOT_SALABLE, - 'This product is out of stock' => self::ERROR_INSUFFICIENT_STOCK, - 'There are no source items' => self::ERROR_NOT_SALABLE, - 'The fewest you may purchase is' => self::ERROR_INSUFFICIENT_STOCK, - 'The most you may purchase is' => self::ERROR_INSUFFICIENT_STOCK, - 'The requested qty is not available' => self::ERROR_INSUFFICIENT_STOCK, - ]; - /** * @var CartRepositoryInterface */ @@ -67,25 +42,29 @@ class AddProductsToCart private $productReader; /** - * @param ProductRepositoryInterface $productRepository + * @var AddProductsToCartError + */ + private $error; + + /** * @param CartRepositoryInterface $cartRepository * @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId * @param BuyRequestBuilder $requestBuilder - * @param ProductReaderInterface|null $productReader - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @param ProductReaderInterface $productReader + * @param AddProductsToCartError $addProductsToCartError */ public function __construct( - ProductRepositoryInterface $productRepository, CartRepositoryInterface $cartRepository, MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId, BuyRequestBuilder $requestBuilder, - ProductReaderInterface $productReader = null + ProductReaderInterface $productReader, + AddProductsToCartError $addProductsToCartError ) { $this->cartRepository = $cartRepository; $this->maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId; $this->requestBuilder = $requestBuilder; - $this->productReader = $productReader ?: ObjectManager::getInstance()->get(ProductReaderInterface::class); + $this->productReader = $productReader; + $this->error = $addProductsToCartError; } /** @@ -106,7 +85,7 @@ public function execute(string $maskedCartId, array $cartItems): AddProductsToCa /** @var MessageInterface $error */ foreach ($errors as $error) { - $allErrors[] = $this->createError($error->getText()); + $allErrors[] = $this->error->create($error->getText()); } } @@ -181,14 +160,14 @@ private function addItemToCart(Quote $cart, Data\CartItem $cartItem, int $cartIt $result = null; if ($cartItem->getQuantity() <= 0) { - $errors[] = $this->createError( + $errors[] = $this->error->create( __('The product quantity should be greater than 0')->render(), $cartItemPosition ); } else { $product = $this->productReader->getProductBySku($sku); if (!$product || !$product->isSaleable() || !$product->isAvailable()) { - $errors[] = $this->createError( + $errors[] = $this->error->create( __('Could not find a product with SKU "%sku"', ['sku' => $sku])->render(), $cartItemPosition ); @@ -196,7 +175,7 @@ private function addItemToCart(Quote $cart, Data\CartItem $cartItem, int $cartIt try { $result = $cart->addProduct($product, $this->requestBuilder->build($cartItem)); } catch (\Throwable $e) { - $errors[] = $this->createError( + $errors[] = $this->error->create( __($e->getMessage())->render(), $cartItemPosition ); @@ -205,7 +184,7 @@ private function addItemToCart(Quote $cart, Data\CartItem $cartItem, int $cartIt if (is_string($result)) { foreach (array_unique(explode("\n", $result)) as $error) { - $errors[] = $this->createError(__($error)->render(), $cartItemPosition); + $errors[] = $this->error->create(__($error)->render(), $cartItemPosition); } } } @@ -213,42 +192,6 @@ private function addItemToCart(Quote $cart, Data\CartItem $cartItem, int $cartIt return $errors; } - /** - * Returns an error object - * - * @param string $message - * @param int $cartItemPosition - * @return Data\Error - */ - private function createError(string $message, int $cartItemPosition = 0): Data\Error - { - return new Data\Error( - $message, - $this->getErrorCode($message), - $cartItemPosition - ); - } - - /** - * Get message error code. - * - * TODO: introduce a separate class for getting error code from a message - * - * @param string $message - * @return string - */ - private function getErrorCode(string $message): string - { - foreach (self::MESSAGE_CODES as $codeMessage => $code) { - if (false !== stripos($message, $codeMessage)) { - return $code; - } - } - - /* If no code was matched, return the default one */ - return self::ERROR_UNDEFINED; - } - /** * Creates a new output from existing errors * diff --git a/app/code/Magento/Quote/Model/Cart/AddProductsToCartError.php b/app/code/Magento/Quote/Model/Cart/AddProductsToCartError.php new file mode 100644 index 0000000000000..fe8c0d72d4655 --- /dev/null +++ b/app/code/Magento/Quote/Model/Cart/AddProductsToCartError.php @@ -0,0 +1,71 @@ + self::ERROR_PRODUCT_NOT_FOUND, + 'The required options you selected are not available' => self::ERROR_NOT_SALABLE, + 'Product that you are trying to add is not available.' => self::ERROR_NOT_SALABLE, + 'This product is out of stock' => self::ERROR_INSUFFICIENT_STOCK, + 'There are no source items' => self::ERROR_NOT_SALABLE, + 'The fewest you may purchase is' => self::ERROR_INSUFFICIENT_STOCK, + 'The most you may purchase is' => self::ERROR_INSUFFICIENT_STOCK, + 'The requested qty is not available' => self::ERROR_INSUFFICIENT_STOCK, + ]; + + /** + * Returns an error object + * + * @param string $message + * @param int $cartItemPosition + * @return Data\Error + */ + public function create(string $message, int $cartItemPosition = 0): Data\Error + { + return new Data\Error( + $message, + $this->getErrorCode($message), + $cartItemPosition + ); + } + + /** + * Get message error code. + * + * @param string $message + * @return string + */ + private function getErrorCode(string $message): string + { + foreach (self::MESSAGE_CODES as $codeMessage => $code) { + if (false !== stripos($message, $codeMessage)) { + return $code; + } + } + + /* If no code was matched, return the default one */ + return self::ERROR_UNDEFINED; + } +} diff --git a/app/code/Magento/Quote/composer.json b/app/code/Magento/Quote/composer.json index 922f3d36fa918..1552e71351af7 100644 --- a/app/code/Magento/Quote/composer.json +++ b/app/code/Magento/Quote/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/QuoteAnalytics/composer.json b/app/code/Magento/QuoteAnalytics/composer.json index 038553b4d487e..c9e9254aa7968 100644 --- a/app/code/Magento/QuoteAnalytics/composer.json +++ b/app/code/Magento/QuoteAnalytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-quote-analytics", "description": "N/A", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-quote": "*", "magento/module-analytics": "*" diff --git a/app/code/Magento/QuoteBundleOptions/composer.json b/app/code/Magento/QuoteBundleOptions/composer.json index 79ad425b2d359..2412e9d23b329 100644 --- a/app/code/Magento/QuoteBundleOptions/composer.json +++ b/app/code/Magento/QuoteBundleOptions/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-quote-bundle-options", "description": "Magento module provides data provider for creating buy request for bundle products", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-quote": "*" }, diff --git a/app/code/Magento/QuoteConfigurableOptions/composer.json b/app/code/Magento/QuoteConfigurableOptions/composer.json index 2da064db42965..35dee93c0b12a 100644 --- a/app/code/Magento/QuoteConfigurableOptions/composer.json +++ b/app/code/Magento/QuoteConfigurableOptions/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-quote-configurable-options", "description": "Magento module provides data provider for creating buy request for configurable products", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-quote": "*" }, diff --git a/app/code/Magento/QuoteDownloadableLinks/composer.json b/app/code/Magento/QuoteDownloadableLinks/composer.json index 2b4dcc3331b8e..47030735c6081 100644 --- a/app/code/Magento/QuoteDownloadableLinks/composer.json +++ b/app/code/Magento/QuoteDownloadableLinks/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-quote-downloadable-links", "description": "Magento module provides data provider for creating buy request for links of downloadable products", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-quote": "*" }, diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForCheckout.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForCheckout.php new file mode 100644 index 0000000000000..4b2d1afdea003 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForCheckout.php @@ -0,0 +1,73 @@ +checkoutAllowance = $checkoutAllowance; + $this->getCartForUser = $getCartForUser; + } + + /** + * Gets the cart for the user validated and configured for guest checkout if applicable + * + * @param string $cartHash + * @param int|null $customerId + * @param int $storeId + * @return Quote + * @throws GraphQlAuthorizationException + * @throws GraphQlInputException + * @throws GraphQlNoSuchEntityException + */ + public function execute(string $cartHash, ?int $customerId, int $storeId): Quote + { + try { + $cart = $this->getCartForUser->execute($cartHash, $customerId, $storeId); + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); + } + $this->checkoutAllowance->execute($cart); + + if (null === $customerId || 0 === $customerId) { + if (!$cart->getCustomerEmail()) { + throw new GraphQlInputException(__("Guest email for cart is missing.")); + } + $cart->setCheckoutMethod(CartManagementInterface::METHOD_GUEST); + } + + return $cart; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php index 21df9465a08e6..77a31cc3cd023 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php @@ -7,16 +7,13 @@ namespace Magento\QuoteGraphQl\Model\Cart; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; -use Magento\Quote\Api\CartManagementInterface; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface; use Magento\Quote\Model\Quote; -use Magento\Store\Api\StoreRepositoryInterface; /** * Get cart @@ -34,31 +31,31 @@ class GetCartForUser private $cartRepository; /** - * @var CheckCartCheckoutAllowance + * @var IsActive */ - private $checkoutAllowance; + private $isActive; /** - * @var StoreRepositoryInterface + * @var UpdateCartCurrency */ - private $storeRepository; + private $updateCartCurrency; /** * @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId * @param CartRepositoryInterface $cartRepository - * @param CheckCartCheckoutAllowance $checkoutAllowance - * @param StoreRepositoryInterface $storeRepository + * @param IsActive $isActive + * @param UpdateCartCurrency $updateCartCurrency */ public function __construct( MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId, CartRepositoryInterface $cartRepository, - CheckCartCheckoutAllowance $checkoutAllowance, - StoreRepositoryInterface $storeRepository = null + IsActive $isActive, + UpdateCartCurrency $updateCartCurrency ) { $this->maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId; $this->cartRepository = $cartRepository; - $this->checkoutAllowance = $checkoutAllowance; - $this->storeRepository = $storeRepository ?: ObjectManager::getInstance()->get(StoreRepositoryInterface::class); + $this->isActive = $isActive; + $this->updateCartCurrency = $updateCartCurrency; } /** @@ -77,26 +74,19 @@ public function execute(string $cartHash, ?int $customerId, int $storeId): Quote { try { $cartId = $this->maskedQuoteIdToQuoteId->execute($cartHash); - } catch (NoSuchEntityException $exception) { - throw new GraphQlNoSuchEntityException( - __('Could not find a cart with ID "%masked_cart_id"', ['masked_cart_id' => $cartHash]) - ); - } - - try { /** @var Quote $cart */ $cart = $this->cartRepository->get($cartId); - } catch (NoSuchEntityException $e) { + } catch (NoSuchEntityException $exception) { throw new GraphQlNoSuchEntityException( __('Could not find a cart with ID "%masked_cart_id"', ['masked_cart_id' => $cartHash]) ); } - if (false === (bool)$cart->getIsActive()) { + if (false === (bool)$this->isActive->execute($cart)) { throw new GraphQlNoSuchEntityException(__('The cart isn\'t active.')); } - $cart = $this->updateCartCurrency($cart, $storeId); + $cart = $this->updateCartCurrency->execute($cart, $storeId); $cartCustomerId = (int)$cart->getCustomerId(); @@ -115,68 +105,4 @@ public function execute(string $cartHash, ?int $customerId, int $storeId): Quote } return $cart; } - - /** - * Gets the cart for the user validated and configured for guest checkout if applicable - * - * @param string $cartHash - * @param int|null $customerId - * @param int $storeId - * @return Quote - * @throws GraphQlAuthorizationException - * @throws GraphQlInputException - * @throws GraphQlNoSuchEntityException - */ - public function getCartForCheckout(string $cartHash, ?int $customerId, int $storeId): Quote - { - try { - $cart = $this->execute($cartHash, $customerId, $storeId); - } catch (NoSuchEntityException $e) { - throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); - } - $this->checkoutAllowance->execute($cart); - - if ((null === $customerId || 0 === $customerId)) { - if (!$cart->getCustomerEmail()) { - throw new GraphQlInputException(__("Guest email for cart is missing.")); - } - $cart->setCheckoutMethod(CartManagementInterface::METHOD_GUEST); - } - - return $cart; - } - - /** - * Sets cart currency based on specified store. - * - * @param Quote $cart - * @param int $storeId - * @return Quote - * @throws GraphQlInputException - * @throws NoSuchEntityException - */ - private function updateCartCurrency(Quote $cart, int $storeId): Quote - { - $cartStore = $this->storeRepository->getById($cart->getStoreId()); - $currentCartCurrencyCode = $cartStore->getCurrentCurrency()->getCode(); - if ((int)$cart->getStoreId() !== $storeId) { - $newStore = $this->storeRepository->getById($storeId); - if ($cartStore->getWebsite() !== $newStore->getWebsite()) { - throw new GraphQlInputException( - __('Can\'t assign cart to store in different website.') - ); - } - $cart->setStoreId($storeId); - $cart->setStoreCurrencyCode($newStore->getCurrentCurrency()); - $cart->setQuoteCurrencyCode($newStore->getCurrentCurrency()); - } elseif ($cart->getQuoteCurrencyCode() !== $currentCartCurrencyCode) { - $cart->setQuoteCurrencyCode($cartStore->getCurrentCurrency()); - } else { - return $cart; - } - $this->cartRepository->save($cart); - $cart = $this->cartRepository->get($cart->getId()); - - return $cart; - } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/IsActive.php b/app/code/Magento/QuoteGraphQl/Model/Cart/IsActive.php new file mode 100644 index 0000000000000..531d7ba119211 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/IsActive.php @@ -0,0 +1,27 @@ +getIsActive(); + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/UpdateCartCurrency.php b/app/code/Magento/QuoteGraphQl/Model/Cart/UpdateCartCurrency.php new file mode 100644 index 0000000000000..109ee7b4ef54a --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/UpdateCartCurrency.php @@ -0,0 +1,74 @@ +cartRepository = $cartRepository; + $this->storeRepository = $storeRepository; + } + + /** + * Sets cart currency based on specified store. + * + * @param Quote $cart + * @param int $storeId + * @return Quote + * @throws GraphQlInputException|NoSuchEntityException|LocalizedException + */ + public function execute(Quote $cart, int $storeId): Quote + { + $cartStore = $this->storeRepository->getById($cart->getStoreId()); + $currentCartCurrencyCode = $cartStore->getCurrentCurrency()->getCode(); + if ((int)$cart->getStoreId() !== $storeId) { + $newStore = $this->storeRepository->getById($storeId); + if ($cartStore->getWebsite() !== $newStore->getWebsite()) { + throw new GraphQlInputException( + __('Can\'t assign cart to store in different website.') + ); + } + $cart->setStoreId($storeId); + $cart->setStoreCurrencyCode($newStore->getCurrentCurrency()); + $cart->setQuoteCurrencyCode($newStore->getCurrentCurrency()); + } elseif ($cart->getQuoteCurrencyCode() !== $currentCartCurrencyCode) { + $cart->setQuoteCurrencyCode($cartStore->getCurrentCurrency()); + } else { + return $cart; + } + $this->cartRepository->save($cart); + return $this->cartRepository->get($cart->getId()); + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php index b0fca63018e76..7cbc64a41d37c 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php @@ -14,8 +14,8 @@ use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\GraphQl\Helper\Error\AggregateExceptionMessageFormatter; +use Magento\QuoteGraphQl\Model\Cart\GetCartForCheckout; use Magento\GraphQl\Model\Query\ContextInterface; -use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; use Magento\QuoteGraphQl\Model\Cart\PlaceOrder as PlaceOrderModel; use Magento\QuoteGraphQl\Model\Cart\PlaceOrderMutexInterface; use Magento\Sales\Api\OrderRepositoryInterface; @@ -26,9 +26,9 @@ class PlaceOrder implements ResolverInterface { /** - * @var GetCartForUser + * @var GetCartForCheckout */ - private $getCartForUser; + private $getCartForCheckout; /** * @var PlaceOrderModel @@ -51,20 +51,20 @@ class PlaceOrder implements ResolverInterface private $placeOrderMutex; /** - * @param GetCartForUser $getCartForUser + * @param GetCartForCheckout $getCartForCheckout * @param PlaceOrderModel $placeOrder * @param OrderRepositoryInterface $orderRepository * @param AggregateExceptionMessageFormatter $errorMessageFormatter * @param PlaceOrderMutexInterface|null $placeOrderMutex */ public function __construct( - GetCartForUser $getCartForUser, + GetCartForCheckout $getCartForCheckout, PlaceOrderModel $placeOrder, OrderRepositoryInterface $orderRepository, AggregateExceptionMessageFormatter $errorMessageFormatter, ?PlaceOrderMutexInterface $placeOrderMutex = null ) { - $this->getCartForUser = $getCartForUser; + $this->getCartForCheckout = $getCartForCheckout; $this->placeOrder = $placeOrder; $this->orderRepository = $orderRepository; $this->errorMessageFormatter = $errorMessageFormatter; @@ -104,7 +104,7 @@ private function run(Field $field, ContextInterface $context, ResolveInfo $info, $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); try { - $cart = $this->getCartForUser->getCartForCheckout($maskedCartId, $userId, $storeId); + $cart = $this->getCartForCheckout->execute($maskedCartId, $userId, $storeId); $orderId = $this->placeOrder->execute($cart, $maskedCartId, $userId); $order = $this->orderRepository->get($orderId); } catch (LocalizedException $e) { diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentAndPlaceOrder.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentAndPlaceOrder.php index 500c2aa359994..1b9ee4ad3907a 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentAndPlaceOrder.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentAndPlaceOrder.php @@ -15,7 +15,7 @@ use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; +use Magento\QuoteGraphQl\Model\Cart\GetCartForCheckout; use Magento\QuoteGraphQl\Model\Cart\SetPaymentAndPlaceOrder as SetPaymentAndPlaceOrderModel; use Magento\Sales\Api\OrderRepositoryInterface; @@ -29,9 +29,9 @@ class SetPaymentAndPlaceOrder implements ResolverInterface { /** - * @var GetCartForUser + * @var GetCartForCheckout */ - private $getCartForUser; + private $getCartForCheckout; /** * @var SetPaymentAndPlaceOrderModel @@ -44,16 +44,16 @@ class SetPaymentAndPlaceOrder implements ResolverInterface private $orderRepository; /** - * @param GetCartForUser $getCartForUser + * @param GetCartForCheckout $getCartForCheckout * @param SetPaymentAndPlaceOrderModel $setPaymentAndPlaceOrder * @param OrderRepositoryInterface $orderRepository */ public function __construct( - GetCartForUser $getCartForUser, + GetCartForCheckout $getCartForCheckout, SetPaymentAndPlaceOrderModel $setPaymentAndPlaceOrder, OrderRepositoryInterface $orderRepository ) { - $this->getCartForUser = $getCartForUser; + $this->getCartForCheckout = $getCartForCheckout; $this->setPaymentAndPlaceOrder = $setPaymentAndPlaceOrder; $this->orderRepository = $orderRepository; } @@ -78,7 +78,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); try { - $cart = $this->getCartForUser->getCartForCheckout($maskedCartId, $userId, $storeId); + $cart = $this->getCartForCheckout->execute($maskedCartId, $userId, $storeId); $orderId = $this->setPaymentAndPlaceOrder->execute($cart, $maskedCartId, $userId, $paymentData); $order = $this->orderRepository->get($orderId); } catch (GraphQlInputException | GraphQlNoSuchEntityException | GraphQlAuthorizationException $e) { diff --git a/app/code/Magento/QuoteGraphQl/composer.json b/app/code/Magento/QuoteGraphQl/composer.json index 4f885fa33a7b0..24cb1382634c2 100644 --- a/app/code/Magento/QuoteGraphQl/composer.json +++ b/app/code/Magento/QuoteGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-quote": "*", "magento/module-checkout": "*", diff --git a/app/code/Magento/RelatedProductGraphQl/composer.json b/app/code/Magento/RelatedProductGraphQl/composer.json index 25bb6dc47722d..9c03a5b18f644 100644 --- a/app/code/Magento/RelatedProductGraphQl/composer.json +++ b/app/code/Magento/RelatedProductGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-catalog": "*", "magento/module-catalog-graph-ql": "*", "magento/framework": "*" diff --git a/app/code/Magento/ReleaseNotification/composer.json b/app/code/Magento/ReleaseNotification/composer.json index 039ea30e339be..4ddab4217f32e 100644 --- a/app/code/Magento/ReleaseNotification/composer.json +++ b/app/code/Magento/ReleaseNotification/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-release-notification", "description": "N/A", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-user": "*", "magento/module-backend": "*", "magento/module-ui": "*", diff --git a/app/code/Magento/RemoteStorage/composer.json b/app/code/Magento/RemoteStorage/composer.json index ff2301d53ea60..107ddf6788fe2 100644 --- a/app/code/Magento/RemoteStorage/composer.json +++ b/app/code/Magento/RemoteStorage/composer.json @@ -2,10 +2,10 @@ "name": "magento/module-remote-storage", "description": "N/A", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", - "league/flysystem": "~2.4.3", - "league/flysystem-aws-s3-v3": "^2.4.3" + "league/flysystem": "^2.4", + "league/flysystem-aws-s3-v3": "^2.4" }, "suggest": { "magento/module-backend": "*", diff --git a/app/code/Magento/Reports/composer.json b/app/code/Magento/Reports/composer.json index e758e3a739f91..887a9bc1730e3 100644 --- a/app/code/Magento/Reports/composer.json +++ b/app/code/Magento/Reports/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/RequireJs/composer.json b/app/code/Magento/RequireJs/composer.json index 746b09474ec03..a8dec7db61404 100644 --- a/app/code/Magento/RequireJs/composer.json +++ b/app/code/Magento/RequireJs/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/Review/composer.json b/app/code/Magento/Review/composer.json index b79ec24b633f3..e6ef2f416962c 100644 --- a/app/code/Magento/Review/composer.json +++ b/app/code/Magento/Review/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/ReviewAnalytics/composer.json b/app/code/Magento/ReviewAnalytics/composer.json index 6694a8e8400cb..7939e3e475668 100644 --- a/app/code/Magento/ReviewAnalytics/composer.json +++ b/app/code/Magento/ReviewAnalytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-review-analytics", "description": "N/A", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-review": "*", "magento/module-analytics": "*" diff --git a/app/code/Magento/ReviewGraphQl/composer.json b/app/code/Magento/ReviewGraphQl/composer.json index ac1c11df1b8dc..e31bb53d3dafc 100644 --- a/app/code/Magento/ReviewGraphQl/composer.json +++ b/app/code/Magento/ReviewGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/module-catalog": "*", "magento/module-review": "*", "magento/module-store": "*", diff --git a/app/code/Magento/Robots/composer.json b/app/code/Magento/Robots/composer.json index d11d4568bf7d5..37c984daa0089 100644 --- a/app/code/Magento/Robots/composer.json +++ b/app/code/Magento/Robots/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-store": "*" }, diff --git a/app/code/Magento/Rss/composer.json b/app/code/Magento/Rss/composer.json index 0b89505e7e618..436c956a56313 100644 --- a/app/code/Magento/Rss/composer.json +++ b/app/code/Magento/Rss/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php b/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php index 10b32d393083c..64e2e881409a7 100644 --- a/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php +++ b/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php @@ -516,7 +516,7 @@ public function loadArray($arr) ) ? $this->_localeFormat->getNumber( $arr['is_value_parsed'] ) : false; - } elseif (!empty($arr['operator']) && $arr['operator'] == '()') { + } elseif (!empty($arr['operator']) && in_array($arr['operator'], ['()', '!()', true])) { if (isset($arr['value'])) { $arr['value'] = preg_replace('/\s*,\s*/', ',', $arr['value']); } diff --git a/app/code/Magento/Rule/Model/Condition/Sql/Builder.php b/app/code/Magento/Rule/Model/Condition/Sql/Builder.php index cb4cb9a12bd5b..ff676739695a3 100644 --- a/app/code/Magento/Rule/Model/Condition/Sql/Builder.php +++ b/app/code/Magento/Rule/Model/Condition/Sql/Builder.php @@ -144,6 +144,7 @@ protected function _joinTablesToCollection( * @return string * @throws \Magento\Framework\Exception\LocalizedException * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _getMappedSqlCondition( AbstractCondition $condition, @@ -155,7 +156,7 @@ protected function _getMappedSqlCondition( // If rule hasn't valid argument - prevent incorrect rule behavior. if (empty($argument)) { return $this->_expressionFactory->create(['expression' => '1 = -1']); - } elseif (preg_match('/[^a-z0-9\-_\.\`]/i', $argument) > 0) { + } elseif (preg_match('/[^a-z0-9\-_\.\`]/i', $argument) > 0 && !$argument instanceof \Zend_Db_Expr) { throw new \Magento\Framework\Exception\LocalizedException(__('Invalid field')); } diff --git a/app/code/Magento/Rule/composer.json b/app/code/Magento/Rule/composer.json index a1b60b7e57eeb..c39cfa4aa88d6 100644 --- a/app/code/Magento/Rule/composer.json +++ b/app/code/Magento/Rule/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "lib-libxml": "*", "magento/framework": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Sales/Model/Order/Address.php b/app/code/Magento/Sales/Model/Order/Address.php index f38345f59b8dc..bb50a59eeea84 100644 --- a/app/code/Magento/Sales/Model/Order/Address.php +++ b/app/code/Magento/Sales/Model/Order/Address.php @@ -142,7 +142,7 @@ public function getName() { $name = ''; if ($this->getPrefix()) { - $name .= $this->getPrefix() . ' '; + $name .= __($this->getPrefix()) . ' '; } $name .= $this->getFirstname(); if ($this->getMiddlename()) { @@ -150,7 +150,7 @@ public function getName() } $name .= ' ' . $this->getLastname(); if ($this->getSuffix()) { - $name .= ' ' . $this->getSuffix(); + $name .= ' ' . __($this->getSuffix()); } return $name; } diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Shipping.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Shipping.php index 0537e361c8926..1bb796565ebea 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Shipping.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Shipping.php @@ -7,6 +7,7 @@ use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Tax\Model\Calculation as TaxCalculation; +use Magento\Sales\Model\Order; /** * Order creditmemo shipping total calculation model @@ -115,25 +116,26 @@ public function collect(\Magento\Sales\Model\Order\Creditmemo $creditmemo) /** * Checks if shipping provided incl tax, tax applied after discount, and discount applied on shipping excl tax * - * @param \Magento\Sales\Model\Order $order + * @param Order $order * @return bool */ - private function isShippingIncludeTaxWithTaxAfterDiscountOnExcl($order): bool + private function isShippingIncludeTaxWithTaxAfterDiscount(Order $order): bool { - return $this->getTaxConfig()->getCalculationSequence($order->getStoreId()) - === TaxCalculation::CALC_TAX_AFTER_DISCOUNT_ON_EXCL && - $this->isSuppliedShippingAmountInclTax($order); + $calculationSequence = $this->getTaxConfig()->getCalculationSequence($order->getStoreId()); + return ($calculationSequence === TaxCalculation::CALC_TAX_AFTER_DISCOUNT_ON_EXCL + || $calculationSequence === TaxCalculation::CALC_TAX_AFTER_DISCOUNT_ON_INCL) + && $this->isSuppliedShippingAmountInclTax($order); } /** * Get allowed shipping amount to refund based on tax settings * - * @param \Magento\Sales\Model\Order $order + * @param Order $order * @return float */ - private function getAllowedAmountInclTax(\Magento\Sales\Model\Order $order): float + private function getAllowedAmountInclTax(Order $order): float { - if ($this->isShippingIncludeTaxWithTaxAfterDiscountOnExcl($order)) { + if ($this->isShippingIncludeTaxWithTaxAfterDiscount($order)) { $result = $order->getShippingInclTax(); foreach ($order->getCreditmemosCollection() as $creditmemo) { $result -= $creditmemo->getShippingInclTax(); @@ -155,7 +157,7 @@ private function getAllowedAmountInclTax(\Magento\Sales\Model\Order $order): flo private function getBaseAllowedAmountInclTax(\Magento\Sales\Model\Order $order): float { $result = $order->getBaseShippingInclTax(); - if ($this->isShippingIncludeTaxWithTaxAfterDiscountOnExcl($order)) { + if ($this->isShippingIncludeTaxWithTaxAfterDiscount($order)) { foreach ($order->getCreditmemosCollection() as $creditmemo) { $result -= $creditmemo->getBaseShippingInclTax(); } diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Tax.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Tax.php index 04c698a8e1a24..3ef0c99bb2b05 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Tax.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Tax.php @@ -5,13 +5,13 @@ */ namespace Magento\Sales\Model\Order\Creditmemo\Total; +use Magento\Framework\App\ObjectManager; use Magento\Sales\Api\Data\CreditmemoInterface; use Magento\Sales\Model\Order\Creditmemo; use Magento\Sales\Model\Order\Invoice; use Magento\Sales\Model\ResourceModel\Order\Invoice as ResourceInvoice; -use Magento\Tax\Model\Config as TaxConfig; use Magento\Tax\Model\Calculation as TaxCalculation; -use Magento\Framework\App\ObjectManager; +use Magento\Tax\Model\Config as TaxConfig; /** * Collects credit memo taxes. @@ -135,7 +135,7 @@ public function collect(Creditmemo $creditmemo) $shippingDelta = $baseOrderShippingAmount - $baseOrderShippingRefundedAmount; if ($shippingDelta > $creditmemo->getBaseShippingAmount() || - $this->isShippingIncludeTaxWithTaxAfterDiscountOnExcl($order->getStoreId())) { + $this->isShippingIncludeTaxWithTaxAfterDiscount($order->getStoreId())) { $part = $creditmemo->getShippingAmount() / $orderShippingAmount; $basePart = $creditmemo->getBaseShippingAmount() / $baseOrderShippingAmount; $shippingTaxAmount = $order->getShippingTaxAmount() * $part; @@ -213,10 +213,12 @@ public function collect(Creditmemo $creditmemo) * @param int|null $storeId * @return bool */ - private function isShippingIncludeTaxWithTaxAfterDiscountOnExcl(?int $storeId): bool + private function isShippingIncludeTaxWithTaxAfterDiscount(?int $storeId): bool { - return $this->taxConfig->getCalculationSequence($storeId) === TaxCalculation::CALC_TAX_AFTER_DISCOUNT_ON_EXCL && - $this->taxConfig->displaySalesShippingInclTax($storeId); + $calculationSequence = $this->taxConfig->getCalculationSequence($storeId); + return ($calculationSequence === TaxCalculation::CALC_TAX_AFTER_DISCOUNT_ON_EXCL + || $calculationSequence === TaxCalculation::CALC_TAX_AFTER_DISCOUNT_ON_INCL) + && $this->taxConfig->displaySalesShippingInclTax($storeId); } /** diff --git a/app/code/Magento/Sales/Test/Fixture/InvoiceComment.php b/app/code/Magento/Sales/Test/Fixture/InvoiceComment.php new file mode 100644 index 0000000000000..0ba09e4dd577a --- /dev/null +++ b/app/code/Magento/Sales/Test/Fixture/InvoiceComment.php @@ -0,0 +1,77 @@ + 0, + InvoiceCommentInterface::PARENT_ID => 0, + CommentInterface::COMMENT => 'Test Comment', + CommentInterface::IS_VISIBLE_ON_FRONT => 0, + EntityInterface::ENTITY_ID => 0, + EntityInterface::CREATED_AT => "0000-00-00 00:00:00", + ]; + + /** + * @var ServiceFactory + */ + private $serviceFactory; + + /** + * @var InvoiceCommentRepositoryInterface + */ + private $invoiceCommentRepository; + + /** + * @param ServiceFactory $serviceFactory + * @param InvoiceCommentRepositoryInterface $invoiceCommentRepository + */ + public function __construct( + ServiceFactory $serviceFactory, + InvoiceCommentRepositoryInterface $invoiceCommentRepository + ) { + $this->serviceFactory = $serviceFactory; + $this->invoiceCommentRepository = $invoiceCommentRepository; + } + + public function apply(array $data = []): ?DataObject + { + $service = $this->serviceFactory->create(InvoiceCommentRepositoryInterface::class, 'save'); + $invoiceComment = $service->execute($this->prepareData($data)); + + return $this->invoiceCommentRepository->get($invoiceComment->getId()); + } + + public function revert(DataObject $data): void + { + $invoice = $this->invoiceCommentRepository->get($data->getId()); + $this->invoiceCommentRepository->delete($invoice); + } + + /** + * Prepare invoice data + * + * @param array $data + * @return array + */ + private function prepareData(array $data): array + { + $data['entity'] = array_merge(self::DEFAULT_DATA, $data); + + return $data; + } +} diff --git a/app/code/Magento/Sales/Test/Mftf/Data/OrderStatusConfigData.xml b/app/code/Magento/Sales/Test/Mftf/Data/OrderStatusConfigData.xml index 4bb8256b68be1..8141d7fb534c5 100644 --- a/app/code/Magento/Sales/Test/Mftf/Data/OrderStatusConfigData.xml +++ b/app/code/Magento/Sales/Test/Mftf/Data/OrderStatusConfigData.xml @@ -15,4 +15,24 @@ Pending pending + + payment/free/order_status + payment + 1 + Pending + pending + + + payment/free/order_status + default + 1 + Custom + custom + + + payment/free/payment_action + default + 1 + 0 + diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Total/ShippingTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Total/ShippingTest.php index 4ae0b3305e8ed..998673a30e17d 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Total/ShippingTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Total/ShippingTest.php @@ -509,14 +509,15 @@ public function testCollectRefundShippingAmountIncTax() /** * situation: The admin user specified the desired refund amount that has taxes and discount embedded within it * + * @dataProvider calculationSequenceDataProvider * @throws LocalizedException */ - public function testCollectUsingShippingInclTaxAndDiscountOnExclBeforeTax() + public function testCollectUsingShippingInclTaxAndDiscountBeforeTax(string $calculationSequence) { $this->taxConfig->expects($this->any())->method('displaySalesShippingInclTax')->willReturn(true); $this->taxConfig->expects($this->any()) ->method('getCalculationSequence') - ->willReturn(TaxCalculation::CALC_TAX_AFTER_DISCOUNT_ON_EXCL); + ->willReturn($calculationSequence); $orderShippingAmount = 14.55; $shippingTaxAmount = 0.45; @@ -603,4 +604,15 @@ public function testCollectUsingShippingInclTaxAndDiscountOnExclBeforeTax() ->willReturnSelf(); $this->shippingCollector->collect($this->creditmemoMock); } + + /** + * @return array + */ + public function calculationSequenceDataProvider(): array + { + return [ + 'inclTax' => [TaxCalculation::CALC_TAX_AFTER_DISCOUNT_ON_INCL], + 'exclTax' => [TaxCalculation::CALC_TAX_AFTER_DISCOUNT_ON_EXCL], + ]; + } } diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentFactoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentFactoryTest.php index e73838d85c7e8..dd721da78ad76 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentFactoryTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentFactoryTest.php @@ -8,7 +8,6 @@ namespace Magento\Sales\Test\Unit\Model\Order; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Sales\Model\Convert\Order; use Magento\Sales\Model\Convert\OrderFactory; use Magento\Sales\Model\Order\Item; @@ -52,8 +51,6 @@ class ShipmentFactoryTest extends TestCase */ protected function setUp(): void { - $objectManager = new ObjectManager($this); - $this->converter = $this->createPartialMock( Order::class, ['toShipment', 'itemToShipmentItem'] @@ -69,12 +66,9 @@ protected function setUp(): void ['create'] ); - $this->subject = $objectManager->getObject( - ShipmentFactory::class, - [ - 'convertOrderFactory' => $convertOrderFactory, - 'trackFactory' => $this->trackFactory - ] + $this->subject = new ShipmentFactory( + $convertOrderFactory, + $this->trackFactory ); } diff --git a/app/code/Magento/Sales/composer.json b/app/code/Magento/Sales/composer.json index 710d5c07d0490..e0ea835d63087 100644 --- a/app/code/Magento/Sales/composer.json +++ b/app/code/Magento/Sales/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Sales/view/frontend/templates/email/invoice/items.phtml b/app/code/Magento/Sales/view/frontend/templates/email/invoice/items.phtml index e2efd650295d4..f2b2de505c300 100644 --- a/app/code/Magento/Sales/view/frontend/templates/email/invoice/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/email/invoice/items.phtml @@ -4,26 +4,29 @@ * See COPYING.txt for license details. */ +/** @var \Magento\Sales\Block\Order\Email\Invoice\Items $block */ +/** @var \Magento\Framework\Escaper $escaper */ + ?> getInvoice() ?> getOrder() ?> - + - getAllItems() as $_item) : ?> - getOrderItem()->getParentItem()) : ?> + getAllItems() as $_item): ?> + getOrderItem()->getParentItem()): ?> getItemHtml($_item) ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/email/items/creditmemo/default.phtml b/app/code/Magento/Sales/view/frontend/templates/email/items/creditmemo/default.phtml index 6c304db453338..7c4b96ea55049 100644 --- a/app/code/Magento/Sales/view/frontend/templates/email/items/creditmemo/default.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/email/items/creditmemo/default.phtml @@ -4,28 +4,29 @@ * See COPYING.txt for license details. */ +/** @var \Magento\Framework\Escaper $escaper */ ?> getItem() ?> getItem()->getOrder(); ?> diff --git a/app/code/Magento/SalesAnalytics/composer.json b/app/code/Magento/SalesAnalytics/composer.json index 6be5b39e3df33..943fbf3e7ef07 100644 --- a/app/code/Magento/SalesAnalytics/composer.json +++ b/app/code/Magento/SalesAnalytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-sales-analytics", "description": "N/A", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-sales": "*", "magento/module-analytics": "*" diff --git a/app/code/Magento/SalesGraphQl/Model/Resolver/Invoices.php b/app/code/Magento/SalesGraphQl/Model/Resolver/Invoices.php index f106752075c25..02d0f7687894a 100644 --- a/app/code/Magento/SalesGraphQl/Model/Resolver/Invoices.php +++ b/app/code/Magento/SalesGraphQl/Model/Resolver/Invoices.php @@ -41,10 +41,31 @@ public function resolve( $invoices[] = [ 'id' => base64_encode($invoice->getEntityId()), 'number' => $invoice['increment_id'], + 'comments' => $this->getInvoiceComments($invoice), 'model' => $invoice, 'order' => $orderModel ]; } return $invoices; } + + /** + * Get invoice comments in proper format + * + * @param InvoiceInterface $invoice + * @return array + */ + private function getInvoiceComments(InvoiceInterface $invoice): array + { + $comments = []; + foreach ($invoice->getComments() as $comment) { + if ($comment->getIsVisibleOnFront()) { + $comments[] = [ + 'timestamp' => $comment->getCreatedAt(), + 'message' => $comment->getComment() + ]; + } + } + return $comments; + } } diff --git a/app/code/Magento/SalesGraphQl/composer.json b/app/code/Magento/SalesGraphQl/composer.json index dfa29321b033e..7215c8fefa8eb 100644 --- a/app/code/Magento/SalesGraphQl/composer.json +++ b/app/code/Magento/SalesGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-sales": "*", "magento/module-store": "*", diff --git a/app/code/Magento/SalesInventory/composer.json b/app/code/Magento/SalesInventory/composer.json index e5c5e90f8dfb2..ad11c308042fb 100644 --- a/app/code/Magento/SalesInventory/composer.json +++ b/app/code/Magento/SalesInventory/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-catalog-inventory": "*", diff --git a/app/code/Magento/SalesRule/composer.json b/app/code/Magento/SalesRule/composer.json index fef739ce979a7..89fd6cb64b89b 100644 --- a/app/code/Magento/SalesRule/composer.json +++ b/app/code/Magento/SalesRule/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/framework-bulk": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/SalesSequence/composer.json b/app/code/Magento/SalesSequence/composer.json index d06655b83bfc2..c00dae5f5b62d 100644 --- a/app/code/Magento/SalesSequence/composer.json +++ b/app/code/Magento/SalesSequence/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/SampleData/composer.json b/app/code/Magento/SampleData/composer.json index 05826fd71fc13..bccca4714b922 100644 --- a/app/code/Magento/SampleData/composer.json +++ b/app/code/Magento/SampleData/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "suggest": { diff --git a/app/code/Magento/Search/Test/Mftf/Test/AutoCompleteSearchTermsAndPhrasesWhileUserIsTypingTest.xml b/app/code/Magento/Search/Test/Mftf/Test/AutoCompleteSearchTermsAndPhrasesWhileUserIsTypingTest.xml index f549b02d2820d..090fc1d3cb50c 100644 --- a/app/code/Magento/Search/Test/Mftf/Test/AutoCompleteSearchTermsAndPhrasesWhileUserIsTypingTest.xml +++ b/app/code/Magento/Search/Test/Mftf/Test/AutoCompleteSearchTermsAndPhrasesWhileUserIsTypingTest.xml @@ -8,6 +8,7 @@ + @@ -67,5 +68,6 @@ <actionGroup ref="StorefrontAssertProductNameOnProductMainPageActionGroup" stepKey="seeProductNameSku"> <argument name="productName" value="$$simpleProduct.name$$"/> </actionGroup> + </test> </tests> diff --git a/app/code/Magento/Search/composer.json b/app/code/Magento/Search/composer.json index 4b9241f8303d7..ed0779d3d7698 100644 --- a/app/code/Magento/Search/composer.json +++ b/app/code/Magento/Search/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog-search": "*", diff --git a/app/code/Magento/Security/composer.json b/app/code/Magento/Security/composer.json index 07fd6655f678e..0a2910591517d 100644 --- a/app/code/Magento/Security/composer.json +++ b/app/code/Magento/Security/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-config": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/SendFriend/composer.json b/app/code/Magento/SendFriend/composer.json index 47623f8f683a1..7ffc4924f2495 100644 --- a/app/code/Magento/SendFriend/composer.json +++ b/app/code/Magento/SendFriend/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/SendFriendGraphQl/composer.json b/app/code/Magento/SendFriendGraphQl/composer.json index e836197fd42e1..6abc8d6baf202 100644 --- a/app/code/Magento/SendFriendGraphQl/composer.json +++ b/app/code/Magento/SendFriendGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-send-friend": "*", diff --git a/app/code/Magento/Shipping/Test/Mftf/Test/AdminDisableEnableShipmentCommentsAndVerifyNotifyCustomerByEmailCheckboxTest.xml b/app/code/Magento/Shipping/Test/Mftf/Test/AdminDisableEnableShipmentCommentsAndVerifyNotifyCustomerByEmailCheckboxTest.xml new file mode 100644 index 0000000000000..f99a4808ba8c1 --- /dev/null +++ b/app/code/Magento/Shipping/Test/Mftf/Test/AdminDisableEnableShipmentCommentsAndVerifyNotifyCustomerByEmailCheckboxTest.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDisableEnableShipmentCommentsAndVerifyNotifyCustomerByEmailCheckboxTest"> + <annotations> + <stories value="There is no Notify Customer by Email checkbox available when shipment comments are disabled"/> + <title value="Disable shipment comments section and validate Notify Customer by Email is disabled"/> + <description value="Disable shipment comments section and validate Notify Customer by Email is disabled"/> + <severity value="MAJOR"/> + <testCaseId value="AC-5678"/> + <group value="shipping"/> + </annotations> + <before> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"/> + + <!--create order for created product and create shipment also--> + <createData entity="CustomerCart" stepKey="createCustomerCart"> + <requiredEntity createDataKey="createCustomer"/> + </createData> + <createData entity="CustomerCartItem" stepKey="addCartItem"> + <requiredEntity createDataKey="createCustomerCart"/> + <requiredEntity createDataKey="createSimpleProduct"/> + </createData> + <createData entity="CustomerAddressInformation" stepKey="addCustomerOrderAddress"> + <requiredEntity createDataKey="createCustomerCart"/> + </createData> + <updateData createDataKey="createCustomerCart" entity="CustomerOrderPaymentMethod" stepKey="sendCustomerPaymentInformation"> + <requiredEntity createDataKey="createCustomerCart"/> + </updateData> + <createData entity="Shipment" stepKey="shipOrder"> + <requiredEntity createDataKey="createCustomerCart"/> + </createData> + <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAsAdmin"/> + + <!-- disable Shipment comments--> + <magentoCLI command="config:set sales_email/shipment_comment/enabled 0" stepKey="disableShipmentComments"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <!--open created order and then open shipment for that order--> + <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="openOrdersGrid"/> + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearFilters"/> + <grabTextFrom selector="{{AdminOrdersGridSection.orderIdByIncrementId($createCustomerCart.return$)}}" stepKey="orderId"/> + <actionGroup ref="FilterShipmentGridByOrderIdActionGroup" stepKey="filterForNewlyCreatedShipment"> + <argument name="orderId" value="$orderId"/> + </actionGroup> + <actionGroup ref="AdminSelectFirstGridRowActionGroup" stepKey="selectShipmentFromGrid"/> + + <!-- verify "Notify Customer by Email" checkbox should not be displayed.--> + <actionGroup ref="AssertAdminThereIsNoNotifyCustomerByEmailCheckboxActionGroup" stepKey="doNotSeeNotifyCustomerCheckbox"/> + + <!-- enable Shipment comments--> + <magentoCLI command="config:set sales_email/shipment_comment/enabled 1" stepKey="enableShipmentComments"/> + <actionGroup ref="AdminOrdersPageOpenActionGroup" stepKey="openOrdersGridSecondTime"/> + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearFiltersSecondTime"/> + <actionGroup ref="FilterShipmentGridByOrderIdActionGroup" stepKey="filterForNewlyCreatedShipmentSecondTime"> + <argument name="orderId" value="$orderId"/> + </actionGroup> + <actionGroup ref="AdminSelectFirstGridRowActionGroup" stepKey="selectShipmentFromGridSecondTime"/> + + <!-- verify "Notify Customer by Email" checkbox should be displayed.--> + <seeElement selector="{{AdminShipmentMainActionsSection.notifyCustomerByEmail}}" stepKey="seeCheckbox"/> + </test> +</tests> + diff --git a/app/code/Magento/Shipping/composer.json b/app/code/Magento/Shipping/composer.json index 8afbe9553094f..0347a97e755d7 100644 --- a/app/code/Magento/Shipping/composer.json +++ b/app/code/Magento/Shipping/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "ext-gd": "*", "magento/framework": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Sitemap/composer.json b/app/code/Magento/Sitemap/composer.json index be9826186e128..3323abebdebac 100644 --- a/app/code/Magento/Sitemap/composer.json +++ b/app/code/Magento/Sitemap/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/Store/composer.json b/app/code/Magento/Store/composer.json index 6dd17c590490e..c4c195e45c138 100644 --- a/app/code/Magento/Store/composer.json +++ b/app/code/Magento/Store/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-catalog": "*", "magento/module-config": "*", diff --git a/app/code/Magento/StoreGraphQl/composer.json b/app/code/Magento/StoreGraphQl/composer.json index d5cb8d1ff4176..f5fd98fdc4cae 100644 --- a/app/code/Magento/StoreGraphQl/composer.json +++ b/app/code/Magento/StoreGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-store": "*", "magento/module-graph-ql": "*", diff --git a/app/code/Magento/Swagger/composer.json b/app/code/Magento/Swagger/composer.json index 0a7b1c401886c..fb357a01e22c0 100644 --- a/app/code/Magento/Swagger/composer.json +++ b/app/code/Magento/Swagger/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/SwaggerWebapi/composer.json b/app/code/Magento/SwaggerWebapi/composer.json index dd1dc5d0011af..ea2b06ed681f9 100644 --- a/app/code/Magento/SwaggerWebapi/composer.json +++ b/app/code/Magento/SwaggerWebapi/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-swagger": "*" }, diff --git a/app/code/Magento/SwaggerWebapiAsync/composer.json b/app/code/Magento/SwaggerWebapiAsync/composer.json index e69821cc1067a..b02a3e031b1ae 100644 --- a/app/code/Magento/SwaggerWebapiAsync/composer.json +++ b/app/code/Magento/SwaggerWebapiAsync/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-swagger": "*" }, diff --git a/app/code/Magento/Swatches/composer.json b/app/code/Magento/Swatches/composer.json index d2f9201e8be5e..91f3d59016f7a 100644 --- a/app/code/Magento/Swatches/composer.json +++ b/app/code/Magento/Swatches/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/SwatchesGraphQl/composer.json b/app/code/Magento/SwatchesGraphQl/composer.json index 1dc7a1528ad1b..744ed81435c34 100644 --- a/app/code/Magento/SwatchesGraphQl/composer.json +++ b/app/code/Magento/SwatchesGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-swatches": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/SwatchesLayeredNavigation/composer.json b/app/code/Magento/SwatchesLayeredNavigation/composer.json index 9af558ec757e4..ff8ea5715b944 100644 --- a/app/code/Magento/SwatchesLayeredNavigation/composer.json +++ b/app/code/Magento/SwatchesLayeredNavigation/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/magento-composer-installer": "*" }, diff --git a/app/code/Magento/Tax/composer.json b/app/code/Magento/Tax/composer.json index e5628b04cc3f9..fd7a5a075998e 100644 --- a/app/code/Magento/Tax/composer.json +++ b/app/code/Magento/Tax/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/TaxGraphQl/composer.json b/app/code/Magento/TaxGraphQl/composer.json index 3ebd323fbfe12..fef2c01d039da 100644 --- a/app/code/Magento/TaxGraphQl/composer.json +++ b/app/code/Magento/TaxGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "suggest": { diff --git a/app/code/Magento/TaxImportExport/composer.json b/app/code/Magento/TaxImportExport/composer.json index b83fe6dcdacf1..2f7d6737e9596 100644 --- a/app/code/Magento/TaxImportExport/composer.json +++ b/app/code/Magento/TaxImportExport/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-directory": "*", diff --git a/app/code/Magento/Theme/Block/Html/Topmenu.php b/app/code/Magento/Theme/Block/Html/Topmenu.php index 4521e38532aba..f8460b43ba2ff 100644 --- a/app/code/Magento/Theme/Block/Html/Topmenu.php +++ b/app/code/Magento/Theme/Block/Html/Topmenu.php @@ -356,17 +356,6 @@ public function getIdentities() return $this->identities; } - /** - * Get tags array for saving cache - * - * @return array - * @since 100.1.0 - */ - protected function getCacheTags() - { - return array_merge(parent::getCacheTags(), $this->getIdentities()); - } - /** * Get menu object. * diff --git a/app/code/Magento/Theme/composer.json b/app/code/Magento/Theme/composer.json index 80fe77d9c123f..658a856db5fc2 100644 --- a/app/code/Magento/Theme/composer.json +++ b/app/code/Magento/Theme/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-cms": "*", diff --git a/app/code/Magento/ThemeGraphQl/composer.json b/app/code/Magento/ThemeGraphQl/composer.json index dbb9afddd7df0..6b4ee27e2f11b 100644 --- a/app/code/Magento/ThemeGraphQl/composer.json +++ b/app/code/Magento/ThemeGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "suggest": { diff --git a/app/code/Magento/Translation/composer.json b/app/code/Magento/Translation/composer.json index 28f6a54cc301f..791bfbd7b1a73 100644 --- a/app/code/Magento/Translation/composer.json +++ b/app/code/Magento/Translation/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-developer": "*", diff --git a/app/code/Magento/Ui/composer.json b/app/code/Magento/Ui/composer.json index 8d6650101b5b2..d25e69071a791 100644 --- a/app/code/Magento/Ui/composer.json +++ b/app/code/Magento/Ui/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Ups/composer.json b/app/code/Magento/Ups/composer.json index e34416ef0c05f..dc80330fa3828 100644 --- a/app/code/Magento/Ups/composer.json +++ b/app/code/Magento/Ups/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog-inventory": "*", diff --git a/app/code/Magento/UrlRewrite/composer.json b/app/code/Magento/UrlRewrite/composer.json index 84f06e17b6465..7dafa8b8f4d07 100644 --- a/app/code/Magento/UrlRewrite/composer.json +++ b/app/code/Magento/UrlRewrite/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/UrlRewriteGraphQl/composer.json b/app/code/Magento/UrlRewriteGraphQl/composer.json index 3e943ecf3e749..5e19ae73f5781 100644 --- a/app/code/Magento/UrlRewriteGraphQl/composer.json +++ b/app/code/Magento/UrlRewriteGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-url-rewrite": "*" }, diff --git a/app/code/Magento/User/composer.json b/app/code/Magento/User/composer.json index 0008abc75e5be..0fa7ec8250c94 100644 --- a/app/code/Magento/User/composer.json +++ b/app/code/Magento/User/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/Usps/composer.json b/app/code/Magento/Usps/composer.json index 8471b83a41246..107d4755d92c4 100644 --- a/app/code/Magento/Usps/composer.json +++ b/app/code/Magento/Usps/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "lib-libxml": "*", "magento/framework": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/Variable/composer.json b/app/code/Magento/Variable/composer.json index a65fefb589422..2af748d990c35 100644 --- a/app/code/Magento/Variable/composer.json +++ b/app/code/Magento/Variable/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-store": "*", diff --git a/app/code/Magento/Vault/composer.json b/app/code/Magento/Vault/composer.json index e285571634993..f671abff34d08 100644 --- a/app/code/Magento/Vault/composer.json +++ b/app/code/Magento/Vault/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-checkout": "*", "magento/module-customer": "*", diff --git a/app/code/Magento/VaultGraphQl/composer.json b/app/code/Magento/VaultGraphQl/composer.json index e4a5dd65fa76d..4d8e565267a81 100644 --- a/app/code/Magento/VaultGraphQl/composer.json +++ b/app/code/Magento/VaultGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-vault": "*", "magento/module-graph-ql": "*" diff --git a/app/code/Magento/Version/composer.json b/app/code/Magento/Version/composer.json index dc9ac096f7215..36503adfc841c 100644 --- a/app/code/Magento/Version/composer.json +++ b/app/code/Magento/Version/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "type": "magento2-module", diff --git a/app/code/Magento/Webapi/composer.json b/app/code/Magento/Webapi/composer.json index 6a4823d2b1d15..d8c713391c4a0 100644 --- a/app/code/Magento/Webapi/composer.json +++ b/app/code/Magento/Webapi/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-authorization": "*", "magento/module-backend": "*", diff --git a/app/code/Magento/WebapiAsync/composer.json b/app/code/Magento/WebapiAsync/composer.json index 7627a890f8303..9bdd9d48f1cc7 100644 --- a/app/code/Magento/WebapiAsync/composer.json +++ b/app/code/Magento/WebapiAsync/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-webapi": "*", "magento/module-asynchronous-operations": "*", diff --git a/app/code/Magento/WebapiSecurity/composer.json b/app/code/Magento/WebapiSecurity/composer.json index bb4d8c10be48c..16851cad3d89f 100644 --- a/app/code/Magento/WebapiSecurity/composer.json +++ b/app/code/Magento/WebapiSecurity/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-webapi": "*" }, diff --git a/app/code/Magento/Weee/composer.json b/app/code/Magento/Weee/composer.json index 3086929d12c0a..226f55ed11319 100644 --- a/app/code/Magento/Weee/composer.json +++ b/app/code/Magento/Weee/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/WeeeGraphQl/composer.json b/app/code/Magento/WeeeGraphQl/composer.json index 1cda2e3bc753b..aa4d28bcc7f73 100644 --- a/app/code/Magento/WeeeGraphQl/composer.json +++ b/app/code/Magento/WeeeGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-store": "*", "magento/module-tax": "*", diff --git a/app/code/Magento/Widget/composer.json b/app/code/Magento/Widget/composer.json index 5538cc70bff45..e30a41ae1f95d 100644 --- a/app/code/Magento/Widget/composer.json +++ b/app/code/Magento/Widget/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/Wishlist/composer.json b/app/code/Magento/Wishlist/composer.json index 4a9ce797c9add..82063e9c1bfbc 100644 --- a/app/code/Magento/Wishlist/composer.json +++ b/app/code/Magento/Wishlist/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", diff --git a/app/code/Magento/WishlistAnalytics/composer.json b/app/code/Magento/WishlistAnalytics/composer.json index 0a313416ce474..d990be3af68b0 100644 --- a/app/code/Magento/WishlistAnalytics/composer.json +++ b/app/code/Magento/WishlistAnalytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-wishlist-analytics", "description": "N/A", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-wishlist": "*", "magento/module-analytics": "*" diff --git a/app/code/Magento/WishlistGraphQl/composer.json b/app/code/Magento/WishlistGraphQl/composer.json index ee08ec077c917..d5bb93fefa7ec 100755 --- a/app/code/Magento/WishlistGraphQl/composer.json +++ b/app/code/Magento/WishlistGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/module-wishlist": "*", "magento/module-store": "*", diff --git a/app/design/adminhtml/Magento/backend/composer.json b/app/design/adminhtml/Magento/backend/composer.json index 450d82475e488..d5cb290cc67b9 100644 --- a/app/design/adminhtml/Magento/backend/composer.json +++ b/app/design/adminhtml/Magento/backend/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "type": "magento2-theme", diff --git a/app/design/frontend/Magento/blank/composer.json b/app/design/frontend/Magento/blank/composer.json index 741f700dfc1e2..afb262619592a 100644 --- a/app/design/frontend/Magento/blank/composer.json +++ b/app/design/frontend/Magento/blank/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*" }, "type": "magento2-theme", diff --git a/app/design/frontend/Magento/luma/composer.json b/app/design/frontend/Magento/luma/composer.json index 34f1ef911a84c..f456c842cbdd4 100644 --- a/app/design/frontend/Magento/luma/composer.json +++ b/app/design/frontend/Magento/luma/composer.json @@ -5,7 +5,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "magento/framework": "*", "magento/theme-frontend-blank": "*" }, diff --git a/composer.json b/composer.json index d54e4091079d7..9378aa9d574f5 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "ext-bcmath": "*", "ext-ctype": "*", "ext-curl": "*", @@ -34,74 +34,74 @@ "ext-xsl": "*", "ext-zip": "*", "lib-libxml": "*", - "colinmollenhour/cache-backend-file": "~1.4.1", - "colinmollenhour/cache-backend-redis": "1.14.2", - "colinmollenhour/credis": "1.13.0", - "colinmollenhour/php-redis-session-abstract": "v1.5.0", + "colinmollenhour/cache-backend-file": "^1.4", + "colinmollenhour/cache-backend-redis": "^1.14", + "colinmollenhour/credis": "^1.13", + "colinmollenhour/php-redis-session-abstract": "^1.5", "composer/composer": "^2.0, !=2.2.16", - "elasticsearch/elasticsearch": "~7.17.0", + "elasticsearch/elasticsearch": "^7.17", "ezyang/htmlpurifier": "^4.14", - "guzzlehttp/guzzle": "^7.4.2", + "guzzlehttp/guzzle": "^7.4", "laminas/laminas-captcha": "^2.12", - "laminas/laminas-code": "~4.5.0", - "laminas/laminas-db": "^2.15.0", - "laminas/laminas-dependency-plugin": "^2.2.0", - "laminas/laminas-di": "^3.7.0", - "laminas/laminas-escaper": "~2.10.0", - "laminas/laminas-eventmanager": "^3.5.0", - "laminas/laminas-feed": "^2.17.0", - "laminas/laminas-file": "^2.11.0", - "laminas/laminas-filter": "^2.17.0", - "laminas/laminas-http": "^2.15.0", - "laminas/laminas-i18n": "^2.17.0", - "laminas/laminas-mail": "^2.16.0", - "laminas/laminas-mime": "^2.9.1", - "laminas/laminas-modulemanager": "^2.11.0", - "laminas/laminas-mvc": "^3.3.3", + "laminas/laminas-code": "^4.5", + "laminas/laminas-db": "^2.15", + "laminas/laminas-dependency-plugin": "^2.5", + "laminas/laminas-di": "^3.7", + "laminas/laminas-escaper": "^2.10", + "laminas/laminas-eventmanager": "^3.5", + "laminas/laminas-feed": "^2.17", + "laminas/laminas-file": "^2.11", + "laminas/laminas-filter": "^2.17", + "laminas/laminas-http": "^2.15", + "laminas/laminas-i18n": "^2.17", + "laminas/laminas-mail": "^2.16", + "laminas/laminas-mime": "^2.9", + "laminas/laminas-modulemanager": "^2.11", + "laminas/laminas-mvc": "^3.3", "laminas/laminas-oauth": "^2.4", - "laminas/laminas-permissions-acl": "^2.10.0", - "laminas/laminas-servicemanager": "^3.16.0", - "laminas/laminas-soap": "^2.10.0", - "laminas/laminas-stdlib": "^3.11.0", - "laminas/laminas-uri": "^2.9.1", - "laminas/laminas-validator": "^2.23.0", - "league/flysystem": "~2.4.5", - "league/flysystem-aws-s3-v3": "^2.4.3", - "magento/composer": "~1.9.0-beta1", - "magento/composer-dependency-version-audit-plugin": "~0.1", + "laminas/laminas-permissions-acl": "^2.10", + "laminas/laminas-servicemanager": "^3.16", + "laminas/laminas-soap": "^2.10", + "laminas/laminas-stdlib": "^3.11", + "laminas/laminas-uri": "^2.9", + "laminas/laminas-validator": "^2.23", + "league/flysystem": "^2.4", + "league/flysystem-aws-s3-v3": "^2.4", + "magento/composer": "^1.9.0-beta1", + "magento/composer-dependency-version-audit-plugin": "^0.1", "magento/magento-composer-installer": ">=0.4.0-beta1", - "magento/zendframework1": "~1.15.0", + "magento/zendframework1": "^1.15", "monolog/monolog": "^2.7", - "opensearch-project/opensearch-php": "~1.0.0", + "opensearch-project/opensearch-php": "^1.0 || ^2.0", "pelago/emogrifier": "^6.0.0", - "php-amqplib/php-amqplib": "~3.2.0", - "phpseclib/mcrypt_compat": "~2.0.2", - "phpseclib/phpseclib": "~3.0.13", - "ramsey/uuid": "~4.2.0", - "symfony/console": "~5.4.12", - "symfony/string": "~5.4.12", - "symfony/intl": "~5.4.11", - "symfony/process": "~5.4.8", - "tedivm/jshrink": "~1.4.0", - "tubalmartin/cssmin": "4.1.1", + "php-amqplib/php-amqplib": "^3.2", + "phpseclib/mcrypt_compat": "^2.0", + "phpseclib/phpseclib": "^3.0", + "ramsey/uuid": "^4.2", + "symfony/console": "^5.4", + "symfony/string": "^5.4", + "symfony/intl": "^5.4", + "symfony/process": "^5.4", + "tedivm/jshrink": "^1.4", + "tubalmartin/cssmin": "^4.1", "web-token/jwt-framework": "^v2.2.7", - "webonyx/graphql-php": "~14.11.6", - "wikimedia/less.php": "^3.0.0" + "webonyx/graphql-php": "^14.11", + "wikimedia/less.php": "^3.0" }, "require-dev": { - "allure-framework/allure-phpunit": "~1.5.0", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.2", - "friendsofphp/php-cs-fixer": "~3.8.0", - "lusitanian/oauth": "~0.8.10", + "allure-framework/allure-phpunit": "^1.5", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "friendsofphp/php-cs-fixer": "^3.8", + "lusitanian/oauth": "^0.8", "magento/magento-coding-standard": "*", "magento/magento2-functional-testing-framework": "^3.10", - "pdepend/pdepend": "~2.10.0", - "phpmd/phpmd": "^2.12.0", - "phpstan/phpstan": "^1.6.8", - "phpunit/phpunit": "~9.5.20", - "sebastian/phpcpd": "^6.0.3", - "squizlabs/php_codesniffer": "~3.6.0", - "symfony/finder": "^5.4.8" + "pdepend/pdepend": "^2.10", + "phpmd/phpmd": "^2.12", + "phpstan/phpstan": "^1.7", + "phpunit/phpunit": "^9.5", + "sebastian/phpcpd": "^6.0", + "squizlabs/php_codesniffer": "^3.6", + "symfony/finder": "^5.4" }, "suggest": { "ext-pcntl": "Need for run processes in parallel mode" diff --git a/composer.lock b/composer.lock index d14fa66e12f60..1f179b22b53a8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3f293d4d78b3671f50ef799b17f4a5b7", + "content-hash": "0ad4c199ce2f8eff1b17f2a05a682c3a", "packages": [ { "name": "aws/aws-crt-php", @@ -2080,28 +2080,28 @@ }, { "name": "laminas/laminas-dependency-plugin", - "version": "2.2.0", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-dependency-plugin.git", - "reference": "73cfb63ddca9d6bfedad5e0a038f6d55063975a3" + "reference": "8f2d10199381cdc7d0843bfadad55f8485df9e38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-dependency-plugin/zipball/73cfb63ddca9d6bfedad5e0a038f6d55063975a3", - "reference": "73cfb63ddca9d6bfedad5e0a038f6d55063975a3", + "url": "https://api.github.com/repos/laminas/laminas-dependency-plugin/zipball/8f2d10199381cdc7d0843bfadad55f8485df9e38", + "reference": "8f2d10199381cdc7d0843bfadad55f8485df9e38", "shasum": "" }, "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": "^7.3 || ~8.0.0 || ~8.1.0" + "composer-plugin-api": ">=1.1.0 <2.3.0", + "php": "~8.0.0 || ~8.1.0 || ~8.2.0" }, "require-dev": { - "composer/composer": "^1.9 || ^2.0", - "laminas/laminas-coding-standard": "^2.2.1", - "mikey179/vfsstream": "^1.6.10@alpha", + "composer/composer": ">=1.9.0 <2.3.0", + "laminas/laminas-coding-standard": "~2.4.0", + "mikey179/vfsstream": "^1.6.11", "phpunit/phpunit": "^9.5.5", - "psalm/plugin-phpunit": "^0.15.1", + "psalm/plugin-phpunit": "^0.17.0", "roave/security-advisories": "dev-master", "vimeo/psalm": "^4.5" }, @@ -2121,7 +2121,7 @@ "description": "Replace zendframework and zfcampus packages with their Laminas Project equivalents.", "support": { "issues": "https://github.com/laminas/laminas-dependency-plugin/issues", - "source": "https://github.com/laminas/laminas-dependency-plugin/tree/2.2.0" + "source": "https://github.com/laminas/laminas-dependency-plugin/tree/2.5.0" }, "funding": [ { @@ -2129,7 +2129,7 @@ "type": "community_bridge" } ], - "time": "2021-09-08T17:51:35+00:00" + "time": "2022-10-16T14:45:47+00:00" }, { "name": "laminas/laminas-di", @@ -2210,32 +2210,32 @@ }, { "name": "laminas/laminas-escaper", - "version": "2.10.0", + "version": "2.12.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-escaper.git", - "reference": "58af67282db37d24e584a837a94ee55b9c7552be" + "reference": "ee7a4c37bf3d0e8c03635d5bddb5bb3184ead490" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/58af67282db37d24e584a837a94ee55b9c7552be", - "reference": "58af67282db37d24e584a837a94ee55b9c7552be", + "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/ee7a4c37bf3d0e8c03635d5bddb5bb3184ead490", + "reference": "ee7a4c37bf3d0e8c03635d5bddb5bb3184ead490", "shasum": "" }, "require": { "ext-ctype": "*", "ext-mbstring": "*", - "php": "^7.4 || ~8.0.0 || ~8.1.0" + "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0" }, "conflict": { "zendframework/zend-escaper": "*" }, "require-dev": { "infection/infection": "^0.26.6", - "laminas/laminas-coding-standard": "~2.3.0", + "laminas/laminas-coding-standard": "~2.4.0", "maglnet/composer-require-checker": "^3.8.0", "phpunit/phpunit": "^9.5.18", - "psalm/plugin-phpunit": "^0.16.1", + "psalm/plugin-phpunit": "^0.17.0", "vimeo/psalm": "^4.22.0" }, "type": "library", @@ -2268,36 +2268,37 @@ "type": "community_bridge" } ], - "time": "2022-03-08T20:15:36+00:00" + "time": "2022-10-10T10:11:09+00:00" }, { "name": "laminas/laminas-eventmanager", - "version": "3.5.0", + "version": "3.6.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-eventmanager.git", - "reference": "41f7209428f37cab9573365e361f4078209aaafa" + "reference": "3f1afbad86cd34a431fdc069f265cfe6f8fc8308" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-eventmanager/zipball/41f7209428f37cab9573365e361f4078209aaafa", - "reference": "41f7209428f37cab9573365e361f4078209aaafa", + "url": "https://api.github.com/repos/laminas/laminas-eventmanager/zipball/3f1afbad86cd34a431fdc069f265cfe6f8fc8308", + "reference": "3f1afbad86cd34a431fdc069f265cfe6f8fc8308", "shasum": "" }, "require": { - "php": "^7.4 || ~8.0.0 || ~8.1.0" + "php": "~8.0.0 || ~8.1.0 || ~8.2.0" }, "conflict": { "container-interop/container-interop": "<1.2", "zendframework/zend-eventmanager": "*" }, "require-dev": { - "laminas/laminas-coding-standard": "~2.2.1", - "laminas/laminas-stdlib": "^3.6", - "phpbench/phpbench": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5.5", - "psr/container": "^1.1.2 || ^2.0.2" + "laminas/laminas-coding-standard": "~2.4.0", + "laminas/laminas-stdlib": "^3.15", + "phpbench/phpbench": "^1.2.6", + "phpunit/phpunit": "^9.5.25", + "psalm/plugin-phpunit": "^0.17.0", + "psr/container": "^1.1.2 || ^2.0.2", + "vimeo/psalm": "^4.28" }, "suggest": { "laminas/laminas-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature", @@ -2335,44 +2336,45 @@ "type": "community_bridge" } ], - "time": "2022-04-06T21:05:17+00:00" + "time": "2022-10-11T12:46:13+00:00" }, { "name": "laminas/laminas-feed", - "version": "2.17.0", + "version": "2.19.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-feed.git", - "reference": "1ccb024ea615606ed1d676ba0fa3f22a398f3ac0" + "reference": "4d0a7a536b48f698914156ca6633104b3aef2f3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/1ccb024ea615606ed1d676ba0fa3f22a398f3ac0", - "reference": "1ccb024ea615606ed1d676ba0fa3f22a398f3ac0", + "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/4d0a7a536b48f698914156ca6633104b3aef2f3b", + "reference": "4d0a7a536b48f698914156ca6633104b3aef2f3b", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "laminas/laminas-escaper": "^2.9", + "laminas/laminas-servicemanager": "^3.14.0", "laminas/laminas-stdlib": "^3.6", - "php": "^7.3 || ~8.0.0 || ~8.1.0" + "php": "~8.0.0 || ~8.1.0 || ~8.2.0" }, "conflict": { "laminas/laminas-servicemanager": "<3.3", "zendframework/zend-feed": "*" }, "require-dev": { - "laminas/laminas-cache": "^2.7.2", - "laminas/laminas-coding-standard": "~2.2.1", - "laminas/laminas-db": "^2.13.3", - "laminas/laminas-http": "^2.15", - "laminas/laminas-servicemanager": "^3.7", - "laminas/laminas-validator": "^2.15", - "phpunit/phpunit": "^9.5.5", - "psalm/plugin-phpunit": "^0.13.0", + "laminas/laminas-cache": "^2.13.2 || ^3.6", + "laminas/laminas-cache-storage-adapter-memory": "^1.1.0 || ^2.1", + "laminas/laminas-coding-standard": "~2.4.0", + "laminas/laminas-db": "^2.15", + "laminas/laminas-http": "^2.16", + "laminas/laminas-validator": "^2.26", + "phpunit/phpunit": "^9.5.25", + "psalm/plugin-phpunit": "^0.17.0", "psr/http-message": "^1.0.1", - "vimeo/psalm": "^4.1" + "vimeo/psalm": "^4.29" }, "suggest": { "laminas/laminas-cache": "Laminas\\Cache component, for optionally caching feeds between requests", @@ -2392,11 +2394,13 @@ "license": [ "BSD-3-Clause" ], - "description": "provides functionality for consuming RSS and Atom feeds", + "description": "provides functionality for creating and consuming RSS and Atom feeds", "homepage": "https://laminas.dev", "keywords": [ + "atom", "feed", - "laminas" + "laminas", + "rss" ], "support": { "chat": "https://laminas.dev/chat", @@ -2412,7 +2416,7 @@ "type": "community_bridge" } ], - "time": "2022-03-24T10:26:04+00:00" + "time": "2022-10-14T13:40:45+00:00" }, { "name": "laminas/laminas-file", @@ -2484,37 +2488,37 @@ }, { "name": "laminas/laminas-filter", - "version": "2.17.0", + "version": "2.23.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-filter.git", - "reference": "7c78483f22e118fbc98be41dc24b39e8c17cdcae" + "reference": "41cff2f850753f0bb3fc75c5ce011fcad6aa1731" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/7c78483f22e118fbc98be41dc24b39e8c17cdcae", - "reference": "7c78483f22e118fbc98be41dc24b39e8c17cdcae", + "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/41cff2f850753f0bb3fc75c5ce011fcad6aa1731", + "reference": "41cff2f850753f0bb3fc75c5ce011fcad6aa1731", "shasum": "" }, "require": { + "ext-mbstring": "*", "laminas/laminas-servicemanager": "^3.14.0", - "laminas/laminas-stdlib": "^3.6.1", - "php": "^7.4 || ~8.0.0 || ~8.1.0" + "laminas/laminas-stdlib": "^3.13.0", + "php": "~8.0.0 || ~8.1.0 || ~8.2.0" }, "conflict": { "laminas/laminas-validator": "<2.10.1", "zendframework/zend-filter": "*" }, "require-dev": { - "laminas/laminas-coding-standard": "^2.3.0", - "laminas/laminas-crypt": "^3.5.1", + "laminas/laminas-coding-standard": "~2.4.0", + "laminas/laminas-crypt": "^3.8", "laminas/laminas-uri": "^2.9.1", "pear/archive_tar": "^1.4.14", - "phpspec/prophecy-phpunit": "^2.0.1", - "phpunit/phpunit": "^9.5.10", + "phpunit/phpunit": "^9.5.25", "psalm/plugin-phpunit": "^0.17.0", "psr/http-factory": "^1.0.1", - "vimeo/psalm": "^4.24.0" + "vimeo/psalm": "^4.28" }, "suggest": { "laminas/laminas-crypt": "Laminas\\Crypt component, for encryption filters", @@ -2558,20 +2562,20 @@ "type": "community_bridge" } ], - "time": "2022-07-17T11:58:06+00:00" + "time": "2022-10-11T10:04:14+00:00" }, { "name": "laminas/laminas-http", - "version": "2.15.1", + "version": "2.17.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-http.git", - "reference": "261f079c3dffcf6f123484db43c40e44c4bf1c79" + "reference": "ac4588d698c93b56bb7c0608d9a7537a3f057239" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-http/zipball/261f079c3dffcf6f123484db43c40e44c4bf1c79", - "reference": "261f079c3dffcf6f123484db43c40e44c4bf1c79", + "url": "https://api.github.com/repos/laminas/laminas-http/zipball/ac4588d698c93b56bb7c0608d9a7537a3f057239", + "reference": "ac4588d698c93b56bb7c0608d9a7537a3f057239", "shasum": "" }, "require": { @@ -2579,15 +2583,15 @@ "laminas/laminas-stdlib": "^3.6", "laminas/laminas-uri": "^2.9.1", "laminas/laminas-validator": "^2.15", - "php": "^7.3 || ~8.0.0 || ~8.1.0" + "php": "~8.0.0 || ~8.1.0 || ~8.2.0" }, "conflict": { "zendframework/zend-http": "*" }, "require-dev": { "ext-curl": "*", - "laminas/laminas-coding-standard": "~2.2.1", - "phpunit/phpunit": "^9.5.5" + "laminas/laminas-coding-standard": "~2.4.0", + "phpunit/phpunit": "^9.5.25" }, "suggest": { "paragonie/certainty": "For automated management of cacert.pem" @@ -2623,27 +2627,27 @@ "type": "community_bridge" } ], - "time": "2021-12-03T10:17:11+00:00" + "time": "2022-10-16T15:51:48+00:00" }, { "name": "laminas/laminas-i18n", - "version": "2.17.0", + "version": "2.19.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-i18n.git", - "reference": "7e8e63353b38792f2f360dc57cfa7187be20f182" + "reference": "ebabca3a6398fc872127bc69a51bda5afc720d67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/7e8e63353b38792f2f360dc57cfa7187be20f182", - "reference": "7e8e63353b38792f2f360dc57cfa7187be20f182", + "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/ebabca3a6398fc872127bc69a51bda5afc720d67", + "reference": "ebabca3a6398fc872127bc69a51bda5afc720d67", "shasum": "" }, "require": { "ext-intl": "*", "laminas/laminas-servicemanager": "^3.14.0", "laminas/laminas-stdlib": "^2.7 || ^3.0", - "php": "^7.4 || ~8.0.0 || ~8.1.0" + "php": "~8.0.0 || ~8.1.0 || ~8.2.0" }, "conflict": { "laminas/laminas-view": "<2.20.0", @@ -2651,19 +2655,18 @@ "zendframework/zend-i18n": "*" }, "require-dev": { - "laminas/laminas-cache": "^3.1.2", - "laminas/laminas-cache-storage-adapter-memory": "^2.0.0", - "laminas/laminas-cache-storage-deprecated-factory": "^1.0.0", - "laminas/laminas-coding-standard": "~2.3.0", - "laminas/laminas-config": "^3.4.0", + "laminas/laminas-cache": "^3.6", + "laminas/laminas-cache-storage-adapter-memory": "^2.1", + "laminas/laminas-cache-storage-deprecated-factory": "^1.0.1", + "laminas/laminas-coding-standard": "~2.4.0", + "laminas/laminas-config": "^3.7", "laminas/laminas-eventmanager": "^3.5.0", - "laminas/laminas-filter": "^2.16.0", - "laminas/laminas-validator": "^2.17.0", - "laminas/laminas-view": "^2.21.0", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5.21", + "laminas/laminas-filter": "^2.21", + "laminas/laminas-validator": "^2.25", + "laminas/laminas-view": "^2.23", + "phpunit/phpunit": "^9.5.25", "psalm/plugin-phpunit": "^0.17.0", - "vimeo/psalm": "^4.24.0" + "vimeo/psalm": "^4.28" }, "suggest": { "laminas/laminas-cache": "You should install this package to cache the translations", @@ -2710,7 +2713,7 @@ "type": "community_bridge" } ], - "time": "2022-07-27T11:23:29+00:00" + "time": "2022-10-10T15:48:56+00:00" }, { "name": "laminas/laminas-json", @@ -2775,27 +2778,27 @@ }, { "name": "laminas/laminas-loader", - "version": "2.8.0", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-loader.git", - "reference": "d0589ec9dd48365fd95ad10d1c906efd7711c16b" + "reference": "51ed9c3fa42d1098a9997571730c0cbf42d078d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/d0589ec9dd48365fd95ad10d1c906efd7711c16b", - "reference": "d0589ec9dd48365fd95ad10d1c906efd7711c16b", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/51ed9c3fa42d1098a9997571730c0cbf42d078d3", + "reference": "51ed9c3fa42d1098a9997571730c0cbf42d078d3", "shasum": "" }, "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0" + "php": "~8.0.0 || ~8.1.0 || ~8.2.0" }, "conflict": { "zendframework/zend-loader": "*" }, "require-dev": { - "laminas/laminas-coding-standard": "~2.2.1", - "phpunit/phpunit": "^9.3" + "laminas/laminas-coding-standard": "~2.4.0", + "phpunit/phpunit": "~9.5.25" }, "type": "library", "autoload": { @@ -2827,49 +2830,46 @@ "type": "community_bridge" } ], - "time": "2021-09-02T18:30:53+00:00" + "time": "2022-10-16T12:50:49+00:00" }, { "name": "laminas/laminas-mail", - "version": "2.16.0", + "version": "2.19.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-mail.git", - "reference": "1ee1a384b96c8af29ecad9b3a7adc27a150ebc49" + "reference": "edf3832c05165775589af2fc698b5f9984d4c5f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-mail/zipball/1ee1a384b96c8af29ecad9b3a7adc27a150ebc49", - "reference": "1ee1a384b96c8af29ecad9b3a7adc27a150ebc49", + "url": "https://api.github.com/repos/laminas/laminas-mail/zipball/edf3832c05165775589af2fc698b5f9984d4c5f1", + "reference": "edf3832c05165775589af2fc698b5f9984d4c5f1", "shasum": "" }, "require": { "ext-iconv": "*", - "laminas/laminas-loader": "^2.8", - "laminas/laminas-mime": "^2.9.1", - "laminas/laminas-stdlib": "^3.6", - "laminas/laminas-validator": "^2.15", - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "symfony/polyfill-intl-idn": "^1.24.0", - "symfony/polyfill-mbstring": "^1.12.0", - "webmozart/assert": "^1.10" - }, - "conflict": { - "zendframework/zend-mail": "*" - }, - "require-dev": { - "laminas/laminas-coding-standard": "~1.0.0", - "laminas/laminas-crypt": "^2.6 || ^3.4", - "laminas/laminas-db": "^2.13.3", - "laminas/laminas-servicemanager": "^3.7", - "phpunit/phpunit": "^9.5.5", - "psalm/plugin-phpunit": "^0.15.1", - "symfony/process": "^5.3.7", - "vimeo/psalm": "^4.7" + "laminas/laminas-loader": "^2.8.0", + "laminas/laminas-mime": "^2.10.0", + "laminas/laminas-stdlib": "^3.11.0", + "laminas/laminas-validator": "^2.23.0", + "php": "~8.0.0 || ~8.1.0 || ~8.2.0", + "symfony/polyfill-intl-idn": "^1.26.0", + "symfony/polyfill-mbstring": "^1.16.0", + "webmozart/assert": "^1.11.0" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.4.0", + "laminas/laminas-crypt": "^3.8.0", + "laminas/laminas-db": "^2.15.0", + "laminas/laminas-servicemanager": "^3.19", + "phpunit/phpunit": "^9.5.25", + "psalm/plugin-phpunit": "^0.17.0", + "symfony/process": "^6.0.11", + "vimeo/psalm": "^4.29" }, "suggest": { - "laminas/laminas-crypt": "Crammd5 support in SMTP Auth", - "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" + "laminas/laminas-crypt": "^3.8 Crammd5 support in SMTP Auth", + "laminas/laminas-servicemanager": "^3.16 when using SMTP to deliver messages" }, "type": "library", "extra": { @@ -2907,7 +2907,7 @@ "type": "community_bridge" } ], - "time": "2022-02-23T21:08:17+00:00" + "time": "2022-10-14T13:05:29+00:00" }, { "name": "laminas/laminas-math", @@ -2978,29 +2978,29 @@ }, { "name": "laminas/laminas-mime", - "version": "2.9.1", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-mime.git", - "reference": "72d21a1b4bb7086d4a4d7058c0abca180b209184" + "reference": "60ec04b755821c79c1987ce291b44e69f2c0831f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/72d21a1b4bb7086d4a4d7058c0abca180b209184", - "reference": "72d21a1b4bb7086d4a4d7058c0abca180b209184", + "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/60ec04b755821c79c1987ce291b44e69f2c0831f", + "reference": "60ec04b755821c79c1987ce291b44e69f2c0831f", "shasum": "" }, "require": { "laminas/laminas-stdlib": "^2.7 || ^3.0", - "php": "^7.3 || ~8.0.0 || ~8.1.0" + "php": "~8.0.0 || ~8.1.0 || ~8.2.0" }, "conflict": { "zendframework/zend-mime": "*" }, "require-dev": { - "laminas/laminas-coding-standard": "~2.2.1", - "laminas/laminas-mail": "^2.12", - "phpunit/phpunit": "^9.3" + "laminas/laminas-coding-standard": "~2.4.0", + "laminas/laminas-mail": "^2.19.0", + "phpunit/phpunit": "~9.5.25" }, "suggest": { "laminas/laminas-mail": "Laminas\\Mail component" @@ -3035,20 +3035,20 @@ "type": "community_bridge" } ], - "time": "2021-09-20T21:19:24+00:00" + "time": "2022-10-18T08:38:15+00:00" }, { "name": "laminas/laminas-modulemanager", - "version": "2.11.0", + "version": "2.14.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-modulemanager.git", - "reference": "6acf5991d10b0b38a2edb08729ed48981b2a5dad" + "reference": "fb0a2c34423f7d3321dd7c42dc5fc4db905a99ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-modulemanager/zipball/6acf5991d10b0b38a2edb08729ed48981b2a5dad", - "reference": "6acf5991d10b0b38a2edb08729ed48981b2a5dad", + "url": "https://api.github.com/repos/laminas/laminas-modulemanager/zipball/fb0a2c34423f7d3321dd7c42dc5fc4db905a99ac", + "reference": "fb0a2c34423f7d3321dd7c42dc5fc4db905a99ac", "shasum": "" }, "require": { @@ -3056,7 +3056,7 @@ "laminas/laminas-config": "^3.7", "laminas/laminas-eventmanager": "^3.4", "laminas/laminas-stdlib": "^3.6", - "php": "^7.3 || ~8.0.0 || ~8.1.0", + "php": "~8.0.0 || ~8.1.0 || ~8.2.0", "webimpress/safe-writer": "^1.0.2 || ^2.1" }, "conflict": { @@ -3064,10 +3064,12 @@ }, "require-dev": { "laminas/laminas-coding-standard": "^2.3", - "laminas/laminas-loader": "^2.8", - "laminas/laminas-mvc": "^3.1.1", - "laminas/laminas-servicemanager": "^3.7", - "phpunit/phpunit": "^9.5.5" + "laminas/laminas-loader": "^2.9.0", + "laminas/laminas-mvc": "^3.5.0", + "laminas/laminas-servicemanager": "^3.19.0", + "phpunit/phpunit": "^9.5.25", + "psalm/plugin-phpunit": "^0.17.0", + "vimeo/psalm": "^4.29" }, "suggest": { "laminas/laminas-console": "Laminas\\Console component", @@ -3105,20 +3107,20 @@ "type": "community_bridge" } ], - "time": "2021-10-13T17:05:17+00:00" + "time": "2022-10-28T09:21:04+00:00" }, { "name": "laminas/laminas-mvc", - "version": "3.3.3", + "version": "3.5.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-mvc.git", - "reference": "7ff2bfbe64048aa83c6d1c7edcbab849123f0150" + "reference": "111e08a9c27274af570260c83abe77204ccf3366" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-mvc/zipball/7ff2bfbe64048aa83c6d1c7edcbab849123f0150", - "reference": "7ff2bfbe64048aa83c6d1c7edcbab849123f0150", + "url": "https://api.github.com/repos/laminas/laminas-mvc/zipball/111e08a9c27274af570260c83abe77204ccf3366", + "reference": "111e08a9c27274af570260c83abe77204ccf3366", "shasum": "" }, "require": { @@ -3130,19 +3132,20 @@ "laminas/laminas-servicemanager": "^3.7", "laminas/laminas-stdlib": "^3.6", "laminas/laminas-view": "^2.14", - "php": "^7.3 || ~8.0.0 || ~8.1.0" + "php": "~8.0.0 || ~8.1.0 || ~8.2.0" }, "conflict": { "zendframework/zend-mvc": "*" }, "require-dev": { "http-interop/http-middleware": "^0.4.1", - "laminas/laminas-coding-standard": "^1.0.0", + "laminas/laminas-coding-standard": "^2.4.0", "laminas/laminas-json": "^3.3", - "laminas/laminas-psr7bridge": "^1.0", + "laminas/laminas-psr7bridge": "^1.8", "laminas/laminas-stratigility": ">=2.0.1 <2.2", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5.5" + "phpspec/prophecy": "^1.15.0", + "phpspec/prophecy-phpunit": "^2.0.1", + "phpunit/phpunit": "^9.5.25" }, "suggest": { "laminas/laminas-json": "(^2.6.1 || ^3.0) To auto-deserialize JSON body content in AbstractRestfulController extensions, when json_decode is unavailable", @@ -3187,7 +3190,7 @@ "type": "community_bridge" } ], - "time": "2022-02-21T20:21:58+00:00" + "time": "2022-10-21T14:19:57+00:00" }, { "name": "laminas/laminas-oauth", @@ -3253,31 +3256,31 @@ }, { "name": "laminas/laminas-permissions-acl", - "version": "2.10.0", + "version": "2.12.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-permissions-acl.git", - "reference": "e927ae0a3001655fea97eb240eeea9d10638e82f" + "reference": "0d88f430953fbcbce382f09090db28905b90d60f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-permissions-acl/zipball/e927ae0a3001655fea97eb240eeea9d10638e82f", - "reference": "e927ae0a3001655fea97eb240eeea9d10638e82f", + "url": "https://api.github.com/repos/laminas/laminas-permissions-acl/zipball/0d88f430953fbcbce382f09090db28905b90d60f", + "reference": "0d88f430953fbcbce382f09090db28905b90d60f", "shasum": "" }, "require": { - "php": "^7.4 || ~8.0.0 || ~8.1.0" + "php": "~8.0.0 || ~8.1.0 || ~8.2.0" }, "conflict": { "laminas/laminas-servicemanager": "<3.0", "zendframework/zend-permissions-acl": "*" }, "require-dev": { - "laminas/laminas-coding-standard": "~2.3.0", - "laminas/laminas-servicemanager": "^3.15.1", - "phpunit/phpunit": "^9.5.0", + "laminas/laminas-coding-standard": "~2.4.0", + "laminas/laminas-servicemanager": "^3.19", + "phpunit/phpunit": "^9.5.25", "psalm/plugin-phpunit": "^0.17.0", - "vimeo/psalm": "^4.24.0" + "vimeo/psalm": "^4.29" }, "suggest": { "laminas/laminas-servicemanager": "To support Laminas\\Permissions\\Acl\\Assertion\\AssertionManager plugin manager usage" @@ -3312,7 +3315,7 @@ "type": "community_bridge" } ], - "time": "2022-07-21T09:23:39+00:00" + "time": "2022-10-17T04:26:35+00:00" }, { "name": "laminas/laminas-recaptcha", @@ -3514,21 +3517,21 @@ }, { "name": "laminas/laminas-servicemanager", - "version": "3.16.0", + "version": "3.19.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-servicemanager.git", - "reference": "863c66733740cd36ebf5e700f4258ef2c68a2a24" + "reference": "ed160729bb8721127efdaac799f9a298963345b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/863c66733740cd36ebf5e700f4258ef2c68a2a24", - "reference": "863c66733740cd36ebf5e700f4258ef2c68a2a24", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/ed160729bb8721127efdaac799f9a298963345b1", + "reference": "ed160729bb8721127efdaac799f9a298963345b1", "shasum": "" }, "require": { "laminas/laminas-stdlib": "^3.2.1", - "php": "~7.4.0 || ~8.0.0 || ~8.1.0", + "php": "~8.0.0 || ~8.1.0 || ~8.2.0", "psr/container": "^1.0" }, "conflict": { @@ -3544,17 +3547,16 @@ "container-interop/container-interop": "^1.2.0" }, "require-dev": { - "composer/package-versions-deprecated": "^1.0", - "laminas/laminas-coding-standard": "~2.3.0", + "composer/package-versions-deprecated": "^1.11.99.5", + "laminas/laminas-coding-standard": "~2.4.0", "laminas/laminas-container-config-test": "^0.7", - "laminas/laminas-dependency-plugin": "^2.1.2", - "mikey179/vfsstream": "^1.6.10@alpha", - "ocramius/proxy-manager": "^2.11", - "phpbench/phpbench": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5.5", + "laminas/laminas-dependency-plugin": "^2.2", + "mikey179/vfsstream": "^1.6.11@alpha", + "ocramius/proxy-manager": "^2.14.1", + "phpbench/phpbench": "^1.2.6", + "phpunit/phpunit": "^9.5.25", "psalm/plugin-phpunit": "^0.17.0", - "vimeo/psalm": "^4.8" + "vimeo/psalm": "^4.28" }, "suggest": { "ocramius/proxy-manager": "ProxyManager ^2.1.1 to handle lazy initialization of services" @@ -3601,7 +3603,7 @@ "type": "community_bridge" } ], - "time": "2022-07-27T14:58:17+00:00" + "time": "2022-10-10T20:59:22+00:00" }, { "name": "laminas/laminas-session", @@ -3755,30 +3757,30 @@ }, { "name": "laminas/laminas-stdlib", - "version": "3.11.0", + "version": "3.15.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-stdlib.git", - "reference": "aad7d2b11ba0069ba0d9b40f6dde3c2fa664b57f" + "reference": "63b66bd4b696f024f42616b9d95cdb10e5109c27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/aad7d2b11ba0069ba0d9b40f6dde3c2fa664b57f", - "reference": "aad7d2b11ba0069ba0d9b40f6dde3c2fa664b57f", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/63b66bd4b696f024f42616b9d95cdb10e5109c27", + "reference": "63b66bd4b696f024f42616b9d95cdb10e5109c27", "shasum": "" }, "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0" + "php": "~8.0.0 || ~8.1.0 || ~8.2.0" }, "conflict": { "zendframework/zend-stdlib": "*" }, "require-dev": { - "laminas/laminas-coding-standard": "~2.3.0", - "phpbench/phpbench": "^1.0", - "phpunit/phpunit": "^9.3.7", + "laminas/laminas-coding-standard": "^2.4.0", + "phpbench/phpbench": "^1.2.6", + "phpunit/phpunit": "^9.5.25", "psalm/plugin-phpunit": "^0.17.0", - "vimeo/psalm": "^4.7" + "vimeo/psalm": "^4.28" }, "type": "library", "autoload": { @@ -3810,7 +3812,7 @@ "type": "community_bridge" } ], - "time": "2022-07-27T12:28:58+00:00" + "time": "2022-10-10T19:10:24+00:00" }, { "name": "laminas/laminas-text", @@ -3872,29 +3874,29 @@ }, { "name": "laminas/laminas-uri", - "version": "2.9.1", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-uri.git", - "reference": "7e837dc15c8fd3949df7d1213246fd7c8640032b" + "reference": "663b050294945c7345cc3a61f3ca661d5f9e1f80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-uri/zipball/7e837dc15c8fd3949df7d1213246fd7c8640032b", - "reference": "7e837dc15c8fd3949df7d1213246fd7c8640032b", + "url": "https://api.github.com/repos/laminas/laminas-uri/zipball/663b050294945c7345cc3a61f3ca661d5f9e1f80", + "reference": "663b050294945c7345cc3a61f3ca661d5f9e1f80", "shasum": "" }, "require": { "laminas/laminas-escaper": "^2.9", "laminas/laminas-validator": "^2.15", - "php": "^7.3 || ~8.0.0 || ~8.1.0" + "php": "~8.0.0 || ~8.1.0 || ~8.2.0" }, "conflict": { "zendframework/zend-uri": "*" }, "require-dev": { - "laminas/laminas-coding-standard": "~2.2.1", - "phpunit/phpunit": "^9.5.5" + "laminas/laminas-coding-standard": "~2.4.0", + "phpunit/phpunit": "^9.5.25" }, "type": "library", "autoload": { @@ -3926,45 +3928,44 @@ "type": "community_bridge" } ], - "time": "2021-09-09T18:37:15+00:00" + "time": "2022-10-16T15:02:45+00:00" }, { "name": "laminas/laminas-validator", - "version": "2.23.0", + "version": "2.26.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-validator.git", - "reference": "6d61b6cc3b222f13807a18d9247cdfb084958b03" + "reference": "a995b21d18c63cd1f5d123d0d2cd31a1c2d828dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/6d61b6cc3b222f13807a18d9247cdfb084958b03", - "reference": "6d61b6cc3b222f13807a18d9247cdfb084958b03", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/a995b21d18c63cd1f5d123d0d2cd31a1c2d828dc", + "reference": "a995b21d18c63cd1f5d123d0d2cd31a1c2d828dc", "shasum": "" }, "require": { "laminas/laminas-servicemanager": "^3.12.0", - "laminas/laminas-stdlib": "^3.10", - "php": "^7.4 || ~8.0.0 || ~8.1.0" + "laminas/laminas-stdlib": "^3.13", + "php": "~8.0.0 || ~8.1.0 || ~8.2.0" }, "conflict": { "zendframework/zend-validator": "*" }, "require-dev": { - "laminas/laminas-coding-standard": "~2.3.0", - "laminas/laminas-db": "^2.7", - "laminas/laminas-filter": "^2.14.0", - "laminas/laminas-http": "^2.14.2", - "laminas/laminas-i18n": "^2.15.0", - "laminas/laminas-session": "^2.12.1", + "laminas/laminas-coding-standard": "^2.4.0", + "laminas/laminas-db": "^2.15.0", + "laminas/laminas-filter": "^2.22", + "laminas/laminas-http": "^2.16.0", + "laminas/laminas-i18n": "^2.19", + "laminas/laminas-session": "^2.13.0", "laminas/laminas-uri": "^2.9.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5.21", + "phpunit/phpunit": "^9.5.25", "psalm/plugin-phpunit": "^0.17.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "vimeo/psalm": "^4.24.0" + "psr/http-client": "^1.0.1", + "psr/http-factory": "^1.0.1", + "psr/http-message": "^1.0.1", + "vimeo/psalm": "^4.28" }, "suggest": { "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", @@ -4012,7 +4013,7 @@ "type": "community_bridge" } ], - "time": "2022-07-27T19:17:59+00:00" + "time": "2022-10-11T12:58:36+00:00" }, { "name": "laminas/laminas-view", @@ -4595,16 +4596,16 @@ }, { "name": "monolog/monolog", - "version": "2.7.0", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "5579edf28aee1190a798bfa5be8bc16c563bd524" + "reference": "720488632c590286b88b80e62aa3d3d551ad4a50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/5579edf28aee1190a798bfa5be8bc16c563bd524", - "reference": "5579edf28aee1190a798bfa5be8bc16c563bd524", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/720488632c590286b88b80e62aa3d3d551ad4a50", + "reference": "720488632c590286b88b80e62aa3d3d551ad4a50", "shasum": "" }, "require": { @@ -4624,11 +4625,10 @@ "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", - "php-console/php-console": "^3.1.3", "phpspec/prophecy": "^1.15", "phpstan/phpstan": "^0.12.91", "phpunit/phpunit": "^8.5.14", - "predis/predis": "^1.1", + "predis/predis": "^1.1 || ^2.0", "rollbar/rollbar": "^1.3 || ^2 || ^3", "ruflin/elastica": "^7", "swiftmailer/swiftmailer": "^5.3|^6.0", @@ -4648,7 +4648,6 @@ "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", "rollbar/rollbar": "Allow sending log messages to Rollbar", "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, @@ -4683,7 +4682,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.7.0" + "source": "https://github.com/Seldaek/monolog/tree/2.8.0" }, "funding": [ { @@ -4695,7 +4694,7 @@ "type": "tidelift" } ], - "time": "2022-06-09T08:59:12+00:00" + "time": "2022-07-24T11:55:47+00:00" }, { "name": "mtdowling/jmespath.php", @@ -4816,16 +4815,16 @@ }, { "name": "opensearch-project/opensearch-php", - "version": "1.0.2", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/opensearch-project/opensearch-php.git", - "reference": "d54af5ff2167bee3eb68d02e210283314ef74712" + "reference": "565c17e0ac1e062f4a6edfeb9745e9deb93ffbeb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opensearch-project/opensearch-php/zipball/d54af5ff2167bee3eb68d02e210283314ef74712", - "reference": "d54af5ff2167bee3eb68d02e210283314ef74712", + "url": "https://api.github.com/repos/opensearch-project/opensearch-php/zipball/565c17e0ac1e062f4a6edfeb9745e9deb93ffbeb", + "reference": "565c17e0ac1e062f4a6edfeb9745e9deb93ffbeb", "shasum": "" }, "require": { @@ -4874,9 +4873,9 @@ ], "support": { "issues": "https://github.com/opensearch-project/opensearch-php/issues", - "source": "https://github.com/opensearch-project/opensearch-php/tree/1.0.2" + "source": "https://github.com/opensearch-project/opensearch-php/tree/2.0.0" }, - "time": "2022-02-08T08:37:31+00:00" + "time": "2022-05-26T19:17:49+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -6299,16 +6298,16 @@ }, { "name": "symfony/config", - "version": "v5.4.9", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "8f551fe22672ac7ab2c95fe46d899f960ed4d979" + "reference": "ec79e03125c1d2477e43dde8528535d90cc78379" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/8f551fe22672ac7ab2c95fe46d899f960ed4d979", - "reference": "8f551fe22672ac7ab2c95fe46d899f960ed4d979", + "url": "https://api.github.com/repos/symfony/config/zipball/ec79e03125c1d2477e43dde8528535d90cc78379", + "reference": "ec79e03125c1d2477e43dde8528535d90cc78379", "shasum": "" }, "require": { @@ -6358,7 +6357,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v5.4.9" + "source": "https://github.com/symfony/config/tree/v5.4.11" }, "funding": [ { @@ -6374,7 +6373,7 @@ "type": "tidelift" } ], - "time": "2022-05-17T10:39:36+00:00" + "time": "2022-07-20T13:00:38+00:00" }, { "name": "symfony/console", @@ -6543,16 +6542,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v5.4.10", + "version": "v5.4.13", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "88d1c0d38c2e60f757fa11d89cfc885f0b7f5171" + "reference": "24cf522668845391c0542bc1de496366072a6d0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/88d1c0d38c2e60f757fa11d89cfc885f0b7f5171", - "reference": "88d1c0d38c2e60f757fa11d89cfc885f0b7f5171", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/24cf522668845391c0542bc1de496366072a6d0e", + "reference": "24cf522668845391c0542bc1de496366072a6d0e", "shasum": "" }, "require": { @@ -6612,7 +6611,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v5.4.10" + "source": "https://github.com/symfony/dependency-injection/tree/v5.4.13" }, "funding": [ { @@ -6628,7 +6627,7 @@ "type": "tidelift" } ], - "time": "2022-06-26T13:00:04+00:00" + "time": "2022-08-30T19:10:13+00:00" }, { "name": "symfony/deprecation-contracts", @@ -6855,20 +6854,20 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.5.2", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1" + "reference": "02ff5eea2f453731cfbc6bc215e456b781480448" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1", - "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/02ff5eea2f453731cfbc6bc215e456b781480448", + "reference": "02ff5eea2f453731cfbc6bc215e456b781480448", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, "suggest": { @@ -6877,7 +6876,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.1-dev" }, "thanks": { "name": "symfony/contracts", @@ -6914,7 +6913,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.1.1" }, "funding": [ { @@ -6930,7 +6929,7 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-02-25T11:15:52+00:00" }, { "name": "symfony/filesystem", @@ -10546,22 +10545,22 @@ }, { "name": "magento/magento-coding-standard", - "version": "26", + "version": "27", "source": { "type": "git", "url": "https://github.com/magento/magento-coding-standard.git", - "reference": "0263b8952b509848ffdab1ea9ab738f48450677c" + "reference": "097bda3e015f35dc7c2efc0b8c7a7d8dfc158a63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento-coding-standard/zipball/0263b8952b509848ffdab1ea9ab738f48450677c", - "reference": "0263b8952b509848ffdab1ea9ab738f48450677c", + "url": "https://api.github.com/repos/magento/magento-coding-standard/zipball/097bda3e015f35dc7c2efc0b8c7a7d8dfc158a63", + "reference": "097bda3e015f35dc7c2efc0b8c7a7d8dfc158a63", "shasum": "" }, "require": { "ext-dom": "*", "ext-simplexml": "*", - "php": ">=7.3", + "php": "^8.1||^8.2", "phpcompatibility/php-compatibility": "^9.3", "rector/rector": "^0.13.0", "squizlabs/php_codesniffer": "^3.6.1", @@ -10588,9 +10587,9 @@ "description": "A set of Magento specific PHP CodeSniffer rules.", "support": { "issues": "https://github.com/magento/magento-coding-standard/issues", - "source": "https://github.com/magento/magento-coding-standard/tree/v26" + "source": "https://github.com/magento/magento-coding-standard/tree/v27" }, - "time": "2022-10-04T10:45:15+00:00" + "time": "2022-10-17T15:19:28+00:00" }, { "name": "magento/magento2-functional-testing-framework", @@ -10795,16 +10794,16 @@ }, { "name": "pdepend/pdepend", - "version": "2.10.3", + "version": "2.12.1", "source": { "type": "git", "url": "https://github.com/pdepend/pdepend.git", - "reference": "da3166a06b4a89915920a42444f707122a1584c9" + "reference": "7a892d56ceafd804b4a2ecc85184640937ce9e84" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pdepend/pdepend/zipball/da3166a06b4a89915920a42444f707122a1584c9", - "reference": "da3166a06b4a89915920a42444f707122a1584c9", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/7a892d56ceafd804b4a2ecc85184640937ce9e84", + "reference": "7a892d56ceafd804b4a2ecc85184640937ce9e84", "shasum": "" }, "require": { @@ -10840,7 +10839,7 @@ "description": "Official version of pdepend to be handled with Composer", "support": { "issues": "https://github.com/pdepend/pdepend/issues", - "source": "https://github.com/pdepend/pdepend/tree/2.10.3" + "source": "https://github.com/pdepend/pdepend/tree/2.12.1" }, "funding": [ { @@ -10848,7 +10847,7 @@ "type": "tidelift" } ], - "time": "2022-02-23T07:53:09+00:00" + "time": "2022-09-08T19:30:37+00:00" }, { "name": "phar-io/manifest", @@ -11011,6 +11010,7 @@ "issues": "https://github.com/PHP-CS-Fixer/diff/issues", "source": "https://github.com/PHP-CS-Fixer/diff/tree/v2.0.2" }, + "abandoned": true, "time": "2020-10-14T08:32:19+00:00" }, { @@ -11302,22 +11302,22 @@ }, { "name": "phpmd/phpmd", - "version": "2.12.0", + "version": "2.13.0", "source": { "type": "git", "url": "https://github.com/phpmd/phpmd.git", - "reference": "c0b678ba71902f539c27c14332aa0ddcf14388ec" + "reference": "dad0228156856b3ad959992f9748514fa943f3e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmd/phpmd/zipball/c0b678ba71902f539c27c14332aa0ddcf14388ec", - "reference": "c0b678ba71902f539c27c14332aa0ddcf14388ec", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/dad0228156856b3ad959992f9748514fa943f3e3", + "reference": "dad0228156856b3ad959992f9748514fa943f3e3", "shasum": "" }, "require": { "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0", "ext-xml": "*", - "pdepend/pdepend": "^2.10.3", + "pdepend/pdepend": "^2.12.1", "php": ">=5.3.9" }, "require-dev": { @@ -11373,7 +11373,7 @@ "support": { "irc": "irc://irc.freenode.org/phpmd", "issues": "https://github.com/phpmd/phpmd/issues", - "source": "https://github.com/phpmd/phpmd/tree/2.12.0" + "source": "https://github.com/phpmd/phpmd/tree/2.13.0" }, "funding": [ { @@ -11381,7 +11381,7 @@ "type": "tidelift" } ], - "time": "2022-03-24T13:33:01+00:00" + "time": "2022-09-10T08:44:15+00:00" }, { "name": "phpspec/prophecy", @@ -11976,20 +11976,20 @@ }, { "name": "psr/cache", - "version": "1.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { @@ -12009,7 +12009,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for caching libraries", @@ -12019,9 +12019,9 @@ "psr-6" ], "support": { - "source": "https://github.com/php-fig/cache/tree/master" + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "time": "2016-08-06T20:24:11+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { "name": "rector/rector", @@ -13700,7 +13700,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "ext-bcmath": "*", "ext-ctype": "*", "ext-curl": "*", @@ -13720,5 +13720,5 @@ "lib-libxml": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.2.0" } diff --git a/dev/tests/api-functional/testsuite/Magento/Customer/Api/AccountManagementRevokeCustomerTokenTest.php b/dev/tests/api-functional/testsuite/Magento/Customer/Api/AccountManagementRevokeCustomerTokenTest.php new file mode 100644 index 0000000000000..66ed169d92b2f --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/Customer/Api/AccountManagementRevokeCustomerTokenTest.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Customer\Api; + +use Exception; +use Magento\Framework\Exception\AuthenticationException; +use Magento\Framework\Webapi\Rest\Request; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\ObjectManager; +use Magento\TestFramework\TestCase\WebapiAbstract; + +/** + * Test class for Magento\Integration\Api\CustomerTokenServiceInterface + */ +class AccountManagementRevokeCustomerTokenTest extends WebapiAbstract +{ + public const RESOURCE_PATH = '/V1/integration/customer/revoke-customer-token'; + public const INTEGRATION_SERVICE = 'integrationCustomerTokenServiceV1'; + public const SERVICE_VERSION = 'V1'; + + /** + * Test token revoking for authenticated customer + * + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testRevokeCustomerToken(): void + { + $token = $this->getCustomerToken(); + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => Request::HTTP_METHOD_POST, + 'token' => $token, + ], + 'soap' => [ + 'service' => self::INTEGRATION_SERVICE, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::INTEGRATION_SERVICE . 'RevokeCustomerAccessToken', + 'token' => $token, + ] + ]; + + $requestData = []; + if (TESTS_WEB_API_ADAPTER === self::ADAPTER_SOAP) { + $requestData['customerId'] = 0; + } + + $this->assertTrue($this->_webApiCall($serviceInfo, $requestData)); + } + + /** + * @return string + * + * @throws AuthenticationException + */ + private function getCustomerToken(): string + { + $userName = 'customer@example.com'; + $password = 'password'; + + /** @var CustomerTokenServiceInterface $customerTokenService */ + $customerTokenService = ObjectManager::getInstance()->get(CustomerTokenServiceInterface::class); + + return $customerTokenService->createCustomerAccessToken($userName, $password); + } + + /** + * Test token revoking for guest customer + */ + public function testRevokeCustomerTokenForGuestCustomer(): void + { + $this->expectException(Exception::class); + $requestData = []; + + if (TESTS_WEB_API_ADAPTER === self::ADAPTER_SOAP) { + $requestData['customerId'] = 0; + } + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::INTEGRATION_SERVICE, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::INTEGRATION_SERVICE . 'RevokeCustomerAccessToken', + ] + ]; + + $this->_webApiCall($serviceInfo, $requestData); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/InvoiceTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/InvoiceTest.php index 8b18d4bd07d1b..5d4495e9a5c36 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/InvoiceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/InvoiceTest.php @@ -7,9 +7,23 @@ namespace Magento\GraphQl\Sales; +use Magento\Catalog\Test\Fixture\Product as ProductFixture; +use Magento\Checkout\Test\Fixture\PlaceOrder as PlaceOrderFixture; +use Magento\Checkout\Test\Fixture\SetBillingAddress as SetBillingAddressFixture; +use Magento\Checkout\Test\Fixture\SetDeliveryMethod as SetDeliveryMethodFixture; +use Magento\Checkout\Test\Fixture\SetGuestEmail as SetGuestEmailFixture; +use Magento\Checkout\Test\Fixture\SetPaymentMethod as SetPaymentMethodFixture; +use Magento\Checkout\Test\Fixture\SetShippingAddress as SetShippingAddressFixture; +use Magento\Customer\Test\Fixture\Customer; use Magento\Framework\Registry; +use Magento\Quote\Test\Fixture\AddProductToCart as AddProductToCartFixture; +use Magento\Quote\Test\Fixture\CustomerCart; +use Magento\Quote\Test\Fixture\GuestCart as GuestCartFixture; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\ResourceModel\Order\Collection; +use Magento\Sales\Test\Fixture\Invoice as InvoiceFixture; +use Magento\Sales\Test\Fixture\InvoiceComment as InvoiceCommentFixture ; +use Magento\TestFramework\Fixture\DataFixture; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\GraphQl\GetCustomerAuthenticationHeader; @@ -410,6 +424,64 @@ public function testPartialInvoiceForCustomerWithTaxesAndDiscounts() $this->deleteOrder(); } + #[ + DataFixture(Customer::class, ['email' => 'customer@search.example.com'], as: 'customer'), + DataFixture(ProductFixture::class, as: 'product'), + DataFixture(CustomerCart::class, ['customer_id' => '$customer.id$'], as: 'cart'), + DataFixture(AddProductToCartFixture::class, ['cart_id' => '$cart.id$', 'product_id' => '$product.id$']), + DataFixture(SetBillingAddressFixture::class, ['cart_id' => '$cart.id$']), + DataFixture(SetShippingAddressFixture::class, ['cart_id' => '$cart.id$']), + DataFixture(SetGuestEmailFixture::class, ['cart_id' => '$cart.id$']), + DataFixture(SetDeliveryMethodFixture::class, ['cart_id' => '$cart.id$']), + DataFixture(SetPaymentMethodFixture::class, ['cart_id' => '$cart.id$']), + DataFixture(PlaceOrderFixture::class, ['cart_id' => '$cart.id$'], 'order'), + DataFixture(InvoiceFixture::class, ['order_id' => '$order.id$'], 'invoice'), + DataFixture(InvoiceCommentFixture::class, [ + 'parent_id' => '$invoice.id$', + 'comment' => 'visible_comment', + 'is_visible_on_front' => 1, + ]), + DataFixture(InvoiceCommentFixture::class, [ + 'parent_id' => '$invoice.id$', + 'comment' => 'non_visible_comment', + 'is_visible_on_front' => 0, + ]), + ] + public function testInvoiceCommentsQuery() + { + $query = + <<<QUERY +{ + customer { + orders { + items { + invoices { + comments { + message + timestamp + } + } + } + } + } +} +QUERY; + + $currentEmail = 'customer@search.example.com'; + $currentPassword = 'password'; + $response = $this->graphQlQuery( + $query, + [], + '', + $this->customerAuthenticationHeader->execute($currentEmail, $currentPassword) + ); + + $invoice = $response['customer']['orders']['items'][0]['invoices'][0]; + $this->assertCount(1, $invoice['comments']); + $this->assertEquals('visible_comment', $invoice['comments'][0]['message']); + $this->assertNotEmpty($invoice['comments'][0]['timestamp']); + } + /** * Prepare invoice for the order * diff --git a/dev/tests/integration/_files/Magento/TestModuleCatalogSearch/Model/SearchEngineVersionReader.php b/dev/tests/integration/_files/Magento/TestModuleCatalogSearch/Model/SearchEngineVersionReader.php index 3c49b2ed63ff1..b565caae4e3fc 100644 --- a/dev/tests/integration/_files/Magento/TestModuleCatalogSearch/Model/SearchEngineVersionReader.php +++ b/dev/tests/integration/_files/Magento/TestModuleCatalogSearch/Model/SearchEngineVersionReader.php @@ -31,6 +31,9 @@ class SearchEngineVersionReader public function getFullVersion(): string { $version = $this->getVersion(); + if (strtolower($this->getDistribution()) == 'opensearch') { + $version = 1; + } return $this->getDistribution() . ($version === 1 ? '' : $version); } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/SortingTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/SortingTest.php index 3bfd90cd35a31..d690b68a3123f 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/SortingTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/SortingTest.php @@ -395,7 +395,6 @@ private function updateCategorySortBy( * @magentoDataFixture Magento/Catalog/_files/products_with_not_empty_layered_navigation_attribute.php * @magentoDataFixture Magento/Framework/Search/_files/product_configurable_with_out-of-stock_child.php * @magentoConfigFixture current_store cataloginventory/options/show_out_of_stock 1 - * @magentoConfigFixture default/catalog/search/engine elasticsearch7 * @dataProvider productListWithOutOfStockSortOrderDataProvider * @param string $sortBy * @param string $direction @@ -416,7 +415,6 @@ public function testProductListOutOfStockSortOrderWithElasticsearch( * @magentoDataFixture Magento/Catalog/_files/products_with_not_empty_layered_navigation_attribute.php * @magentoDataFixture Magento/Framework/Search/_files/product_configurable_with_out-of-stock_child.php * @magentoConfigFixture current_store cataloginventory/options/show_out_of_stock 1 - * @magentoConfigFixture default/catalog/search/engine mysql * @dataProvider productListWithOutOfStockSortOrderDataProvider * @param string $sortBy * @param string $direction diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest/ProductOptionsTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest/ProductOptionsTest.php index 050d125b3c944..121962a692d83 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest/ProductOptionsTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest/ProductOptionsTest.php @@ -7,10 +7,19 @@ namespace Magento\CatalogImportExport\Model\Import\ProductTest; +use Magento\Catalog\Api\Data\ProductCustomOptionInterface; use Magento\Catalog\Api\ProductCustomOptionRepositoryInterface; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Helper\Data as CatalogConfig; +use Magento\Catalog\Test\Fixture\Product as ProductFixture; use Magento\CatalogImportExport\Model\Import\ProductTestBase; +use Magento\ImportExport\Helper\Data as ImportExportConfig; +use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Test\Fixture\Store as StoreFixture; +use Magento\TestFramework\Fixture\AppIsolation; +use Magento\TestFramework\Fixture\Config; +use Magento\TestFramework\Fixture\DataFixture; /** * Integration test for \Magento\CatalogImportExport\Model\Import\Product class. @@ -140,99 +149,142 @@ public function testSaveCustomOptions(string $importFile, string $sku, int $expe /** * Tests adding of custom options with multiple store views * - * @magentoConfigFixture current_store catalog/price/scope 1 - * @magentoDataFixture Magento/Store/_files/core_second_third_fixturestore.php + * @dataProvider saveCustomOptionsWithMultipleStoreViewsDataProvider + * @param string $importFile + * @param array $expected */ - public function testSaveCustomOptionsWithMultipleStoreViews() - { + #[ + AppIsolation(true), + Config(CatalogConfig::XML_PATH_PRICE_SCOPE, CatalogConfig::PRICE_SCOPE_WEBSITE, ScopeInterface::SCOPE_STORE), + DataFixture(StoreFixture::class, ['code' => 'secondstore']), + DataFixture( + ProductFixture::class, + [ + 'sku' => 'simple2', + 'options' => [ + [ + 'type' => ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN, + 'title' => 'Option 1', + 'values' => [ + [ + 'title' => 'Option 1 Value 1', + 'price' => 2.5, + 'sku' => 'option1value1', + ], + [ + 'title' => 'Option 1 Value 2', + 'price' => 3, + 'sku' => 'option1value2', + ], + ] + ] + ] + ] + ), + ] + public function testSaveCustomOptionsWithMultipleStoreViews( + string $importFile, + array $expected + ) { + $expected = $this->getFullExpectedOptions($expected); $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); /** @var StoreManagerInterface $storeManager */ $storeManager = $objectManager->get(StoreManagerInterface::class); - $storeCodes = [ - 'admin', - 'default', - 'secondstore', - ]; - /** @var StoreManagerInterface $storeManager */ - $importFile = 'product_with_custom_options_and_multiple_store_views.csv'; - $sku = 'simple'; $pathToFile = __DIR__ . '/../_files/' . $importFile; $importModel = $this->createImportModel($pathToFile); $errors = $importModel->validateData(); $this->assertTrue($errors->getErrorsCount() == 0, 'Import File Validation Failed'); $importModel->importData(); /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ - $productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $productRepository = $objectManager->get( \Magento\Catalog\Api\ProductRepositoryInterface::class ); - foreach ($storeCodes as $storeCode) { - $storeManager->setCurrentStore($storeCode); - $product = $productRepository->get($sku); - $options = $product->getOptionInstance()->getProductOptions($product); - $expectedData = $this->getExpectedOptionsData($pathToFile, $storeCode); - $expectedData = $this->mergeWithExistingData($expectedData, $options); - $actualData = $this->getActualOptionsData($options); - // assert of equal type+titles - $expectedOptions = $expectedData['options']; - // we need to save key values - $actualOptions = $actualData['options']; - sort($expectedOptions); - sort($actualOptions); - $this->assertEquals( - $expectedOptions, - $actualOptions, - 'Expected and actual options arrays does not match' - ); - - // assert of options data - $this->assertCount( - count($expectedData['data']), - $actualData['data'], - 'Expected and actual data count does not match' - ); - $this->assertCount( - count($expectedData['values']), - $actualData['values'], - 'Expected and actual values count does not match' - ); - - foreach ($expectedData['options'] as $expectedId => $expectedOption) { - $elementExist = false; - // find value in actual options and values - foreach ($actualData['options'] as $actualId => $actualOption) { - if ($actualOption == $expectedOption) { - $elementExist = true; - $this->assertEquals( - $expectedData['data'][$expectedId], - $actualData['data'][$actualId], - 'Expected data does not match actual data' - ); - if (array_key_exists($expectedId, $expectedData['values'])) { - $this->assertEquals( - $expectedData['values'][$expectedId], - $actualData['values'][$actualId], - 'Expected values does not match actual data' - ); - } - unset($actualData['options'][$actualId]); - // remove value in case of duplicating key values - break; + $actual = []; + foreach ($expected as $sku => $storesData) { + foreach (array_keys($storesData) as $storeCode) { + $product = $productRepository->get($sku, false, $storeManager->getStore($storeCode)->getId(), true); + $options = $product->getOptionInstance()->getProductOptions($product); + $actual[$sku][$storeCode] = []; + /** @var $option \Magento\Catalog\Model\Product\Option */ + foreach ($options as $option) { + $optionData = [ + 'type' => $option->getType(), + 'title' => $option->getTitle() + ]; + $optionData += $this->getOptionData($option); + if (in_array($option->getType(), $this->specificTypes)) { + $optionData['values'] = $this->getOptionValues($option); } + $actual[$sku][$storeCode][] = $optionData; } - $this->assertTrue($elementExist, 'Element must exist.'); } + } + + $this->assertEquals($expected, $actual); + + // Make sure that after importing existing options again, option IDs and option value IDs are not changed + $expectedIds = []; + $actualIds = []; + foreach (array_keys($expected) as $sku) { + $expectedIds[$sku] = $this->getCustomOptionValues($sku); + } + $importModel = $this->createImportModel($pathToFile); + $importModel->validateData(); + $importModel->importData(); + foreach (array_keys($expected) as $sku) { + $actualIds[$sku] = $this->getCustomOptionValues($sku); - // Make sure that after importing existing options again, option IDs and option value IDs are not changed - $customOptionValues = $this->getCustomOptionValues($sku); - $importModel = $this->createImportModel($pathToFile); - $importModel->validateData(); - $importModel->importData(); - $this->assertEquals( - $customOptionValues, - $this->getCustomOptionValues($sku), - 'Option IDs changed after second import' - ); } + + $this->assertEquals( + $expectedIds, + $actualIds, + 'Option IDs changed after second import' + ); + } + + /** + * Tests adding of custom options with multiple store views across bunches + * + * @dataProvider saveCustomOptionsWithMultipleStoreViewsDataProvider + * @param string $importFile + * @param array $expected + */ + #[ + AppIsolation(true), + Config(CatalogConfig::XML_PATH_PRICE_SCOPE, CatalogConfig::PRICE_SCOPE_WEBSITE, ScopeInterface::SCOPE_STORE), + Config(ImportExportConfig::XML_PATH_BUNCH_SIZE, 2, ScopeInterface::SCOPE_STORE), + DataFixture(StoreFixture::class, ['code' => 'secondstore']), + DataFixture( + ProductFixture::class, + [ + 'sku' => 'simple2', + 'options' => [ + [ + 'type' => ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN, + 'title' => 'Option 1', + 'values' => [ + [ + 'title' => 'Option 1 Value 1', + 'price' => 2.5, + 'sku' => 'option1value1', + ], + [ + 'title' => 'Option 1 Value 2', + 'price' => 3, + 'sku' => 'option1value2', + ], + ] + ] + ] + ] + ), + ] + public function testSaveCustomOptionsWithMultipleStoreViewsAcrossMultipleBunches( + string $importFile, + array $expected + ) { + $this->testSaveCustomOptionsWithMultipleStoreViews($importFile, $expected); } /** @@ -259,6 +311,401 @@ public function getBehaviorDataProvider(): array ]; } + /** + * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function saveCustomOptionsWithMultipleStoreViewsDataProvider(): array + { + return [ + [ + 'product_with_custom_options_and_multiple_store_views.csv', + [ + 'simple' => [ + 'admin' => [ + [ + 'title' => 'Test Field Title', + 'type' => 'field', + 'is_require' => '1', + 'sku' => '1-text', + 'price' => '100.000000', + 'max_characters' => '0', + 'sort_order' => '1', + ], + [ + 'title' => 'Test Date and Time Title', + 'type' => 'date_time', + 'is_require' => '1', + 'sku' => '2-date', + 'price' => '200.000000', + 'max_characters' => '0', + 'sort_order' => '2', + ], + [ + 'title' => 'Test Select', + 'type' => 'drop_down', + 'is_require' => '1', + 'sku' => '', + 'price' => null, + 'max_characters' => '0', + 'sort_order' => '3', + 'values' => [ + [ + 'title' => 'Select Option 1', + 'sku' => '3-1-select', + 'price' => '310.000000', + ], + [ + 'title' => 'Select Option 2', + 'sku' => '3-2-select', + 'price' => '320.000000', + ] + ] + ], + [ + 'title' => 'Test Checkbox', + 'type' => 'checkbox', + 'is_require' => '1', + 'sku' => '', + 'price' => null, + 'max_characters' => '0', + 'sort_order' => '4', + 'values' => [ + [ + 'title' => 'Checkbox Option 1', + 'sku' => '4-1-select', + 'price' => '410.000000', + ], + [ + 'title' => 'Checkbox Option 2', + 'sku' => '4-2-select', + 'price' => '420.000000', + ] + ] + ], + [ + 'title' => 'Test Radio', + 'type' => 'radio', + 'is_require' => '1', + 'sku' => '', + 'price' => null, + 'max_characters' => '0', + 'sort_order' => '5', + 'values' => [ + [ + 'title' => 'Radio Option 1', + 'sku' => '5-1-radio', + 'price' => '510.000000', + ], + [ + 'title' => 'Radio Option 2', + 'sku' => '5-2-radio', + 'price' => '520.000000', + ] + ] + ] + ], + 'default' => [ + [ + 'title' => 'Test Field Title_default', + ], + [ + 'title' => 'Test Date and Time Title_default', + ], + [ + 'title' => 'Test Select_default', + 'values' => [ + [ + 'title' => 'Select Option 1_default', + ], + [ + 'title' => 'Select Option 2_default', + ] + ] + ], + [ + 'title' => 'Test Checkbox_default', + 'values' => [ + [ + 'title' => 'Checkbox Option 1_default', + ], + [ + 'title' => 'Checkbox Option 2_default', + ] + ] + ], + [ + 'title' => 'Test Radio_default', + 'values' => [ + [ + 'title' => 'Radio Option 1_default', + ], + [ + 'title' => 'Radio Option 2_default', + ] + ] + ] + ], + 'secondstore' => [ + [ + 'title' => 'Test Field Title_fixture_second_store', + 'price' => '101.000000' + ], + [ + 'title' => 'Test Date and Time Title_fixture_second_store', + 'price' => '201.000000' + ], + [ + 'title' => 'Test Select_fixture_second_store', + 'values' => [ + [ + 'title' => 'Select Option 1_fixture_second_store', + 'price' => '311.000000' + ], + [ + 'title' => 'Select Option 2_fixture_second_store', + 'price' => '321.000000' + ] + ] + ], + [ + 'title' => 'Test Checkbox_second_store', + 'values' => [ + [ + 'title' => 'Checkbox Option 1_second_store', + 'price' => '411.000000' + ], + [ + 'title' => 'Checkbox Option 2_second_store', + 'price' => '421.000000' + ] + ] + ], + [ + 'title' => 'Test Radio_fixture_second_store', + 'values' => [ + [ + 'title' => 'Radio Option 1_fixture_second_store', + 'price' => '511.000000' + ], + [ + 'title' => 'Radio Option 2_fixture_second_store', + 'price' => '521.000000' + ] + ] + ] + ], + ], + 'newprod2' => [ + 'admin' => [], + 'default' => [], + 'secondstore' => [], + ], + 'newprod3' => [ + 'admin' => [ + [ + 'title' => 'Line 1', + 'type' => 'field', + 'is_require' => '1', + 'sku' => '', + 'price' => null, + 'max_characters' => '30', + 'sort_order' => '1', + ], + [ + 'title' => 'Line 2', + 'type' => 'field', + 'is_require' => '0', + 'sku' => '', + 'price' => null, + 'max_characters' => '30', + 'sort_order' => '2', + ], + ], + 'default' => [ + [ + 'title' => 'Line 1', + 'type' => 'field', + 'is_require' => '1', + 'sku' => '', + 'price' => null, + 'max_characters' => '30', + 'sort_order' => '1', + ], + [ + 'title' => 'Line 2', + 'type' => 'field', + 'is_require' => '0', + 'sku' => '', + 'price' => null, + 'max_characters' => '30', + 'sort_order' => '2', + ], + ], + 'secondstore' => [ + [ + 'title' => 'Line 1', + 'type' => 'field', + 'is_require' => '1', + 'sku' => '', + 'price' => null, + 'max_characters' => '30', + 'sort_order' => '1', + ], + [ + 'title' => 'Line 2', + 'type' => 'field', + 'is_require' => '0', + 'sku' => '', + 'price' => null, + 'max_characters' => '30', + 'sort_order' => '2', + ], + ], + ], + 'newprod4' => [ + 'admin' => [], + 'default' => [], + 'secondstore' => [], + ], + 'newprod5' => [ + 'admin' => [ + [ + 'title' => 'Line 3', + 'type' => 'field', + 'is_require' => '1', + 'sku' => '', + 'price' => null, + 'max_characters' => '30', + 'sort_order' => '1', + ], + [ + 'title' => 'Line 4', + 'type' => 'field', + 'is_require' => '0', + 'sku' => '', + 'price' => null, + 'max_characters' => '30', + 'sort_order' => '2', + ], + ], + 'default' => [ + [ + 'title' => 'Line 3', + 'type' => 'field', + 'is_require' => '1', + 'sku' => '', + 'price' => null, + 'max_characters' => '30', + 'sort_order' => '1', + ], + [ + 'title' => 'Line 4', + 'type' => 'field', + 'is_require' => '0', + 'sku' => '', + 'price' => null, + 'max_characters' => '30', + 'sort_order' => '2', + ], + ], + 'secondstore' => [ + [ + 'title' => 'Line 3', + 'type' => 'field', + 'is_require' => '1', + 'sku' => '', + 'price' => null, + 'max_characters' => '30', + 'sort_order' => '1', + ], + [ + 'title' => 'Line 4', + 'type' => 'field', + 'is_require' => '0', + 'sku' => '', + 'price' => null, + 'max_characters' => '30', + 'sort_order' => '2', + ], + ], + ], + 'simple2' => [ + 'admin' => [ + [ + 'title' => 'Option 1', + 'type' => 'drop_down', + 'is_require' => '1', + 'sku' => '', + 'price' => null, + 'max_characters' => '0', + 'sort_order' => '1', + 'values' => [ + [ + 'title' => 'Option 1 Value 1', + 'sku' => 'option1value1', + 'price' => '1.200000', + ], + [ + 'title' => 'Option 1 Value 2', + 'sku' => 'option1value2', + 'price' => '1.400000', + ] + ] + ] + ], + 'default' => [ + [ + 'title' => 'Option 1 Store1', + 'type' => 'drop_down', + 'is_require' => '1', + 'sku' => '', + 'price' => null, + 'max_characters' => '0', + 'sort_order' => '1', + 'values' => [ + [ + 'title' => 'Option 1 Value 1 Store1', + 'sku' => 'option1value1', + 'price' => '1.100000', + ], + [ + 'title' => 'Option 1 Value 2 Store1', + 'sku' => 'option1value2', + 'price' => '1.300000', + ] + ] + ] + ], + 'secondstore' => [ + [ + 'title' => 'Option 1 Store2', + 'type' => 'drop_down', + 'is_require' => '1', + 'sku' => '', + 'price' => null, + 'max_characters' => '0', + 'sort_order' => '1', + 'values' => [ + [ + 'title' => 'Option 1 Value 1 Store2', + 'sku' => 'option1value1', + 'price' => '1.000000', + ], + [ + 'title' => 'Option 1 Value 2 Store2', + 'sku' => 'option1value2', + 'price' => '1.200000', + ] + ] + ] + ], + ] + ] + ] + ]; + } + /** * @param string $productSku * @return array ['optionId' => ['optionValueId' => 'optionValueTitle', ...], ...] @@ -488,4 +935,27 @@ protected function getOptionValues(\Magento\Catalog\Model\Product\Option $option return false; } + + /** + * @param array $expected + * @return array + */ + private function getFullExpectedOptions(array $expected): array + { + foreach ($expected as &$data) { + foreach ($data as $store => &$options) { + if ($store !== 'admin') { + foreach ($options as $optKey => &$option) { + $option += $data['admin'][$optKey]; + if (isset($option['values'])) { + foreach ($option['values'] as $valKey => &$value) { + $value += $data['admin'][$optKey]['values'][$valKey]; + } + } + } + } + } + } + return $expected; + } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv index 0d4c53ca5812d..6aefef5ed836a 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv @@ -1,8 +1,11 @@ sku,website_code,store_view_code,attribute_set_code,product_type,name,description,short_description,weight,product_online,visibility,product_websites,categories,price,special_price,special_price_from_date,special_price_to_date,tax_class_name,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,additional_images,additional_image_labels,configurable_variation_labels,configurable_variations,bundle_price_type,bundle_sku_type,bundle_weight_type,bundle_values,downloadble_samples,downloadble_links,associated_skus,related_skus,crosssell_skus,upsell_skus,custom_options,additional_attributes,manage_stock,is_in_stock,qty,out_of_stock_qty,is_qty_decimal,allow_backorders,min_cart_qty,max_cart_qty,notify_on_stock_below,qty_increments,enable_qty_increments,is_decimal_divided,new_from_date,new_to_date,gift_message_available,created_at,updated_at,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_price,msrp_display_actual_price_type,map_enabled -simple,base,,Default,simple,New Product,,,9,1,"Catalog, Search","base,secondwebsite",,10,,,,Taxable Goods,new-product,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title,type=field,required=1,sku=1-text,price=100.000000|name=Test Date and Time Title,type=date_time,required=1,sku=2-date,price=200.000000|name=Test Select,type=drop_down,required=1,sku=3-1-select,price=310.000000,option_title=Select Option 1|name=Test Select,type=drop_down,required=1,sku=3-2-select,price=320.000000,option_title=Select Option 2|name=Test Checkbox,type=checkbox,required=1,sku=4-1-select,price=410.000000,option_title=Checkbox Option 1|name=Test Checkbox,type=checkbox,required=1,sku=4-2-select,price=420.000000,option_title=Checkbox Option 2|name=Test Radio,type=radio,required=1,sku=5-1-radio,price=510.000000,option_title=Radio Option 1|name=Test Radio,type=radio,required=1,sku=5-2-radio,price=520.000000,option_title=Radio Option 2",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, -simple,,default,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title_default,type=field,sku=1-text|name=Test Date and Time Title_default,type=date_time,sku=2-date|name=Test Select_default,type=drop_down,sku=3-1-select,option_title=Select Option 1_default|name=Test Select_default,type=drop_down,sku=3-2-select,option_title=Select Option 2_default|name=Test Checkbox_default,type=checkbox,sku=4-1-select,option_title=Checkbox Option 1_default|name=Test Checkbox_default,type=checkbox,sku=4-2-select,option_title=Checkbox Option 2_default|name=Test Radio_default,type=radio,sku=5-1-radio,option_title=Radio Option 1_default|name=Test Radio_default,type=radio,sku=5-2-radio,option_title=Radio Option 2_default",,,,,,,,,,,,,,,,,,,,,,,,,,, -simple,,secondstore,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title_fixture_second_store,type=field,sku=1-text,price=101.000000|name=Test Date and Time Title_fixture_second_store,type=date_time,sku=2-date,price=201.000000|name=Test Select_fixture_second_store,type=drop_down,sku=3-1-select,price=311.000000,option_title=Select Option 1_fixture_second_store|name=Test Select_fixture_second_store,type=drop_down,sku=3-2-select,price=321.000000,option_title=Select Option 2_fixture_second_store|name=Test Checkbox_second_store,type=checkbox,sku=4-1-select,price=411.000000,option_title=Checkbox Option 1_second_store|name=Test Checkbox_second_store,type=checkbox,sku=4-2-select,price=421.000000,option_title=Checkbox Option 2_second_store|name=Test Radio_fixture_second_store,type=radio,sku=5-1-radio,price=511.000000,option_title=Radio Option 1_fixture_second_store|name=Test Radio_fixture_second_store,type=radio,sku=5-2-radio,price=521.000000,option_title=Radio Option 2_fixture_second_store",,,,,,,,,,,,,,,,,,,,,,,,,,, -newprod2,base,secondstore,Default,configurable,New Product 2,,,9,1,"Catalog, Search","base,secondwebsite",,10,,,,Taxable Goods,new-product-2,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, -newprod3,base,,Default,configurable,New Product 3,,,9,1,"Catalog, Search","base,secondwebsite",,10,,,,Taxable Goods,new-product-3,,,,,,,,,,,,,,,,,,,,,,,,"name=Line 1,type=field,max_characters=30,required=1,option_title=Line 1|name=Line 2,type=field,max_characters=30,required=0,option_title=Line 2",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, -newprod4,base,secondstore,Default,configurable,New Product 4,,,9,1,"Catalog, Search","base,secondwebsite",,10,,,,Taxable Goods,new-product-4,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, -newprod5,base,,Default,configurable,New Product 5,,,9,1,"Catalog, Search","base,secondwebsite",,10,,,,Taxable Goods,new-product-5,,,,,,,,,,,,,,,,,,,,,,,,"name=Line 3,type=field,max_characters=30,required=1,option_title=Line 3|name=Line 4,type=field,max_characters=30,required=0,option_title=Line 4",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, +simple,base,,Default,simple,New Product,,,9,1,"Catalog, Search",base,,10,,,,Taxable Goods,new-product,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title,type=field,required=1,sku=1-text,price=100.000000|name=Test Date and Time Title,type=date_time,required=1,sku=2-date,price=200.000000|name=Test Select,type=drop_down,required=1,sku=3-1-select,price=310.000000,option_title=Select Option 1|name=Test Select,type=drop_down,required=1,sku=3-2-select,price=320.000000,option_title=Select Option 2|name=Test Checkbox,type=checkbox,required=1,sku=4-1-select,price=410.000000,option_title=Checkbox Option 1|name=Test Checkbox,type=checkbox,required=1,sku=4-2-select,price=420.000000,option_title=Checkbox Option 2|name=Test Radio,type=radio,required=1,sku=5-1-radio,price=510.000000,option_title=Radio Option 1|name=Test Radio,type=radio,required=1,sku=5-2-radio,price=520.000000,option_title=Radio Option 2",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, +simple,,default,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title_default,type=field,required=1,sku=1-text|name=Test Date and Time Title_default,type=date_time,required=1,sku=2-date|name=Test Select_default,type=drop_down,required=1,sku=3-1-select,option_title=Select Option 1_default|name=Test Select_default,type=drop_down,required=1,sku=3-2-select,option_title=Select Option 2_default|name=Test Checkbox_default,type=checkbox,required=1,sku=4-1-select,option_title=Checkbox Option 1_default|name=Test Checkbox_default,type=checkbox,required=1,sku=4-2-select,option_title=Checkbox Option 2_default|name=Test Radio_default,type=radio,required=1,sku=5-1-radio,option_title=Radio Option 1_default|name=Test Radio_default,type=radio,required=1,sku=5-2-radio,option_title=Radio Option 2_default",,,,,,,,,,,,,,,,,,,,,,,,,,, +simple,,secondstore,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title_fixture_second_store,type=field,required=1,sku=1-text,price=101.000000|name=Test Date and Time Title_fixture_second_store,type=date_time,required=1,sku=2-date,price=201.000000|name=Test Select_fixture_second_store,type=drop_down,required=1,sku=3-1-select,price=311.000000,option_title=Select Option 1_fixture_second_store|name=Test Select_fixture_second_store,type=drop_down,required=1,sku=3-2-select,price=321.000000,option_title=Select Option 2_fixture_second_store|name=Test Checkbox_second_store,type=checkbox,required=1,sku=4-1-select,price=411.000000,option_title=Checkbox Option 1_second_store|name=Test Checkbox_second_store,type=checkbox,required=1,sku=4-2-select,price=421.000000,option_title=Checkbox Option 2_second_store|name=Test Radio_fixture_second_store,type=radio,required=1,sku=5-1-radio,price=511.000000,option_title=Radio Option 1_fixture_second_store|name=Test Radio_fixture_second_store,type=radio,required=1,sku=5-2-radio,price=521.000000,option_title=Radio Option 2_fixture_second_store",,,,,,,,,,,,,,,,,,,,,,,,,,, +newprod2,base,secondstore,Default,configurable,New Product 2,,,9,1,"Catalog, Search",base,,10,,,,Taxable Goods,new-product-2,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, +newprod3,base,,Default,configurable,New Product 3,,,9,1,"Catalog, Search",base,,10,,,,Taxable Goods,new-product-3,,,,,,,,,,,,,,,,,,,,,,,,"name=Line 1,type=field,max_characters=30,required=1,option_title=Line 1|name=Line 2,type=field,max_characters=30,required=0,option_title=Line 2",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, +newprod4,base,secondstore,Default,configurable,New Product 4,,,9,1,"Catalog, Search",base,,10,,,,Taxable Goods,new-product-4,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, +newprod5,base,,Default,configurable,New Product 5,,,9,1,"Catalog, Search",base,,10,,,,Taxable Goods,new-product-5,,,,,,,,,,,,,,,,,,,,,,,,"name=Line 3,type=field,max_characters=30,required=1,option_title=Line 3|name=Line 4,type=field,max_characters=30,required=0,option_title=Line 4",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, +simple2,base,,Default,simple,Simple 2,,,9,1,"Catalog, Search",base,,10,,,,Taxable Goods,,,,,,,,,,,,,,,,,,,,,,,,,"name=Option 1,type=drop_down,required=1,sku=option1value1,price=1.2,option_title=Option 1 Value 1|name=Option 1,type=drop_down,required=1,sku=option1value2,price=1.4,option_title=Option 1 Value 2",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, +simple2,,default,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Option 1 Store1,type=drop_down,required=1,sku=option1value1,price=1.1,option_title=Option 1 Value 1 Store1|name=Option 1 Store1,type=drop_down,required=1,sku=option1value2,price=1.3,option_title=Option 1 Value 2 Store1",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,,,, +simple2,,secondstore,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Option 1 Store2,type=drop_down,required=1,sku=option1value1,price=1.0,option_title=Option 1 Value 1 Store2|name=Option 1 Store2,type=drop_down,required=1,sku=option1value2,price=1.2,option_title=Option 1 Value 2 Store2",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,,,, diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/Model/ResourceModel/Product/ConditionsToCollectionApplierTest.php b/dev/tests/integration/testsuite/Magento/CatalogRule/Model/ResourceModel/Product/ConditionsToCollectionApplierTest.php index b06de8109ecbd..778c7c42249fc 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogRule/Model/ResourceModel/Product/ConditionsToCollectionApplierTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/Model/ResourceModel/Product/ConditionsToCollectionApplierTest.php @@ -433,6 +433,22 @@ private function conditionProvider() 'simple-product-13', ] ], + + // test filter by multiple sku and "is not one of" condition + 'variation 23' => [ + 'condition' => $this->getConditionsForVariation23(), + 'expected-sku' => [ + 'simple-product-3', + 'simple-product-4', + 'simple-product-6', + 'simple-product-7', + 'simple-product-8', + 'simple-product-9', + 'simple-product-11', + 'simple-product-12', + 'simple-product-13', + ] + ], ]; } @@ -1058,6 +1074,25 @@ private function getConditionsForVariation22() return $this->getCombineConditionFromArray($conditions); } + private function getConditionsForVariation23() + { + $conditions = [ + 'type' => \Magento\CatalogRule\Model\Rule\Condition\Combine::class, + 'aggregator' => 'all', + 'value' => 1, + 'conditions' => [ + [ + 'type' => \Magento\CatalogRule\Model\Rule\Condition\Product::class, + 'operator' => '!()', + 'value' => 'simple-product-1, simple-product-2, simple-product-5, simple-product-10', + 'attribute' => 'sku' + ] + ] + ]; + + return $this->getCombineConditionFromArray($conditions); + } + private function getCombineConditionFromArray(array $data) { $combinedCondition = $this->combinedConditionFactory->create(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogWidget/Model/Rule/Condition/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogWidget/Model/Rule/Condition/ProductTest.php index e231251db2c3f..37077d60e683a 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogWidget/Model/Rule/Condition/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogWidget/Model/Rule/Condition/ProductTest.php @@ -102,11 +102,11 @@ public function testAddNonGlobalAttributeToCollectionNoProducts() $this->conditionProduct->addToCollection($collection); $collectedAttributes = $this->conditionProduct->getRule()->getCollectedAttributes(); $this->assertArrayHasKey('visibility', $collectedAttributes); - $query = (string)$collection->getSelect(); - $this->assertStringNotContainsString('visibility', $query); - $this->assertEquals('', $this->conditionProduct->getMappedSqlField()); + $this->assertEquals(0, $collection->getSize()); + $this->assertStringContainsString('visibility', (string)$this->conditionProduct->getMappedSqlField()); $this->assertFalse($this->conditionProduct->hasValueParsed()); - $this->assertFalse($this->conditionProduct->hasValue()); + $this->assertTrue($this->conditionProduct->hasValue()); + $this->assertEquals('4', $this->conditionProduct->getValue()); } /** @@ -121,9 +121,11 @@ public function testAddNonGlobalAttributeToCollection() $this->conditionProduct->addToCollection($collection); $collectedAttributes = $this->conditionProduct->getRule()->getCollectedAttributes(); $this->assertArrayHasKey('visibility', $collectedAttributes); - $query = (string)$collection->getSelect(); - $this->assertStringNotContainsString('visibility', $query); - $this->assertEquals('e.entity_id', $this->conditionProduct->getMappedSqlField()); + $this->assertEquals(1, $collection->getSize()); + $this->assertStringContainsString('visibility', (string)$this->conditionProduct->getMappedSqlField()); + $this->assertFalse($this->conditionProduct->hasValueParsed()); + $this->assertTrue($this->conditionProduct->hasValue()); + $this->assertEquals('4', $this->conditionProduct->getValue()); } /** diff --git a/lib/internal/Magento/Framework/Amqp/composer.json b/lib/internal/Magento/Framework/Amqp/composer.json index 701f2df167065..d6f7337988934 100644 --- a/lib/internal/Magento/Framework/Amqp/composer.json +++ b/lib/internal/Magento/Framework/Amqp/composer.json @@ -11,7 +11,7 @@ ], "require": { "magento/framework": "*", - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "php-amqplib/php-amqplib": "~3.2.0" }, "autoload": { diff --git a/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php b/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php index 2f4c097b1f02a..9668f4e2239df 100644 --- a/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php +++ b/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php @@ -140,7 +140,7 @@ private function checkFromTo(&$fields, &$conditions) $_fields = array_unique($fields); $_conditions = []; foreach ($conditions as $condition) { - $_conditions[array_key_first($condition)] = array_first($condition); + $_conditions[array_key_first($condition)] = reset($condition); } if ((count($_fields) == 1) && (count($_conditions) == 2) && isset($_conditions['from']) && isset($_conditions['to']) diff --git a/lib/internal/Magento/Framework/App/DeploymentConfig.php b/lib/internal/Magento/Framework/App/DeploymentConfig.php index 538e653549550..6713baa3a1d54 100644 --- a/lib/internal/Magento/Framework/App/DeploymentConfig.php +++ b/lib/internal/Magento/Framework/App/DeploymentConfig.php @@ -35,14 +35,14 @@ class DeploymentConfig * * @var array */ - private $data; + private $data = []; /** * Flattened data * * @var array */ - private $flatData; + private $flatData = []; /** * Injected configuration data @@ -76,16 +76,18 @@ public function __construct(DeploymentConfig\Reader $reader, $overrideData = []) */ public function get($key = null, $defaultValue = null) { - $this->load(); if ($key === null) { + if (empty($this->flatData)) { + $this->reloadData(); + } return $this->flatData; } - - if (array_key_exists($key, $this->flatData) && $this->flatData[$key] === null) { - return ''; + $result = $this->getByKey($key); + if ($result === null) { + $this->reloadData(); + $result = $this->getByKey($key); } - - return $this->flatData[$key] ?? $defaultValue; + return $result ?? $defaultValue; } /** @@ -97,27 +99,31 @@ public function get($key = null, $defaultValue = null) */ public function isAvailable() { - $this->load(); - return isset($this->flatData[ConfigOptionsListConstants::CONFIG_PATH_INSTALL_DATE]); + return $this->get(ConfigOptionsListConstants::CONFIG_PATH_INSTALL_DATE) !== null; } /** * Gets a value specified key from config data * - * @param string $key + * @param string|null $key * @return null|mixed * @throws FileSystemException * @throws RuntimeException */ public function getConfigData($key = null) { - $this->load(); - - if ($key !== null && !isset($this->data[$key])) { - return null; + if ($key === null) { + if (empty($this->data)) { + $this->reloadData(); + } + return $this->data; } - - return $this->data[$key] ?? $this->data; + $result = $this->getConfigDataByKey($key); + if ($result === null) { + $this->reloadData(); + $result = $this->getConfigDataByKey($key); + } + return $result; } /** @@ -127,7 +133,8 @@ public function getConfigData($key = null) */ public function resetData() { - $this->data = null; + $this->data = []; + $this->flatData = []; } /** @@ -140,8 +147,7 @@ public function resetData() */ public function isDbAvailable() { - $this->load(); - return isset($this->data['db']); + return $this->getConfigData('db') !== null; } /** @@ -164,28 +170,26 @@ private function getEnvOverride() : array * @throws FileSystemException * @throws RuntimeException */ - private function load() + private function reloadData(): void { - if (empty($this->data)) { - $this->data = array_replace( - $this->reader->load(), - $this->overrideData ?? [], - $this->getEnvOverride() - ); - // flatten data for config retrieval using get() - $this->flatData = $this->flattenParams($this->data); - - // allow reading values from env variables by convention - // MAGENTO_DC_{path}, like db/connection/default/host => - // can be overwritten by MAGENTO_DC_DB__CONNECTION__DEFAULT__HOST - foreach (getenv() as $key => $value) { - if (false !== \strpos($key, self::MAGENTO_ENV_PREFIX) - && $key !== self::OVERRIDE_KEY - ) { - // convert MAGENTO_DC_DB__CONNECTION__DEFAULT__HOST into db/connection/default/host - $flatKey = strtolower(str_replace([self::MAGENTO_ENV_PREFIX, '__'], ['', '/'], $key)); - $this->flatData[$flatKey] = $value; - } + $this->data = array_replace( + $this->reader->load(), + $this->overrideData ?? [], + $this->getEnvOverride() + ); + // flatten data for config retrieval using get() + $this->flatData = $this->flattenParams($this->data); + + // allow reading values from env variables by convention + // MAGENTO_DC_{path}, like db/connection/default/host => + // can be overwritten by MAGENTO_DC_DB__CONNECTION__DEFAULT__HOST + foreach (getenv() as $key => $value) { + if (false !== \strpos($key, self::MAGENTO_ENV_PREFIX) + && $key !== self::OVERRIDE_KEY + ) { + // convert MAGENTO_DC_DB__CONNECTION__DEFAULT__HOST into db/connection/default/host + $flatKey = strtolower(str_replace([self::MAGENTO_ENV_PREFIX, '__'], ['', '/'], $key)); + $this->flatData[$flatKey] = $value; } } } @@ -197,12 +201,12 @@ private function load() * each level of array is accessible by path key * * @param array $params - * @param string $path - * @param array $flattenResult + * @param string|null $path + * @param array|null $flattenResult * @return array * @throws RuntimeException */ - private function flattenParams(array $params, $path = null, array &$flattenResult = null) : array + private function flattenParams(array $params, ?string $path = null, array &$flattenResult = null): array { if (null === $flattenResult) { $flattenResult = []; @@ -236,4 +240,30 @@ private function flattenParams(array $params, $path = null, array &$flattenResul return $flattenResult; } + + /** + * Returns flat data by key + * + * @param string|null $key + * @return mixed|null + */ + private function getByKey(?string $key) + { + if (array_key_exists($key, $this->flatData) && $this->flatData[$key] === null) { + return ''; + } + + return $this->flatData[$key] ?? null; + } + + /** + * Returns data by key + * + * @param string|null $key + * @return mixed|null + */ + private function getConfigDataByKey(?string $key) + { + return $this->data[$key] ?? null; + } } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfigTest.php b/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfigTest.php index 191a86c442f95..42b4d7866c23b 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfigTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfigTest.php @@ -1,8 +1,10 @@ <?php + /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + declare(strict_types=1); namespace Magento\Framework\App\Test\Unit; @@ -10,6 +12,8 @@ use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\DeploymentConfig\Reader; use Magento\Framework\Config\ConfigOptionsListConstants; +use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Exception\RuntimeException; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -20,12 +24,12 @@ class DeploymentConfigTest extends TestCase */ private static $fixture = [ - 'configData1' => 'scalar_value', - 'configData2' => [ + 'configData1' => 'scalar_value', + 'configData2' => [ 'foo' => 1, 'bar' => ['baz' => 2], ], - 'configData3' => null, + 'configData3' => null, 'test_override' => 'original', ]; @@ -34,16 +38,16 @@ class DeploymentConfigTest extends TestCase */ private static $flattenedFixture = [ - 'configData1' => 'scalar_value', - 'configData2' => [ + 'configData1' => 'scalar_value', + 'configData2' => [ 'foo' => 1, 'bar' => ['baz' => 2], ], - 'configData2/foo' => 1, - 'configData2/bar' => ['baz' => 2], + 'configData2/foo' => 1, + 'configData2/bar' => ['baz' => 2], 'configData2/bar/baz' => 2, - 'configData3' => null, - 'test_override' => 'overridden', + 'configData3' => null, + 'test_override' => 'overridden', ]; /** @@ -59,7 +63,7 @@ class DeploymentConfigTest extends TestCase /** * @var DeploymentConfig */ - protected $_deploymentConfig; + protected $deploymentConfig; /** * @var DeploymentConfig @@ -69,81 +73,100 @@ class DeploymentConfigTest extends TestCase /** * @var MockObject */ - private $reader; + private $readerMock; public static function setUpBeforeClass(): void { - self::$fixtureConfig = require __DIR__ . '/_files/config.php'; + self::$fixtureConfig = require __DIR__ . '/_files/config.php'; self::$fixtureConfigMerged = require __DIR__ . '/_files/other/local_developer_merged.php'; } protected function setUp(): void { - $this->reader = $this->createMock(Reader::class); - $this->_deploymentConfig = new DeploymentConfig( - $this->reader, + $this->readerMock = $this->createMock(Reader::class); + $this->deploymentConfig = new DeploymentConfig( + $this->readerMock, ['test_override' => 'overridden'] ); $this->_deploymentConfigMerged = new DeploymentConfig( - $this->reader, + $this->readerMock, require __DIR__ . '/_files/other/local_developer.php' ); } + /** + * @return void + * @throws FileSystemException + * @throws RuntimeException + */ public function testGetters(): void { - $this->reader->expects($this->once())->method('load')->willReturn(self::$fixture); - $this->assertSame(self::$flattenedFixture, $this->_deploymentConfig->get()); - // second time to ensure loader will be invoked only once - $this->assertSame(self::$flattenedFixture, $this->_deploymentConfig->get()); - $this->assertSame('scalar_value', $this->_deploymentConfig->getConfigData('configData1')); - $this->assertSame(self::$fixture['configData2'], $this->_deploymentConfig->getConfigData('configData2')); - $this->assertSame(self::$fixture['configData3'], $this->_deploymentConfig->getConfigData('configData3')); - $this->assertSame('', $this->_deploymentConfig->get('configData3')); - $this->assertSame('defaultValue', $this->_deploymentConfig->get('invalid_key', 'defaultValue')); - $this->assertNull($this->_deploymentConfig->getConfigData('invalid_key')); - $this->assertSame('overridden', $this->_deploymentConfig->get('test_override')); + $this->readerMock->expects($this->any())->method('load')->willReturn(self::$fixture); + $this->assertSame(self::$flattenedFixture, $this->deploymentConfig->get()); + $this->assertSame('scalar_value', $this->deploymentConfig->getConfigData('configData1')); + $this->assertSame(self::$fixture['configData2'], $this->deploymentConfig->getConfigData('configData2')); + $this->assertSame(self::$fixture['configData3'], $this->deploymentConfig->getConfigData('configData3')); + $this->assertSame('', $this->deploymentConfig->get('configData3')); + $this->assertSame('defaultValue', $this->deploymentConfig->get('invalid_key', 'defaultValue')); + $this->assertNull($this->deploymentConfig->getConfigData('invalid_key')); + $this->assertSame('overridden', $this->deploymentConfig->get('test_override')); } + /** + * @return void + * @throws FileSystemException + * @throws RuntimeException + */ public function testIsAvailable(): void { - $this->reader->expects($this->once())->method('load')->willReturn( + $this->readerMock->expects($this->once())->method('load')->willReturn( [ ConfigOptionsListConstants::CONFIG_PATH_INSTALL_DATE => 1, ] ); - $object = new DeploymentConfig($this->reader); + $object = new DeploymentConfig($this->readerMock); $this->assertTrue($object->isAvailable()); } + /** + * @return void + * @throws FileSystemException + * @throws RuntimeException + */ public function testNotAvailable(): void { - $this->reader->expects($this->once())->method('load')->willReturn([]); - $object = new DeploymentConfig($this->reader); + $this->readerMock->expects($this->once())->method('load')->willReturn([]); + $object = new DeploymentConfig($this->readerMock); $this->assertFalse($object->isAvailable()); } /** * test if the configuration changes during the same request, the configuration remain the same + * + * @return void + * @throws FileSystemException + * @throws RuntimeException */ public function testNotAvailableThenAvailable(): void { - $this->reader->expects($this->once())->method('load')->willReturn(['Test']); - $object = new DeploymentConfig($this->reader); + $this->readerMock->expects($this->exactly(2))->method('load')->willReturn(['Test']); + $object = new DeploymentConfig($this->readerMock); $this->assertFalse($object->isAvailable()); $this->assertFalse($object->isAvailable()); } /** - * @param array $data * @dataProvider keyCollisionDataProvider + * @param array $data + * @throws FileSystemException + * @throws RuntimeException */ public function testKeyCollision(array $data): void { $this->expectException('Exception'); $this->expectExceptionMessage('Key collision'); - $this->reader->expects($this->once())->method('load')->willReturn($data); - $object = new DeploymentConfig($this->reader); + $this->readerMock->expects($this->once())->method('load')->willReturn($data); + $object = new DeploymentConfig($this->readerMock); $object->get(); } @@ -171,49 +194,71 @@ public function keyCollisionDataProvider(): array ]; } + /** + * @return void + * @throws FileSystemException + * @throws RuntimeException + */ public function testResetData(): void { - $this->reader->expects($this->exactly(2))->method('load')->willReturn(self::$fixture); - $this->assertSame(self::$flattenedFixture, $this->_deploymentConfig->get()); - $this->_deploymentConfig->resetData(); + $this->readerMock->expects($this->exactly(2))->method('load')->willReturn(self::$fixture); + $this->assertSame(self::$flattenedFixture, $this->deploymentConfig->get()); + $this->deploymentConfig->resetData(); // second time to ensure loader will be invoked only once after reset - $this->assertSame(self::$flattenedFixture, $this->_deploymentConfig->get()); - $this->assertSame(self::$flattenedFixture, $this->_deploymentConfig->get()); + $this->assertSame(self::$flattenedFixture, $this->deploymentConfig->get()); + $this->assertSame(self::$flattenedFixture, $this->deploymentConfig->get()); } + /** + * @return void + * @throws FileSystemException + * @throws RuntimeException + */ public function testIsDbAvailable(): void { - $this->reader->expects($this->exactly(2))->method('load')->willReturnOnConsecutiveCalls([], ['db' => []]); - $this->assertFalse($this->_deploymentConfig->isDbAvailable()); - $this->_deploymentConfig->resetData(); - $this->assertTrue($this->_deploymentConfig->isDbAvailable()); + $this->readerMock->expects($this->exactly(2))->method('load')->willReturnOnConsecutiveCalls([], ['db' => []]); + $this->assertFalse($this->deploymentConfig->isDbAvailable()); + $this->assertTrue($this->deploymentConfig->isDbAvailable()); + } + + /** + * @return void + * @throws FileSystemException + * @throws RuntimeException + */ + public function testResetDataOnMissingConfig(): void + { + $this->readerMock->expects($this->once())->method('load')->willReturn(self::$fixture); + $defaultValue = 'some_default_value'; + $result = $this->deploymentConfig->get('missing/key', $defaultValue); + $this->assertEquals($defaultValue, $result); } - public function testNoEnvVariables() + public function testNoEnvVariables(): void { - $this->reader->expects($this->once())->method('load')->willReturn(['a'=>'b']); - $this->assertSame('b', $this->_deploymentConfig->get('a')); + $this->readerMock->expects($this->once())->method('load')->willReturn(['a'=>'b']); + $this->assertSame('b', $this->deploymentConfig->get('a')); } - public function testEnvVariables() + public function testEnvVariables(): void { - $this->reader->expects($this->once())->method('load')->willReturn([]); + $this->readerMock->expects($this->once())->method('load')->willReturn([]); putenv('MAGENTO_DC__OVERRIDE={"a": "c"}'); - $this->assertSame('c', $this->_deploymentConfig->get('a')); + $this->assertSame('c', $this->deploymentConfig->get('a')); } - public function testEnvVariablesWithNoBaseConfig() + public function testEnvVariablesWithNoBaseConfig(): void { - $this->reader->expects($this->once())->method('load')->willReturn(['a'=>'b']); + $this->readerMock->expects($this->once())->method('load')->willReturn(['a'=>'b']); putenv('MAGENTO_DC_A=c'); putenv('MAGENTO_DC_B__B__B=D'); - $this->assertSame('c', $this->_deploymentConfig->get('a')); - $this->assertSame('D', $this->_deploymentConfig->get('b/b/b')); + $this->assertSame('c', $this->deploymentConfig->get('a')); + $this->assertSame('D', $this->deploymentConfig->get('b/b/b')); } - public function testEnvVariablesSubstitution() + public function testEnvVariablesSubstitution(): void { - $this->reader->expects($this->once()) + $this->readerMock->expects($this->once()) ->method('load') ->willReturn( [ @@ -224,8 +269,8 @@ public function testEnvVariablesSubstitution() ); putenv('MAGENTO_DC____A=c'); putenv('MAGENTO_DC____B=D'); - $this->assertSame('c', $this->_deploymentConfig->get('a')); - $this->assertSame('D', $this->_deploymentConfig->get('b'), 'return value from env'); - $this->assertSame('e$%^&', $this->_deploymentConfig->get('c'), 'return default value'); + $this->assertSame('c', $this->deploymentConfig->get('a')); + $this->assertSame('D', $this->deploymentConfig->get('b'), 'return value from env'); + $this->assertSame('e$%^&', $this->deploymentConfig->get('c'), 'return default value'); } } diff --git a/lib/internal/Magento/Framework/Async/Code/Generator/ProxyDeferredGenerator.php b/lib/internal/Magento/Framework/Async/Code/Generator/ProxyDeferredGenerator.php index 859a664470072..dc4e83b9905ec 100644 --- a/lib/internal/Magento/Framework/Async/Code/Generator/ProxyDeferredGenerator.php +++ b/lib/internal/Magento/Framework/Async/Code/Generator/ProxyDeferredGenerator.php @@ -9,6 +9,7 @@ use Magento\Framework\Async\DeferredInterface; use Magento\Framework\Code\Generator\EntityAbstract; +use Magento\Framework\GetReflectionMethodReturnTypeValueTrait; use Magento\Framework\ObjectManager\DefinitionFactory; use Magento\Framework\ObjectManager\NoninterceptableInterface; @@ -17,6 +18,8 @@ */ class ProxyDeferredGenerator extends EntityAbstract { + use GetReflectionMethodReturnTypeValueTrait; + /** * Entity type */ @@ -234,24 +237,4 @@ protected function _validateData() return $result; } - - /** - * Returns return type - * - * @param \ReflectionMethod $method - * @return null|string - */ - private function getReturnTypeValue(\ReflectionMethod $method): ?string - { - $returnTypeValue = null; - $returnType = $method->getReturnType(); - if ($returnType) { - $returnTypeValue = ($returnType->allowsNull() && $returnType->getName() !== 'mixed' ? '?' : ''); - $returnTypeValue .= ($returnType->getName() === 'self') - ? $this->_getFullyQualifiedClassName($method->getDeclaringClass()->getName()) - : $returnType->getName(); - } - - return $returnTypeValue; - } } diff --git a/lib/internal/Magento/Framework/Bulk/composer.json b/lib/internal/Magento/Framework/Bulk/composer.json index 7733ce9fea4c6..7beccb44975b3 100644 --- a/lib/internal/Magento/Framework/Bulk/composer.json +++ b/lib/internal/Magento/Framework/Bulk/composer.json @@ -11,7 +11,7 @@ ], "require": { "magento/framework": "*", - "php": "~7.4.0||~8.1.0" + "php": "~8.1.0||~8.2.0" }, "autoload": { "psr-4": { diff --git a/lib/internal/Magento/Framework/Cache/Backend/RemoteSynchronizedCache.php b/lib/internal/Magento/Framework/Cache/Backend/RemoteSynchronizedCache.php index 71f67b4aa6034..04efd1c60c4cb 100644 --- a/lib/internal/Magento/Framework/Cache/Backend/RemoteSynchronizedCache.php +++ b/lib/internal/Magento/Framework/Cache/Backend/RemoteSynchronizedCache.php @@ -195,14 +195,14 @@ public function load($id, $doNotTestCacheValidity = false) { $localData = $this->local->load($id); - if ($localData) { + if ($localData !== false) { if ($this->getDataVersion($localData) === $this->loadRemoteDataVersion($id)) { return $localData; } } $remoteData = $this->remote->load($id); - if ($remoteData) { + if ($remoteData !== false) { $this->local->save($remoteData, $id); return $remoteData; @@ -233,10 +233,15 @@ public function save($data, $id, $tags = [], $specificLifetime = false) { $dataToSave = $data; $remHash = $this->loadRemoteDataVersion($id); - + $isRemoteUpToDate = false; if ($remHash !== false && $this->getDataVersion($data) === $remHash) { - $dataToSave = $this->remote->load($id); - } else { + $remoteData = $this->remote->load($id); + if ($remoteData !== false && $this->getDataVersion($data) === $this->getDataVersion($remoteData)) { + $isRemoteUpToDate = true; + $dataToSave = $remoteData; + } + } + if (!$isRemoteUpToDate) { $this->remote->save($data, $id, $tags, $specificLifetime); $this->saveRemoteDataVersion($data, $id, $tags, $specificLifetime); } diff --git a/lib/internal/Magento/Framework/Cache/Test/Unit/Backend/RemoteSynchronizedCacheTest.php b/lib/internal/Magento/Framework/Cache/Test/Unit/Backend/RemoteSynchronizedCacheTest.php index d4a83180796ee..4b3b57a77b823 100644 --- a/lib/internal/Magento/Framework/Cache/Test/Unit/Backend/RemoteSynchronizedCacheTest.php +++ b/lib/internal/Magento/Framework/Cache/Test/Unit/Backend/RemoteSynchronizedCacheTest.php @@ -10,17 +10,11 @@ use Magento\Framework\Cache\Backend\Database; use Magento\Framework\Cache\Backend\RemoteSynchronizedCache; use Magento\Framework\DB\Adapter\Pdo\Mysql; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class RemoteSynchronizedCacheTest extends TestCase { - /** - * @var ObjectManager - */ - protected $objectManager; - /** * @var \Cm_Cache_Backend_File|MockObject */ @@ -41,24 +35,12 @@ class RemoteSynchronizedCacheTest extends TestCase */ protected function setUp(): void { - $this->objectManager = new ObjectManager($this); - - $this->localCacheMockExample = $this->getMockBuilder(\Cm_Cache_Backend_File::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->remoteCacheMockExample = $this->getMockBuilder(Database::class) - ->disableOriginalConstructor() - ->getMock(); - /** @var \Magento\Framework\Cache\Backend\Database $databaseCacheInstance */ - - $this->remoteSyncCacheInstance = $this->objectManager->getObject( - RemoteSynchronizedCache::class, + $this->localCacheMockExample = $this->createMock(\Cm_Cache_Backend_File::class); + $this->remoteCacheMockExample = $this->createMock(Database::class); + $this->remoteSyncCacheInstance = new RemoteSynchronizedCache( [ - 'options' => [ - 'remote_backend' => $this->remoteCacheMockExample, - 'local_backend' => $this->localCacheMockExample - ] + 'remote_backend' => $this->remoteCacheMockExample, + 'local_backend' => $this->localCacheMockExample ] ); } @@ -67,19 +49,13 @@ protected function setUp(): void * Test that exception is thrown if cache is not configured. * * @param array $options - * * @return void * @dataProvider initializeWithExceptionDataProvider */ public function testInitializeWithException($options): void { $this->expectException('Zend_Cache_Exception'); - $this->objectManager->getObject( - RemoteSynchronizedCache::class, - [ - 'options' => $options - ] - ); + new RemoteSynchronizedCache($options); } /** @@ -119,12 +95,7 @@ public function initializeWithExceptionDataProvider(): array */ public function testInitializeWithOutException($options): void { - $result = $this->objectManager->getObject( - RemoteSynchronizedCache::class, - [ - 'options' => $options - ] - ); + $result = new RemoteSynchronizedCache($options); $this->assertInstanceOf(RemoteSynchronizedCache::class, $result); } @@ -377,6 +348,38 @@ public function testSaveWithEqualRemoteData(): void $this->remoteSyncCacheInstance->save($remoteData, 1, $tags); } + /** + * Test data save when remote data are missed but hash exists. + * + * @return void + */ + public function testSaveWithEqualHashesAndMissedRemoteData(): void + { + $cacheKey = 'key'; + $dataToSave = '2'; + $remoteData = '1'; + $tags = ['MAGE']; + + $this->remoteCacheMockExample + ->method('load') + ->willReturnOnConsecutiveCalls(\hash('sha256', $dataToSave), $remoteData); + + $this->remoteCacheMockExample + ->expects($this->exactly(2)) + ->method('save') + ->withConsecutive( + [$dataToSave, $cacheKey, $tags], + [\hash('sha256', $dataToSave), $cacheKey . ':hash', $tags] + )->willReturn(true); + $this->localCacheMockExample + ->expects($this->once()) + ->method('save') + ->with($dataToSave, $cacheKey, []) + ->willReturn(true); + + $this->remoteSyncCacheInstance->save($dataToSave, $cacheKey, $tags); + } + /** * @return void */ diff --git a/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php b/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php index 8ccf26db333d8..d7f720cc7cb0c 100644 --- a/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php +++ b/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php @@ -338,6 +338,9 @@ private function extractParameterType( if ($parameterType instanceof \ReflectionUnionType) { $parameterType = $parameterType->getTypes(); $parameterType = implode('|', $parameterType); + } elseif ($parameterType instanceof \ReflectionIntersectionType) { + $parameterType = $parameterType->getTypes(); + $parameterType = implode('&', $parameterType); } else { $parameterType = $parameterType->getName(); } diff --git a/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php b/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php index 93f159a56d4e9..450955084d021 100644 --- a/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php +++ b/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php @@ -137,8 +137,9 @@ private function processType(\ReflectionClass $class, \Laminas\Code\Reflection\P * @param \ReflectionClass $class * @param array $classArguments * @return array|null + * @throws \ReflectionException */ - public function getParentCall(\ReflectionClass $class, array $classArguments) + public function getParentCall(\ReflectionClass $class, array $classArguments): ?array { /** Skip native PHP types */ if (!$class->getFileName()) { @@ -146,7 +147,12 @@ public function getParentCall(\ReflectionClass $class, array $classArguments) } $trimFunction = function (&$value) { - $value = trim($value, PHP_EOL . ' $'); + $position = strpos($value, ':'); + if ($position !== false) { + $value = trim(substr($value, 0, $position), PHP_EOL . ' '); + } else { + $value = trim($value, PHP_EOL . ' $'); + } }; $method = $class->getMethod('__construct'); @@ -158,10 +164,11 @@ public function getParentCall(\ReflectionClass $class, array $classArguments) $content = implode('', array_slice($source, $start, $length)); $pattern = '/parent::__construct\(([ ' . PHP_EOL . - ']*[$]{1}[a-zA-Z0-9_]*,)*[ ' . + ']*' . + '([a-zA-Z0-9_]+([ ' . PHP_EOL . '])*:([ ' . PHP_EOL . '])*)*[$][a-zA-Z0-9_]*,)*[ ' . PHP_EOL . ']*' . - '([$]{1}[a-zA-Z0-9_]*){1}[' . + '([a-zA-Z0-9_]+([ ' . PHP_EOL . '])*:([ ' . PHP_EOL . '])*)*([$][a-zA-Z0-9_]*)[' . PHP_EOL . ' ]*\);/'; @@ -176,6 +183,10 @@ public function getParentCall(\ReflectionClass $class, array $classArguments) $arguments = substr(trim($arguments), 20, -2); $arguments = explode(',', $arguments); + $isNamedArgument = []; + foreach ($arguments as $argumentPosition => $argumentName) { + $isNamedArgument[$argumentPosition] = (bool)strpos($argumentName, ':'); + } array_walk($arguments, $trimFunction); $output = []; @@ -185,8 +196,10 @@ public function getParentCall(\ReflectionClass $class, array $classArguments) 'name' => $argumentName, 'position' => $argumentPosition, 'type' => $type, + 'isNamedArgument' => $isNamedArgument[$argumentPosition], ]; } + return $output; } diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/Reader/ArgumentsReaderTest.php b/lib/internal/Magento/Framework/Code/Test/Unit/Reader/ArgumentsReaderTest.php index 9bf8ae3721738..64e1277bb1c1f 100644 --- a/lib/internal/Magento/Framework/Code/Test/Unit/Reader/ArgumentsReaderTest.php +++ b/lib/internal/Magento/Framework/Code/Test/Unit/Reader/ArgumentsReaderTest.php @@ -270,8 +270,18 @@ public function testGetParentCallWithRightArgumentsOrder() ] ); $expectedResult = [ - ['name' => 'stdClassObject', 'position' => 0, 'type' => '\stdClass'], - ['name' => 'secondClass', 'position' => 1, 'type' => '\ClassExtendsDefaultPhpType'], + [ + 'name' => 'stdClassObject', + 'position' => 0, + 'type' => '\stdClass', + 'isNamedArgument' => false + ], + [ + 'name' => 'secondClass', + 'position' => 1, + 'type' => '\ClassExtendsDefaultPhpType', + 'isNamedArgument' => false + ], ]; $this->assertEquals($expectedResult, $actualResult); } @@ -287,8 +297,17 @@ public function testGetParentCallWithWrongArgumentsOrder() ] ); $expectedResult = [ - ['name' => 'secondClass', 'position' => 0, 'type' => '\ClassExtendsDefaultPhpType'], - ['name' => 'stdClassObject', 'position' => 1, 'type' => '\stdClass'], + [ + 'name' => 'secondClass', + 'position' => 0, + 'type' => '\ClassExtendsDefaultPhpType', + 'isNamedArgument' => false], + [ + 'name' => 'stdClassObject', + 'position' => 1, + 'type' => '\stdClass', + 'isNamedArgument' => false + ], ]; $this->assertEquals($expectedResult, $actualResult); } @@ -304,8 +323,72 @@ public function testGetParentCallWithSeparateLineFormat() ] ); $expectedResult = [ - ['name' => 'stdClassObject', 'position' => 0, 'type' => '\stdClass'], - ['name' => 'secondClass', 'position' => 1, 'type' => '\ClassExtendsDefaultPhpType'], + [ + 'name' => 'stdClassObject', + 'position' => 0, + 'type' => '\stdClass', + 'isNamedArgument' => false + ], + [ + 'name' => 'secondClass', + 'position' => 1, + 'type' => '\ClassExtendsDefaultPhpType', + 'isNamedArgument' => false + ], + ]; + $this->assertEquals($expectedResult, $actualResult); + } + + public function testGetParentCallWithNamedArguments() + { + $class = new \ReflectionClass('ClassWithNamedArgumentsForParentCall'); + $actualResult = $this->_model->getParentCall( + $class, + [ + 'stdClassObject' => ['type' => '\stdClass'], + 'runeTimeException' => ['type' => '\ClassExtendsDefaultPhpType'] + ] + ); + $expectedResult = [ + [ + 'name' => 'stdClassObject', + 'position' => 0, + 'type' => '\stdClass', + 'isNamedArgument' => true + ], + [ + 'name' => 'runeTimeException', + 'position' => 1, + 'type' => '\ClassExtendsDefaultPhpType', + 'isNamedArgument' => true + ], + ]; + $this->assertEquals($expectedResult, $actualResult); + } + + public function testGetParentCallWithMixedArguments() + { + $class = new \ReflectionClass('ClassWithMixedArgumentsForParentCall'); + $actualResult = $this->_model->getParentCall( + $class, + [ + 'stdClassObject' => ['type' => '\stdClass'], + 'runeTimeException' => ['type' => '\ClassExtendsDefaultPhpType'] + ] + ); + $expectedResult = [ + [ + 'name' => 'stdClassObject', + 'position' => 0, + 'type' => '\stdClass', + 'isNamedArgument' => false + ], + [ + 'name' => 'runeTimeException', + 'position' => 1, + 'type' => '\ClassExtendsDefaultPhpType', + 'isNamedArgument' => true + ], ]; $this->assertEquals($expectedResult, $actualResult); } diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/Reader/_files/ClassesForArgumentsReader.php b/lib/internal/Magento/Framework/Code/Test/Unit/Reader/_files/ClassesForArgumentsReader.php index dcc5a7c11e553..88e22176810d1 100644 --- a/lib/internal/Magento/Framework/Code/Test/Unit/Reader/_files/ClassesForArgumentsReader.php +++ b/lib/internal/Magento/Framework/Code/Test/Unit/Reader/_files/ClassesForArgumentsReader.php @@ -257,3 +257,50 @@ public function __construct(\stdClass $stdClassObject, \ClassExtendsDefaultPhpTy $this->argumentTwo = $secondClass; } } +class ClassWithNamedArgumentsForParentCall extends FirstClassForParentCall +{ + /** + * @var stdClass + */ + protected $_stdClassObject; + + /** + * @var ClassExtendsDefaultPhpType + */ + protected $_runeTimeException; + + /** + * @param stdClass $stdClassObject + * @param ClassExtendsDefaultPhpType $runeTimeException + */ + public function __construct(\stdClass $stdClassObject, \ClassExtendsDefaultPhpType $runeTimeException) + { + parent::__construct(stdClassObject: $stdClassObject, runeTimeException: $runeTimeException); + $this->_stdClassObject = $stdClassObject; + $this->_runeTimeException = $runeTimeException; + } +} + +class ClassWithMixedArgumentsForParentCall extends FirstClassForParentCall +{ + /** + * @var stdClass + */ + protected $_stdClassObject; + + /** + * @var ClassExtendsDefaultPhpType + */ + protected $_runeTimeException; + + /** + * @param stdClass $stdClassObject + * @param ClassExtendsDefaultPhpType $runeTimeException + */ + public function __construct(\stdClass $stdClassObject, \ClassExtendsDefaultPhpType $runeTimeException) + { + parent::__construct($stdClassObject, runeTimeException: $runeTimeException); + $this->_stdClassObject = $stdClassObject; + $this->_runeTimeException = $runeTimeException; + } +} diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/Validator/ConstructorIntegrityTest.php b/lib/internal/Magento/Framework/Code/Test/Unit/Validator/ConstructorIntegrityTest.php index 4c77775602787..340a5fa81a9e2 100644 --- a/lib/internal/Magento/Framework/Code/Test/Unit/Validator/ConstructorIntegrityTest.php +++ b/lib/internal/Magento/Framework/Code/Test/Unit/Validator/ConstructorIntegrityTest.php @@ -5,9 +5,10 @@ */ namespace Magento\Framework\Code\Test\Unit\Validator; +use Magento\SomeModule\Model\NamedArguments\NamedParametersTest; +use Magento\SomeModule\Model\NamedArguments\MixedParametersTest; use PHPUnit\Framework\TestCase; use Magento\Framework\Code\Validator\ConstructorIntegrity; -use Magento\SomeModule\Model\One\Test; use Magento\Framework\Exception\ValidatorException; require_once __DIR__ . '/../_files/app/code/Magento/SomeModule/Model/Three/Test.php'; @@ -16,6 +17,8 @@ require_once __DIR__ . '/../_files/app/code/Magento/SomeModule/Model/Four/Test.php'; require_once __DIR__ . '/../_files/app/code/Magento/SomeModule/Model/Five/Test.php'; require_once __DIR__ . '/../_files/app/code/Magento/SomeModule/Model/Six/Test.php'; +require_once __DIR__ . '/../_files/app/code/Magento/SomeModule/Model/NamedArguments/NamedParametersTest.php'; +require_once __DIR__ . '/../_files/app/code/Magento/SomeModule/Model/NamedArguments/MixedParametersTest.php'; require_once __DIR__ . '/_files/ClassesForConstructorIntegrity.php'; class ConstructorIntegrityTest extends TestCase { @@ -31,7 +34,7 @@ protected function setUp(): void public function testValidateIfParentClassExist() { - $this->assertTrue($this->_model->validate(Test::class)); + $this->assertTrue($this->_model->validate(\Magento\SomeModule\Model\One\Test::class)); } public function testValidateIfClassHasParentConstructCall() @@ -39,6 +42,16 @@ public function testValidateIfClassHasParentConstructCall() $this->assertTrue($this->_model->validate(\Magento\SomeModule\Model\Two\Test::class)); } + public function testValidateIfClassHasParentConstructCallWithNamedArguments() + { + $this->assertTrue($this->_model->validate(NamedParametersTest::class)); + } + + public function testValidateIfClassHasParentConstructCallWithMixedArguments() + { + $this->assertTrue($this->_model->validate(MixedParametersTest::class)); + } + public function testValidateIfClassHasArgumentsQtyEqualToParentClass() { $this->assertTrue($this->_model->validate(\Magento\SomeModule\Model\Three\Test::class)); @@ -46,10 +59,6 @@ public function testValidateIfClassHasArgumentsQtyEqualToParentClass() public function testValidateIfClassHasExtraArgumentInTheParentConstructor() { - $fileName = realpath(__DIR__ . '/../_files/app/code/Magento/SomeModule/Model/Four/Test.php'); - $fileName = str_replace('\\', '/', $fileName); - $this->expectException(ValidatorException::class); - $this->expectExceptionMessage('Extra parameters passed to parent construct: $factory. File: ' . $fileName); $this->_model->validate(\Magento\SomeModule\Model\Four\Test::class); } diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/_files/app/code/Magento/SomeModule/Model/NamedArguments/MixedParametersTest.php b/lib/internal/Magento/Framework/Code/Test/Unit/_files/app/code/Magento/SomeModule/Model/NamedArguments/MixedParametersTest.php new file mode 100644 index 0000000000000..720d11b00b9de --- /dev/null +++ b/lib/internal/Magento/Framework/Code/Test/Unit/_files/app/code/Magento/SomeModule/Model/NamedArguments/MixedParametersTest.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SomeModule\Model\NamedArguments; + +require_once __DIR__ . '/ParentClassTest.php'; + +class MixedParametersTest extends ParentClassTest +{ + /** + * @param \stdClass $stdClassObject + * @param array $arrayVariable + */ + public function __construct(\stdClass $stdClassObject, array $arrayVariable) + { + parent::__construct($stdClassObject, arrayVariable: $arrayVariable); + } +} diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/_files/app/code/Magento/SomeModule/Model/NamedArguments/NamedParametersTest.php b/lib/internal/Magento/Framework/Code/Test/Unit/_files/app/code/Magento/SomeModule/Model/NamedArguments/NamedParametersTest.php new file mode 100644 index 0000000000000..c49e03f36ab7d --- /dev/null +++ b/lib/internal/Magento/Framework/Code/Test/Unit/_files/app/code/Magento/SomeModule/Model/NamedArguments/NamedParametersTest.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SomeModule\Model\NamedArguments; + +require_once __DIR__ . '/ParentClassTest.php'; + +class NamedParametersTest extends ParentClassTest +{ + /** + * @param \stdClass $stdClassObject + * @param array $arrayVariable + */ + public function __construct(\stdClass $stdClassObject, array $arrayVariable) + { + parent::__construct(arrayVariable: $arrayVariable, stdClassObject: $stdClassObject); + } +} diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/_files/app/code/Magento/SomeModule/Model/NamedArguments/ParentClassTest.php b/lib/internal/Magento/Framework/Code/Test/Unit/_files/app/code/Magento/SomeModule/Model/NamedArguments/ParentClassTest.php new file mode 100644 index 0000000000000..8f545bdeb6b1c --- /dev/null +++ b/lib/internal/Magento/Framework/Code/Test/Unit/_files/app/code/Magento/SomeModule/Model/NamedArguments/ParentClassTest.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SomeModule\Model\NamedArguments; + +class ParentClassTest +{ + /** + * @var \stdClass + */ + protected \stdClass $stdClassObject; + + /** + * @var array + */ + protected array $arrayVariable; + + /** + * @param \stdClass $stdClassObject + * @param array $arrayVariable + */ + public function __construct(\stdClass $stdClassObject, array $arrayVariable) + { + $this->stdClassObject = $stdClassObject; + $this->arrayVariable = $arrayVariable; + } +} diff --git a/lib/internal/Magento/Framework/Code/Validator/ConstructorIntegrity.php b/lib/internal/Magento/Framework/Code/Validator/ConstructorIntegrity.php index 59491ee3c9cdc..d12c66c3a595a 100644 --- a/lib/internal/Magento/Framework/Code/Validator/ConstructorIntegrity.php +++ b/lib/internal/Magento/Framework/Code/Validator/ConstructorIntegrity.php @@ -1,15 +1,19 @@ <?php /** - * Class constructor validator. Validates call of parent construct - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\Code\Validator; use Magento\Framework\Code\ValidatorInterface; +use Magento\Framework\Exception\ValidatorException; use Magento\Framework\Phrase; +/** + * Class constructor validator. Validates call of parent construct + */ class ConstructorIntegrity implements ValidatorInterface { /** @@ -30,7 +34,7 @@ public function __construct(\Magento\Framework\Code\Reader\ArgumentsReader $argu * * @param string $className * @return bool - * @throws \Magento\Framework\Exception\ValidatorException + * @throws ValidatorException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -66,56 +70,78 @@ public function validate($className) $parentArguments = $this->_argumentsReader->getConstructorArguments($parent, true, true); foreach ($parentArguments as $index => $requiredArgument) { - if (isset($callArguments[$index])) { - $actualArgument = $callArguments[$index]; - } else { - if ($requiredArgument['isOptional']) { + $reIndexedCallArguments = array_column($callArguments, null, 'name'); + if (isset($reIndexedCallArguments[$requiredArgument['name']])) { + if ($reIndexedCallArguments[$requiredArgument['name']]['isNamedArgument'] === true) { + $actualArgument = $reIndexedCallArguments[$requiredArgument['name']]; + $this->checkCompatibleTypes($requiredArgument['type'], $actualArgument['type'], $class); continue; } + } - $classPath = str_replace('\\', '/', $class->getFileName()); - throw new \Magento\Framework\Exception\ValidatorException( - new Phrase( - 'Missed required argument %1 in parent::__construct call. File: %2', - [$requiredArgument['name'], $classPath] - ) - ); + if (isset($callArguments[$index]) && $callArguments[$index]['isNamedArgument'] === true) { + $this->checkIfRequiredArgumentIsOptional($requiredArgument, $class); } - $isCompatibleTypes = $this->_argumentsReader->isCompatibleType( - $requiredArgument['type'], - $actualArgument['type'] - ); - if (false == $isCompatibleTypes) { - $classPath = str_replace('\\', '/', $class->getFileName()); - throw new \Magento\Framework\Exception\ValidatorException( - new Phrase( - 'Incompatible argument type: Required type: %1. Actual type: %2; File: %3%4%5', - [$requiredArgument['type'], $actualArgument['type'], PHP_EOL, $classPath, PHP_EOL] - ) - ); + if (isset($callArguments[$index])) { + $actualArgument = $callArguments[$index]; + $this->checkCompatibleTypes($requiredArgument['type'], $actualArgument['type'], $class); + } else { + $this->checkIfRequiredArgumentIsOptional($requiredArgument, $class); } } - /** - * Try to detect unused arguments - * Check whether count of passed parameters less or equal that count of count parent class arguments - */ - if (count($callArguments) > count($parentArguments)) { - $extraParameters = array_slice($callArguments, count($parentArguments)); - $names = []; - foreach ($extraParameters as $param) { - $names[] = '$' . $param['name']; - } + return true; + } + /** + * Check argument type compatibility + * + * @param string $requiredArgumentType + * @param string $actualArgumentType + * @param \ReflectionClass $class + * @return void + * @throws ValidatorException + */ + private function checkCompatibleTypes( + $requiredArgumentType, + $actualArgumentType, + \ReflectionClass $class + ): void { + $isCompatibleTypes = $this->_argumentsReader->isCompatibleType( + $requiredArgumentType, + $actualArgumentType + ); + + if (!$isCompatibleTypes) { $classPath = str_replace('\\', '/', $class->getFileName()); - throw new \Magento\Framework\Exception\ValidatorException( + throw new ValidatorException( new Phrase( - 'Extra parameters passed to parent construct: %1. File: %2', - [implode(', ', $names), $classPath] + 'Incompatible argument type: Required type: %1. Actual type: %2; File: %3%4%5', + [$requiredArgumentType, $actualArgumentType, PHP_EOL, $classPath, PHP_EOL] + ) + ); + } + } + + /** + * Check if required argument is optional + * + * @param array $requiredArgument + * @param \ReflectionClass $class + * @return void + * @throws ValidatorException + */ + private function checkIfRequiredArgumentIsOptional(array $requiredArgument, \ReflectionClass $class): void + { + if (!$requiredArgument['isOptional']) { + $classPath = str_replace('\\', '/', $class->getFileName()); + throw new ValidatorException( + new Phrase( + 'Missed required argument %1 in parent::__construct call. File: %2', + [$requiredArgument['name'], $classPath] ) ); } - return true; } } diff --git a/lib/internal/Magento/Framework/GetReflectionMethodReturnTypeValueTrait.php b/lib/internal/Magento/Framework/GetReflectionMethodReturnTypeValueTrait.php new file mode 100644 index 0000000000000..82bc6d0d3ead9 --- /dev/null +++ b/lib/internal/Magento/Framework/GetReflectionMethodReturnTypeValueTrait.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework; + +/** + * Returns the return type of reflection method + */ +trait GetReflectionMethodReturnTypeValueTrait +{ + /** + * Get return type + * + * @param \ReflectionMethod $method + * @return string|null + */ + private function getReturnTypeValue(\ReflectionMethod $method): ?string + { + $returnTypeValue = null; + $returnType = $method->getReturnType(); + if ($returnType) { + if ($returnType instanceof \ReflectionUnionType || $returnType instanceof \ReflectionIntersectionType) { + return $this->getReturnTypeValues($returnType, $method); + } + + $className = $method->getDeclaringClass()->getName(); + $returnTypeValue = ($returnType->allowsNull() && $returnType->getName() !== 'mixed' ? '?' : ''); + $returnTypeValue .= ($returnType->getName() === 'self') + ? $className ? '\\' . ltrim($className, '\\') : '' + : $returnType->getName(); + } + + return $returnTypeValue; + } + + /** + * Get return type values for Intersection|Union types + * + * @param \ReflectionIntersectionType|\ReflectionUnionType $returnType + * @param \ReflectionMethod $method + * @return string|null + */ + private function getReturnTypeValues( + \ReflectionIntersectionType|\ReflectionUnionType $returnType, + \ReflectionMethod $method + ): ?string { + $returnTypeValue = []; + foreach ($method->getReturnType()->getTypes() as $type) { + $returnTypeValue[] = $type->getName(); + } + + return implode( + $returnType instanceof \ReflectionUnionType ? '|' : '&', + $returnTypeValue + ); + } +} diff --git a/lib/internal/Magento/Framework/Image/Adapter/Gd2.php b/lib/internal/Magento/Framework/Image/Adapter/Gd2.php index 442418ecb17fa..037a2eb56d6c1 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/Gd2.php +++ b/lib/internal/Magento/Framework/Image/Adapter/Gd2.php @@ -538,8 +538,8 @@ private function createWatermarkBasedOnPosition( } elseif ($this->getWatermarkPosition() == self::POSITION_STRETCH) { $watermark = $this->createWaterMark($watermark, $this->_imageSrcWidth, $this->_imageSrcHeight); } elseif ($this->getWatermarkPosition() == self::POSITION_CENTER) { - $positionX = $this->_imageSrcWidth / 2 - imagesx($watermark) / 2; - $positionY = $this->_imageSrcHeight / 2 - imagesy($watermark) / 2; + $positionX = (int) ($this->_imageSrcWidth / 2 - imagesx($watermark) / 2); + $positionY = (int) ($this->_imageSrcHeight / 2 - imagesy($watermark) / 2); $this->imagecopymergeWithAlphaFix( $this->_imageHandler, $watermark, diff --git a/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php b/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php index d67fe7799db22..d0b88a34c09d8 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php +++ b/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php @@ -334,8 +334,8 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = $watermark->sampleImage($this->_imageSrcWidth, $this->_imageSrcHeight); break; case self::POSITION_CENTER: - $positionX = ($this->_imageSrcWidth - $watermark->getImageWidth()) / 2; - $positionY = ($this->_imageSrcHeight - $watermark->getImageHeight()) / 2; + $positionX = (int) (($this->_imageSrcWidth - $watermark->getImageWidth()) / 2); + $positionY = (int) (($this->_imageSrcHeight - $watermark->getImageHeight()) / 2); break; case self::POSITION_TOP_RIGHT: $positionX = $this->_imageSrcWidth - $watermark->getImageWidth(); diff --git a/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php b/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php index 4b8685facad7c..f04bea1030ade 100644 --- a/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php +++ b/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php @@ -8,9 +8,12 @@ namespace Magento\Framework\Interception\Code\Generator; use Magento\Framework\Code\Generator\EntityAbstract; +use Magento\Framework\GetReflectionMethodReturnTypeValueTrait; class Interceptor extends EntityAbstract { + use GetReflectionMethodReturnTypeValueTrait; + public const ENTITY_TYPE = 'interceptor'; /** @@ -200,24 +203,4 @@ protected function _validateData() return $result; } - - /** - * Returns return type - * - * @param \ReflectionMethod $method - * @return null|string - */ - private function getReturnTypeValue(\ReflectionMethod $method): ?string - { - $returnTypeValue = null; - $returnType = $method->getReturnType(); - if ($returnType) { - $returnTypeValue = ($returnType->allowsNull() && $returnType->getName() !== 'mixed' ? '?' : ''); - $returnTypeValue .= ($returnType->getName() === 'self') - ? $this->_getFullyQualifiedClassName($method->getDeclaringClass()->getName()) - : $returnType->getName(); - } - - return $returnTypeValue; - } } diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/InterceptorTest.php b/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/InterceptorTest.php index 3a8553803a2be..6b38b36384bc8 100644 --- a/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/InterceptorTest.php +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/InterceptorTest.php @@ -10,9 +10,17 @@ use Composer\Autoload\ClassLoader; use Magento\Framework\Code\Generator\Io; use Magento\Framework\Interception\Code\Generator\Interceptor; +use Magento\Framework\Interception\Code\Generator\ReflectionIntersectionTypeSample; +use Magento\Framework\Interception\Code\Generator\ReflectionUnionTypeSample; +use Magento\Framework\Interception\Code\Generator\Sample; +use Magento\Framework\Interception\Code\Generator\SampleBackendMenu; +use Magento\Framework\Interception\Code\Generator\TSample; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class InterceptorTest extends TestCase { /** @@ -82,20 +90,30 @@ public function interceptorDataProvider() { return [ [ - \Magento\Framework\Interception\Code\Generator\Sample::class, - \Magento\Framework\Interception\Code\Generator\Sample\Interceptor::class, + Sample::class, + Sample\Interceptor::class, 'Interceptor' ], [ - \Magento\Framework\Interception\Code\Generator\TSample::class, - \Magento\Framework\Interception\Code\Generator\TSample\Interceptor::class, + TSample::class, + TSample\Interceptor::class, 'TInterceptor' ], [ - \Magento\Framework\Interception\Code\Generator\SampleBackendMenu::class, - \Magento\Framework\Interception\Code\Generator\SampleBackendMenu\Interceptor::class, + SampleBackendMenu::class, + SampleBackendMenu\Interceptor::class, 'SampleBackendMenuInterceptor', ], + [ + ReflectionUnionTypeSample::class, + ReflectionUnionTypeSample\Interceptor::class, + 'ReflectionUnionTypeSampleInterceptor', + ], + [ + ReflectionIntersectionTypeSample::class, + ReflectionIntersectionTypeSample\Interceptor::class, + 'ReflectionIntersectionTypeSampleInterceptor', + ], ]; } } diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/_files/ReflectionIntersectionTypeSample.php b/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/_files/ReflectionIntersectionTypeSample.php new file mode 100644 index 0000000000000..a9bfb3389d5b1 --- /dev/null +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/_files/ReflectionIntersectionTypeSample.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Interception\Code\Generator; + +use Magento\Backend\Model\Menu; + +class ReflectionIntersectionTypeSample extends Menu +{ + /** + * Intersection type attribute + * + * @var ReflectionIntersectionTypeSample&Menu + */ + private ReflectionIntersectionTypeSample&Menu $attribute; + + /** + * @return ReflectionIntersectionTypeSample&Menu + */ + public function getValue(): ReflectionIntersectionTypeSample&Menu + { + return $this->attribute; + } + + /** + * @param ReflectionIntersectionTypeSample&Menu $value + */ + public function setValue(ReflectionIntersectionTypeSample&Menu $value) + { + $this->attribute = $value; + } +} diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/_files/ReflectionIntersectionTypeSampleInterceptor.txt b/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/_files/ReflectionIntersectionTypeSampleInterceptor.txt new file mode 100644 index 0000000000000..e6e41d69196ea --- /dev/null +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/_files/ReflectionIntersectionTypeSampleInterceptor.txt @@ -0,0 +1,141 @@ +namespace Magento\Framework\Interception\Code\Generator\ReflectionIntersectionTypeSample; + +/** + * Interceptor class for @see \Magento\Framework\Interception\Code\Generator\ReflectionIntersectionTypeSample + */ +class Interceptor extends \Magento\Framework\Interception\Code\Generator\ReflectionIntersectionTypeSample implements \Magento\Framework\Interception\InterceptorInterface +{ + use \Magento\Framework\Interception\Interceptor; + + public function __construct(\Psr\Log\LoggerInterface $logger, $pathInMenuStructure = '', ?\Magento\Backend\Model\Menu\Item\Factory $menuItemFactory = null, ?\Magento\Framework\Serialize\SerializerInterface $serializer = null) + { + $this->___init(); + parent::__construct($logger, $pathInMenuStructure, $menuItemFactory, $serializer); + } + + /** + * {@inheritdoc} + */ + public function getValue() : \Magento\Backend\Model\Menu&\Magento\Framework\Interception\Code\Generator\ReflectionIntersectionTypeSample + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'getValue'); + return $pluginInfo ? $this->___callPlugins('getValue', func_get_args(), $pluginInfo) : parent::getValue(); + } + + /** + * {@inheritdoc} + */ + public function setValue(\Magento\Backend\Model\Menu&\Magento\Framework\Interception\Code\Generator\ReflectionIntersectionTypeSample $value) + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'setValue'); + return $pluginInfo ? $this->___callPlugins('setValue', func_get_args(), $pluginInfo) : parent::setValue($value); + } + + /** + * {@inheritdoc} + */ + public function add(\Magento\Backend\Model\Menu\Item $item, $parentId = null, $index = null) + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'add'); + return $pluginInfo ? $this->___callPlugins('add', func_get_args(), $pluginInfo) : parent::add($item, $parentId, $index); + } + + /** + * {@inheritdoc} + */ + public function get($itemId) + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'get'); + return $pluginInfo ? $this->___callPlugins('get', func_get_args(), $pluginInfo) : parent::get($itemId); + } + + /** + * {@inheritdoc} + */ + public function move($itemId, $toItemId, $sortIndex = null) + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'move'); + return $pluginInfo ? $this->___callPlugins('move', func_get_args(), $pluginInfo) : parent::move($itemId, $toItemId, $sortIndex); + } + + /** + * {@inheritdoc} + */ + public function remove($itemId) + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'remove'); + return $pluginInfo ? $this->___callPlugins('remove', func_get_args(), $pluginInfo) : parent::remove($itemId); + } + + /** + * {@inheritdoc} + */ + public function reorder($itemId, $position) + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'reorder'); + return $pluginInfo ? $this->___callPlugins('reorder', func_get_args(), $pluginInfo) : parent::reorder($itemId, $position); + } + + /** + * {@inheritdoc} + */ + public function isLast(\Magento\Backend\Model\Menu\Item $item) + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'isLast'); + return $pluginInfo ? $this->___callPlugins('isLast', func_get_args(), $pluginInfo) : parent::isLast($item); + } + + /** + * {@inheritdoc} + */ + public function getFirstAvailable() + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'getFirstAvailable'); + return $pluginInfo ? $this->___callPlugins('getFirstAvailable', func_get_args(), $pluginInfo) : parent::getFirstAvailable(); + } + + /** + * {@inheritdoc} + */ + public function getParentItems($itemId) + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'getParentItems'); + return $pluginInfo ? $this->___callPlugins('getParentItems', func_get_args(), $pluginInfo) : parent::getParentItems($itemId); + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'serialize'); + return $pluginInfo ? $this->___callPlugins('serialize', func_get_args(), $pluginInfo) : parent::serialize(); + } + + /** + * {@inheritdoc} + */ + public function toArray() + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'toArray'); + return $pluginInfo ? $this->___callPlugins('toArray', func_get_args(), $pluginInfo) : parent::toArray(); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'unserialize'); + return $pluginInfo ? $this->___callPlugins('unserialize', func_get_args(), $pluginInfo) : parent::unserialize($serialized); + } + + /** + * {@inheritdoc} + */ + public function populateFromArray(array $data) + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'populateFromArray'); + return $pluginInfo ? $this->___callPlugins('populateFromArray', func_get_args(), $pluginInfo) : parent::populateFromArray($data); + } +} diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/_files/ReflectionUnionTypeSample.php b/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/_files/ReflectionUnionTypeSample.php new file mode 100644 index 0000000000000..720adbb2dc527 --- /dev/null +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/_files/ReflectionUnionTypeSample.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Interception\Code\Generator; + +class ReflectionUnionTypeSample +{ + /** + * Union type attribute + * + * @var int|string + */ + private int|string $attribute; + + public function getValue(): int|string + { + return $this->attribute; + } + + /** + * @param int|string $value + */ + public function setValue(int|string $value) + { + $this->attribute = $value; + } +} diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/_files/ReflectionUnionTypeSampleInterceptor.txt b/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/_files/ReflectionUnionTypeSampleInterceptor.txt new file mode 100644 index 0000000000000..67a8fc808f3f5 --- /dev/null +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/Code/Generator/_files/ReflectionUnionTypeSampleInterceptor.txt @@ -0,0 +1,32 @@ +namespace Magento\Framework\Interception\Code\Generator\ReflectionUnionTypeSample; + +/** + * Interceptor class for @see \Magento\Framework\Interception\Code\Generator\ReflectionUnionTypeSample + */ +class Interceptor extends \Magento\Framework\Interception\Code\Generator\ReflectionUnionTypeSample implements \Magento\Framework\Interception\InterceptorInterface +{ + use \Magento\Framework\Interception\Interceptor; + + public function __construct() + { + $this->___init(); + } + + /** + * {@inheritdoc} + */ + public function getValue() : int|string + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'getValue'); + return $pluginInfo ? $this->___callPlugins('getValue', func_get_args(), $pluginInfo) : parent::getValue(); + } + + /** + * {@inheritdoc} + */ + public function setValue(int|string $value) + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'setValue'); + return $pluginInfo ? $this->___callPlugins('setValue', func_get_args(), $pluginInfo) : parent::setValue($value); + } +} diff --git a/lib/internal/Magento/Framework/Logger/LoggerProxy.php b/lib/internal/Magento/Framework/Logger/LoggerProxy.php index 0a589f3e9f4f0..0d08e83f2735a 100644 --- a/lib/internal/Magento/Framework/Logger/LoggerProxy.php +++ b/lib/internal/Magento/Framework/Logger/LoggerProxy.php @@ -99,6 +99,7 @@ private function getLogger(): LoggerInterface */ public function emergency($message, array $context = []) { + $context = $this->addExceptionToContext($message, $context); $this->getLogger()->emergency($message, $context); } @@ -107,6 +108,7 @@ public function emergency($message, array $context = []) */ public function alert($message, array $context = []) { + $context = $this->addExceptionToContext($message, $context); $this->getLogger()->alert($message, $context); } @@ -115,6 +117,7 @@ public function alert($message, array $context = []) */ public function critical($message, array $context = []) { + $context = $this->addExceptionToContext($message, $context); $this->getLogger()->critical($message, $context); } @@ -123,6 +126,7 @@ public function critical($message, array $context = []) */ public function error($message, array $context = []) { + $context = $this->addExceptionToContext($message, $context); $this->getLogger()->error($message, $context); } @@ -131,6 +135,7 @@ public function error($message, array $context = []) */ public function warning($message, array $context = []) { + $context = $this->addExceptionToContext($message, $context); $this->getLogger()->warning($message, $context); } @@ -139,6 +144,7 @@ public function warning($message, array $context = []) */ public function notice($message, array $context = []) { + $context = $this->addExceptionToContext($message, $context); $this->getLogger()->notice($message, $context); } @@ -147,6 +153,7 @@ public function notice($message, array $context = []) */ public function info($message, array $context = []) { + $context = $this->addExceptionToContext($message, $context); $this->getLogger()->info($message, $context); } @@ -155,6 +162,7 @@ public function info($message, array $context = []) */ public function debug($message, array $context = []) { + $context = $this->addExceptionToContext($message, $context); $this->getLogger()->debug($message, $context); } @@ -163,6 +171,22 @@ public function debug($message, array $context = []) */ public function log($level, $message, array $context = []) { + $context = $this->addExceptionToContext($message, $context); $this->getLogger()->log($level, $message, $context); } + + /** + * Ensure exception logging by adding it to context + * + * @param mixed $message + * @param array $context + * @return array + */ + protected function addExceptionToContext($message, array $context = []): array + { + if ($message instanceof \Throwable && !isset($context['exception'])) { + $context['exception'] = $message; + } + return $context; + } } diff --git a/lib/internal/Magento/Framework/Logger/Test/Unit/LoggerProxyTest.php b/lib/internal/Magento/Framework/Logger/Test/Unit/LoggerProxyTest.php index 582c940ade51e..16134f226fb5a 100644 --- a/lib/internal/Magento/Framework/Logger/Test/Unit/LoggerProxyTest.php +++ b/lib/internal/Magento/Framework/Logger/Test/Unit/LoggerProxyTest.php @@ -99,4 +99,36 @@ public function createWithArguments(): void $loggerProxy = new LoggerProxy($objectManager); $loggerProxy->log(LogLevel::ALERT, 'test'); } + + /** + * @test + * + * @param $method + * + * @return void + * @dataProvider methodsList + */ + public function logException($method): void + { + $deploymentConfig = $this->getMockBuilder(DeploymentConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManager = $this->getMockBuilder(ObjectManagerInterface::class) + ->getMockForAbstractClass(); + + $logger = $this->getMockBuilder(LoggerInterface::class) + ->getMockForAbstractClass(); + + $objectManager + ->method('get') + ->withConsecutive([DeploymentConfig::class], [Monolog::class]) + ->willReturnOnConsecutiveCalls($deploymentConfig, $logger); + + $message = new \Exception('This is an exception.'); + + $logger->expects($this->once())->method($method)->with($message); + + $loggerProxy = new LoggerProxy($objectManager); + $loggerProxy->$method($message); + } } diff --git a/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php b/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php index b68a242b50722..a6865c44d57f3 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php +++ b/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php @@ -5,13 +5,13 @@ */ namespace Magento\Framework\MessageQueue\Code\Generator; +use Laminas\Code\Reflection\MethodReflection; use Magento\Framework\Code\Generator\DefinedClasses; use Magento\Framework\Code\Generator\Io; use Magento\Framework\Communication\Config\ReflectionGenerator; use Magento\Framework\Communication\ConfigInterface as CommunicationConfig; use Magento\Framework\MessageQueue\Code\Generator\Config\RemoteServiceReader\Communication as RemoteServiceReader; use Magento\Framework\Reflection\MethodsMap as ServiceMethodsMap; -use Laminas\Code\Reflection\MethodReflection; /** * Code generator for remote services. @@ -20,8 +20,8 @@ */ class RemoteServiceGenerator extends \Magento\Framework\Code\Generator\EntityAbstract { - const ENTITY_TYPE = 'remote'; - const REMOTE_SERVICE_SUFFIX = 'Remote'; + public const ENTITY_TYPE = 'remote'; + public const REMOTE_SERVICE_SUFFIX = 'Remote'; /** * @var CommunicationConfig @@ -173,7 +173,21 @@ protected function _getClassMethods() */ private function convertMethodType($type) { - return $type instanceof \ReflectionNamedType ? $type->getName() : $type; + $returnTypeValue = $type instanceof \ReflectionNamedType ? $type->getName() : $type; + + if ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) { + $returnTypeValue = []; + foreach ($type->getTypes() as $type) { + $returnTypeValue[] = $type->getName(); + } + + $returnTypeValue = implode( + $type instanceof \ReflectionUnionType ? '|' : '&', + $returnTypeValue + ); + } + + return $returnTypeValue; } /** @@ -230,6 +244,7 @@ protected function validateResultClassName() * @return ReflectionGenerator * * @deprecated 103.0.0 + * @see https://jira.corp.adobe.com/browse/MAGETWO-56305 */ private function getReflectionGenerator() { diff --git a/lib/internal/Magento/Framework/MessageQueue/composer.json b/lib/internal/Magento/Framework/MessageQueue/composer.json index 9039e5a8775b2..07cce7c905463 100644 --- a/lib/internal/Magento/Framework/MessageQueue/composer.json +++ b/lib/internal/Magento/Framework/MessageQueue/composer.json @@ -11,7 +11,7 @@ ], "require": { "magento/framework": "*", - "php": "~7.4.0||~8.1.0" + "php": "~8.1.0||~8.2.0" }, "autoload": { "psr-4": { diff --git a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php index f9bf616681cb6..e1ae712c2af1a 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php +++ b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php @@ -7,8 +7,12 @@ namespace Magento\Framework\ObjectManager\Code\Generator; +use Magento\Framework\GetReflectionMethodReturnTypeValueTrait; + class Proxy extends \Magento\Framework\Code\Generator\EntityAbstract { + use GetReflectionMethodReturnTypeValueTrait; + /** * Entity type */ @@ -268,24 +272,4 @@ protected function _validateData() return $result; } - - /** - * Returns return type - * - * @param \ReflectionMethod $method - * @return null|string - */ - private function getReturnTypeValue(\ReflectionMethod $method): ?string - { - $returnTypeValue = null; - $returnType = $method->getReturnType(); - if ($returnType) { - $returnTypeValue = ($returnType->allowsNull() && $returnType->getName() !== 'mixed' ? '?' : ''); - $returnTypeValue .= ($returnType->getName() === 'self') - ? $this->_getFullyQualifiedClassName($method->getDeclaringClass()->getName()) - : $returnType->getName(); - } - - return $returnTypeValue; - } } diff --git a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php index 645e3fd7eff78..405b0f619da30 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php +++ b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php @@ -18,13 +18,14 @@ * Class Repository * @deprecated 101.0.0 As current implementation breaks Repository contract. Not removed from codebase to prevent * possible backward incompatibilities if this functionality being used by 3rd party developers. + * @see https://jira.corp.adobe.com/browse/MAGETWO-70985 */ class Repository extends \Magento\Framework\Code\Generator\EntityAbstract { /** * Entity type repository */ - const ENTITY_TYPE = 'repository'; + public const ENTITY_TYPE = 'repository'; /** * The namespace of repository interface @@ -750,6 +751,20 @@ private function getClassMethods($name) */ private function getTypeHintText($type) { - return $type instanceof \ReflectionType ? $type->getName() : $type; + $returnTypeValue = $type instanceof \ReflectionType ? $type->getName() : $type; + + if ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) { + $returnTypeValue = []; + foreach ($type->getTypes() as $type) { + $returnTypeValue[] = $type->getName(); + } + + $returnTypeValue = implode( + $type instanceof \ReflectionUnionType ? '|' : '&', + $returnTypeValue + ); + } + + return $returnTypeValue; } } diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/_files/Sample.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/_files/Sample.php index b51351e46fa2e..ae873142a1705 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/_files/Sample.php +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/_files/Sample.php @@ -22,6 +22,13 @@ class Sample */ private $config = []; + /** + * Union type attribute + * + * @var int|string + */ + private int|string $attribute; + /** * @param array $messages */ @@ -53,4 +60,20 @@ public function getConfig(): array { return $this->config; } + + /** + * @param int|string $attribute + */ + public function setAttribute(int|string $attribute) + { + $this->attribute = $attribute; + } + + /** + * @return int|string + */ + public function getAttribute(): int|string + { + return $this->attribute; + } } diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/_files/SampleProxy.txt b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/_files/SampleProxy.txt index 2c56472f323cf..6ee7bdcbaf3a3 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/_files/SampleProxy.txt +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/_files/SampleProxy.txt @@ -117,4 +117,20 @@ class Sample_Proxy extends Sample implements \Magento\Framework\ObjectManager\No { return $this->_getSubject()->getConfig(); } + + /** + * {@inheritdoc} + */ + public function setAttribute(int|string $attribute) + { + return $this->_getSubject()->setAttribute($attribute); + } + + /** + * {@inheritdoc} + */ + public function getAttribute() : int|string + { + return $this->_getSubject()->getAttribute(); + } } diff --git a/lib/internal/Magento/Framework/Reflection/DataObjectProcessor.php b/lib/internal/Magento/Framework/Reflection/DataObjectProcessor.php index 967826be09ceb..94023d129d1db 100644 --- a/lib/internal/Magento/Framework/Reflection/DataObjectProcessor.php +++ b/lib/internal/Magento/Framework/Reflection/DataObjectProcessor.php @@ -46,6 +46,11 @@ class DataObjectProcessor */ private $processors; + /** + * @var array[] + */ + private $excludedMethodsClassMap; + /** * @param MethodsMap $methodsMapProcessor * @param TypeCaster $typeCaster @@ -53,6 +58,7 @@ class DataObjectProcessor * @param CustomAttributesProcessor $customAttributesProcessor * @param ExtensionAttributesProcessor $extensionAttributesProcessor * @param array $processors + * @param array $excludedMethodsClassMap */ public function __construct( MethodsMap $methodsMapProcessor, @@ -60,7 +66,8 @@ public function __construct( FieldNamer $fieldNamer, CustomAttributesProcessor $customAttributesProcessor, ExtensionAttributesProcessor $extensionAttributesProcessor, - array $processors = [] + array $processors = [], + array $excludedMethodsClassMap = [] ) { $this->methodsMapProcessor = $methodsMapProcessor; $this->typeCaster = $typeCaster; @@ -68,6 +75,7 @@ public function __construct( $this->extensionAttributesProcessor = $extensionAttributesProcessor; $this->customAttributesProcessor = $customAttributesProcessor; $this->processors = $processors; + $this->excludedMethodsClassMap = $excludedMethodsClassMap; } /** @@ -84,7 +92,13 @@ public function buildOutputDataArray($dataObject, $dataObjectType) $methods = $this->methodsMapProcessor->getMethodsMap($dataObjectType); $outputData = []; + $excludedMethodsForDataObjectType = $this->excludedMethodsClassMap[$dataObjectType] ?? []; + foreach (array_keys($methods) as $methodName) { + if (in_array($methodName, $excludedMethodsForDataObjectType)) { + continue; + } + if (!$this->methodsMapProcessor->isMethodValidForDataField($dataObjectType, $methodName)) { continue; } diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/DataObjectProcessorTest.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/DataObjectProcessorTest.php index 8e55d4395d20d..620b3684b4abd 100644 --- a/lib/internal/Magento/Framework/Reflection/Test/Unit/DataObjectProcessorTest.php +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/DataObjectProcessorTest.php @@ -26,6 +26,11 @@ class DataObjectProcessorTest extends TestCase */ private $dataObjectProcessor; + /** + * @var MethodsMap + */ + private $methodsMapProcessor; + /** * @var ExtensionAttributesProcessor|MockObject */ @@ -34,7 +39,7 @@ class DataObjectProcessorTest extends TestCase protected function setUp(): void { $objectManager = new ObjectManager($this); - $methodsMapProcessor = $objectManager->getObject( + $this->methodsMapProcessor = $objectManager->getObject( MethodsMap::class, [ 'fieldNamer' => $objectManager->getObject(FieldNamer::class), @@ -48,7 +53,7 @@ protected function setUp(): void ->willReturn(['unserializedData']); $objectManager->setBackwardCompatibleProperty( - $methodsMapProcessor, + $this->methodsMapProcessor, 'serializer', $serializerMock ); @@ -56,27 +61,32 @@ protected function setUp(): void $this->extensionAttributesProcessorMock = $this->getMockBuilder(ExtensionAttributesProcessor::class) ->disableOriginalConstructor() ->getMock(); + } + + /** + * @param array $extensionAttributes + * @param array $excludedMethodsClassMap + * @param array $expectedOutput + * @dataProvider buildOutputDataArrayDataProvider + */ + public function testBuildOutputDataArray( + array $extensionAttributes, + array $excludedMethodsClassMap, + array $expectedOutput + ) { + $objectManager = new ObjectManager($this); $this->dataObjectProcessor = $objectManager->getObject( DataObjectProcessor::class, [ - 'methodsMapProcessor' => $methodsMapProcessor, + 'methodsMapProcessor' => $this->methodsMapProcessor, 'typeCaster' => $objectManager->getObject(TypeCaster::class), 'fieldNamer' => $objectManager->getObject(FieldNamer::class), - 'extensionAttributesProcessor' => $this->extensionAttributesProcessorMock + 'extensionAttributesProcessor' => $this->extensionAttributesProcessorMock, + 'excludedMethodsClassMap' => $excludedMethodsClassMap, ] ); - } - /** - * @param array $extensionAttributes - * @param array $expectedOutputDataArray - * - * @dataProvider buildOutputDataArrayDataProvider - */ - public function testBuildOutputDataArray($extensionAttributes, $expectedOutputDataArray) - { - $objectManager = new ObjectManager($this); /** @var TestDataObject $testDataObject */ $testDataObject = $objectManager->getObject( TestDataObject::class, @@ -87,13 +97,19 @@ public function testBuildOutputDataArray($extensionAttributes, $expectedOutputDa ] ); - $this->extensionAttributesProcessorMock->expects($this->once()) + if (in_array('getExtensionAttributes', $excludedMethodsClassMap[TestDataInterface::class] ?? [])) { + $expectedTimes = $this->never(); + } else { + $expectedTimes = $this->once(); + } + + $this->extensionAttributesProcessorMock->expects($expectedTimes) ->method('buildOutputDataArray') ->willReturn($extensionAttributes); $outputData = $this->dataObjectProcessor ->buildOutputDataArray($testDataObject, TestDataInterface::class); - $this->assertEquals($expectedOutputDataArray, $outputData); + $this->assertEquals($expectedOutput, $outputData); } /** @@ -101,26 +117,50 @@ public function testBuildOutputDataArray($extensionAttributes, $expectedOutputDa */ public function buildOutputDataArrayDataProvider() { - $expectedOutputDataArray = [ + $expectedOutput = [ 'id' => '1', 'address' => 'someAddress', 'default_shipping' => 'true', 'required_billing' => 'false', ]; - $extensionAttributeArray = [ + + $extensionAttributes = [ 'attribute1' => 'value1', - 'attribute2' => 'value2' + 'attribute2' => 'value2', ]; return [ - 'No Attributes' => [[], $expectedOutputDataArray], - 'With Attributes' => [ - $extensionAttributeArray, + 'No Extension Attributes or Excluded Methods' => [ + [], + [], + $expectedOutput, + ], + 'With Extension Attributes' => [ + $extensionAttributes, + [], array_merge( - $expectedOutputDataArray, - ['extension_attributes' => $extensionAttributeArray] - ) - ] + $expectedOutput, + ['extension_attributes' => $extensionAttributes] + ), + ], + 'With Excluded Method' => [ + [], + [ + TestDataInterface::class => [ + 'getAddress', + ], + ], + array_diff_key($expectedOutput, array_flip(['address'])), + ], + 'With getExtensionAttributes as Excluded Method' => [ + $extensionAttributes, + [ + TestDataInterface::class => [ + 'getExtensionAttributes', + ], + ], + $expectedOutput, + ], ]; } } diff --git a/lib/internal/Magento/Framework/Reflection/TypeProcessor.php b/lib/internal/Magento/Framework/Reflection/TypeProcessor.php index 8591e2f096a90..b513acb019d17 100644 --- a/lib/internal/Magento/Framework/Reflection/TypeProcessor.php +++ b/lib/internal/Magento/Framework/Reflection/TypeProcessor.php @@ -59,7 +59,8 @@ class TypeProcessor * * @return NameFinder * - * @deprecated 100.1.0 + * @deprecated 100.1.0 Refactor TypeProcessor + * @see https://jira.corp.adobe.com/browse/MAGETWO-51906 */ private function getNameFinder() { @@ -254,7 +255,8 @@ public function getDescription(DocBlockReflection $doc) * @param string $getterName * @return string * - * @deprecated 100.1.0 + * @deprecated 100.1.0 Refactor TypeProcessor + * @see https://jira.corp.adobe.com/browse/MAGETWO-51906 */ public function dataObjectGetterNameToFieldName($getterName) { @@ -267,7 +269,8 @@ public function dataObjectGetterNameToFieldName($getterName) * @param string $shortDescription * @return string * - * @deprecated 100.1.0 + * @deprecated 100.1.0 Refactor TypeProcessor + * @see https://jira.corp.adobe.com/browse/MAGETWO-51906 */ protected function dataObjectGetterDescriptionToFieldDescription($shortDescription) { @@ -547,7 +550,7 @@ public function getParamType(ParameterReflection $param) return strpos($paramType, '[]') !== false ? $paramType : "{$paramType}[]"; } - return $this->resolveFullyQualifiedClassName($param->getDeclaringClass(), $type); + return $this->resolveFullyQualifiedClassName($param->getDeclaringClass(), $type ?? ''); } /** @@ -701,7 +704,8 @@ public function getParamDescription(ParameterReflection $param) * @return string processed method name * @throws \Exception If $camelCaseProperty has no corresponding getter method * - * @deprecated 100.1.0 + * @deprecated 100.1.0 Refactor TypeProcessor + * @see https://jira.corp.adobe.com/browse/MAGETWO-51906 */ public function findGetterMethodName(ClassReflection $class, $camelCaseProperty) { @@ -739,7 +743,8 @@ protected function setType(&$value, $type) * @return string processed method name * @throws \Exception If $camelCaseProperty has no corresponding setter method * - * @deprecated 100.1.0 + * @deprecated 100.1.0 Refactor TypeProcessor + * @see https://jira.corp.adobe.com/browse/MAGETWO-51906 */ public function findSetterMethodName(ClassReflection $class, $camelCaseProperty) { @@ -756,7 +761,8 @@ public function findSetterMethodName(ClassReflection $class, $camelCaseProperty) * @return string processed method name * @throws \Exception If $camelCaseProperty has no corresponding setter method * - * @deprecated 100.1.0 + * @deprecated 100.1.0 Refactor TypeProcessor + * @see https://jira.corp.adobe.com/browse/MAGETWO-51906 */ protected function findAccessorMethodName( ClassReflection $class, @@ -777,7 +783,8 @@ protected function findAccessorMethodName( * @param string $methodName * @return bool * - * @deprecated 100.1.0 + * @deprecated 100.1.0 Refactor TypeProcessor + * @see https://jira.corp.adobe.com/browse/MAGETWO-51906 */ protected function classHasMethod(ClassReflection $class, $methodName) { diff --git a/lib/internal/Magento/Framework/View/Asset/PreProcessor/FileNameResolver.php b/lib/internal/Magento/Framework/View/Asset/PreProcessor/FileNameResolver.php index 6f2ca8544dded..3777699421ce8 100644 --- a/lib/internal/Magento/Framework/View/Asset/PreProcessor/FileNameResolver.php +++ b/lib/internal/Magento/Framework/View/Asset/PreProcessor/FileNameResolver.php @@ -36,20 +36,17 @@ function (AlternativeSourceInterface $alternativeSource) { * @param string $fileName * @return string */ - public function resolve($fileName) + public function resolve(string $fileName): string { $compiledFile = $fileName; $extension = pathinfo($fileName, PATHINFO_EXTENSION); foreach ($this->alternativeSources as $name => $alternative) { - if ($alternative->isExtensionSupported($extension) - && strpos(basename($fileName), '_') !== 0 - ) { - $compiledFile = $fileName !== null - ? substr($fileName, 0, strlen($fileName) - strlen($extension) - 1) - : ''; - $compiledFile = $compiledFile . '.' . $name; + if ($alternative->isExtensionSupported($extension) && !str_starts_with(basename($fileName), '_')) { + $compiledFile = substr($fileName, 0, strlen($fileName) - strlen($extension) - 1); + $compiledFile = sprintf('%s.%s', $compiledFile, $name); } } + return $compiledFile; } } diff --git a/lib/internal/Magento/Framework/composer.json b/lib/internal/Magento/Framework/composer.json index 03ab3457c43a2..35686828de8cf 100644 --- a/lib/internal/Magento/Framework/composer.json +++ b/lib/internal/Magento/Framework/composer.json @@ -10,7 +10,7 @@ "sort-packages": true }, "require": { - "php": "~7.4.0||~8.1.0", + "php": "~8.1.0||~8.2.0", "ext-bcmath": "*", "ext-curl": "*", "ext-dom": "*", @@ -23,34 +23,33 @@ "ext-sodium": "*", "ext-xsl": "*", "lib-libxml": "*", - "colinmollenhour/php-redis-session-abstract": "v1.5.0", + "colinmollenhour/php-redis-session-abstract": "^1.5", "composer/composer": "^2.0, !=2.2.16", "ezyang/htmlpurifier": "^4.14", - "guzzlehttp/guzzle": "^7.4.2", - "laminas/laminas-code": "~4.5.0", - "laminas/laminas-escaper": "~2.10.0", - "laminas/laminas-file": "^2.11.0", - "laminas/laminas-filter": "^2.17.0", - "laminas/laminas-http": "^2.15.0", - "laminas/laminas-i18n": "^2.17.0", - "laminas/laminas-mail": "^2.16.0", - "laminas/laminas-mime": "^2.9.1", - "laminas/laminas-permissions-acl": "^2.10.0", - "laminas/laminas-stdlib": "^3.11.0", + "guzzlehttp/guzzle": "^7.4", + "laminas/laminas-code": "^4.5", + "laminas/laminas-escaper": "^2.10", + "laminas/laminas-file": "^2.11", + "laminas/laminas-filter": "^2.17", + "laminas/laminas-http": "^2.15", + "laminas/laminas-i18n": "^2.17", + "laminas/laminas-mail": "^2.16", + "laminas/laminas-mime": "^2.9", + "laminas/laminas-permissions-acl": "^2.10", + "laminas/laminas-stdlib": "^3.11", "laminas/laminas-oauth": "^2.4", - "laminas/laminas-uri": "^2.9.1", - "laminas/laminas-validator": "^2.23.0", - "magento/composer-dependency-version-audit-plugin": "~0.1", - "magento/zendframework1": "~1.15.0", + "laminas/laminas-uri": "^2.9", + "laminas/laminas-validator": "^2.23", + "magento/composer-dependency-version-audit-plugin": "^0.1", + "magento/zendframework1": "^1.15", "monolog/monolog": "^2.7", - "ramsey/uuid": "~4.2.0", - "symfony/console": "~5.4.12", - "symfony/string": "~5.4.12", - "symfony/intl": "~5.4.11", - "symfony/process": "~5.4.8", - "tedivm/jshrink": "~1.4.0", - "webonyx/graphql-php": "~14.11.6", - "wikimedia/less.php": "^3.0.0" + "ramsey/uuid": "^4.2", + "symfony/console": "^5.4", + "symfony/intl": "^5.4", + "symfony/process": "^5.4", + "tedivm/jshrink": "^1.4", + "webonyx/graphql-php": "^14.11", + "wikimedia/less.php": "^3.0" }, "archive": { "exclude": [ diff --git a/lib/web/jquery/fileUploader/jquery.fileupload-process.js b/lib/web/jquery/fileUploader/jquery.fileupload-process.js index a2f1009e508d1..a65ee91400161 100644 --- a/lib/web/jquery/fileUploader/jquery.fileupload-process.js +++ b/lib/web/jquery/fileUploader/jquery.fileupload-process.js @@ -120,7 +120,7 @@ options.processQueue = processQueue; }, - // Returns the number of files currently in the processsing queue: + // Returns the number of files currently in the processing queue: processing: function () { return this._processing; }, diff --git a/lib/web/jquery/fileUploader/jquery.fileupload.js b/lib/web/jquery/fileUploader/jquery.fileupload.js index 8f0ff0d4faf03..48e836e91c122 100644 --- a/lib/web/jquery/fileUploader/jquery.fileupload.js +++ b/lib/web/jquery/fileUploader/jquery.fileupload.js @@ -455,7 +455,7 @@ _initProgressListener: function (options) { var that = this, xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); - // Accesss to the native XHR object is required to add event listeners + // Access to the native XHR object is required to add event listeners // for the upload progress event: if (xhr.upload) { $(xhr.upload).on('progress', function (e) { @@ -489,15 +489,14 @@ name = String(name); if (map[name]) { // eslint-disable-next-line no-param-reassign - name = name.replace(/(?: \(([\d]+)\))?(\.[^.]+)?$/, function ( - _, - p1, - p2 - ) { - var index = p1 ? Number(p1) + 1 : 1; - var ext = p2 || ''; - return ' (' + index + ')' + ext; - }); + name = name.replace( + /(?: \(([\d]+)\))?(\.[^.]+)?$/, + function (_, p1, p2) { + var index = p1 ? Number(p1) + 1 : 1; + var ext = p2 || ''; + return ' (' + index + ')' + ext; + } + ); return this._getUniqueFilename(name, map); } map[name] = true; @@ -1172,7 +1171,7 @@ data.fileInputClone = inputClone; $('<form></form>').append(inputClone)[0].reset(); // Detaching allows to insert the fileInput on another form - // without loosing the file input value: + // without losing the file input value: input.after(inputClone).detach(); // If the fileInput had focus before it was detached, // restore focus to the inputClone. @@ -1298,8 +1297,7 @@ _getSingleFileInputFiles: function (fileInput) { // eslint-disable-next-line no-param-reassign fileInput = $(fileInput); - var entries = - fileInput.prop('webkitEntries') || fileInput.prop('entries'), + var entries = fileInput.prop('entries'), files, value; if (entries && entries.length) { diff --git a/lib/web/jquery/fileUploader/vendor/jquery.ui.widget.js b/lib/web/jquery/fileUploader/vendor/jquery.ui.widget.js index 69096aaa35ef4..78e6255728ec3 100644 --- a/lib/web/jquery/fileUploader/vendor/jquery.ui.widget.js +++ b/lib/web/jquery/fileUploader/vendor/jquery.ui.widget.js @@ -660,9 +660,8 @@ ) { return; } - return (typeof handler === 'string' - ? instance[handler] - : handler + return ( + typeof handler === 'string' ? instance[handler] : handler ).apply(instance, arguments); } @@ -699,9 +698,8 @@ _delay: function (handler, delay) { var instance = this; function handlerProxy() { - return (typeof handler === 'string' - ? instance[handler] - : handler + return ( + typeof handler === 'string' ? instance[handler] : handler ).apply(instance, arguments); } return setTimeout(handlerProxy, delay || 0); @@ -737,9 +735,8 @@ data = data || {}; event = $.Event(event); - event.type = (type === this.widgetEventPrefix - ? type - : this.widgetEventPrefix + type + event.type = ( + type === this.widgetEventPrefix ? type : this.widgetEventPrefix + type ).toLowerCase(); // The original event may come from any element diff --git a/pub/get.php b/pub/get.php index a3267d8f1741a..fb619a0dfa51f 100644 --- a/pub/get.php +++ b/pub/get.php @@ -49,8 +49,10 @@ if (file_exists($configCacheFile) && is_readable($configCacheFile)) { $config = json_decode(file_get_contents($configCacheFile), true); - //checking update time - if (filemtime($configCacheFile) + $config['update_time'] > time()) { + // Checking update time + if (isset($config['update_time'], $config['media_directory'], $config['allowed_resources']) + && filemtime($configCacheFile) + $config['update_time'] > time() + ) { $mediaDirectory = $config['media_directory']; $allowedResources = $config['allowed_resources'];