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/.github/ISSUE_TEMPLATE/developer-experience-issue.md b/.github/ISSUE_TEMPLATE/developer-experience-issue.md
deleted file mode 100644
index 332e069f7006e..0000000000000
--- a/.github/ISSUE_TEMPLATE/developer-experience-issue.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-name: Developer experience issue
-about: Issues related to customization, extensibility, modularity
-labels: 'Triage: Dev.Experience'
-
----
-
-
-
-### Summary (*)
-
-
-### Examples (*)
-
-
-### Proposed solution
-
-
----
-Please provide [Severity](https://devdocs.magento.com/guides/v2.4/contributor-guide/contributing.html#backlog) assessment for the Issue as Reporter. This information will help during Confirmation and Issue triage processes.
-
-- [ ] Severity: **S0** _- Affects critical data or functionality and leaves users with no workaround._
-- [ ] Severity: **S1** _- Affects critical data or functionality and forces users to employ a workaround._
-- [ ] Severity: **S2** _- Affects non-critical data or functionality and forces users to employ a workaround._
-- [ ] Severity: **S3** _- Affects non-critical data or functionality and does not force users to employ a workaround._
-- [ ] Severity: **S4** _- Affects aesthetics, professional look and feel, “quality” or “usability”._
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/developer_experience_issue.yaml b/.github/ISSUE_TEMPLATE/developer_experience_issue.yaml
new file mode 100644
index 0000000000000..974331feee224
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/developer_experience_issue.yaml
@@ -0,0 +1,45 @@
+name: Developer experience issue
+description: Issues related to customization, extensibility, modularity
+labels: ['Triage: Dev.Experience']
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Please read [our guidelines](https://developer.adobe.com/commerce/contributor/guides/code-contributions/#report-an-issue) before submitting the issue.
+ - type: textarea
+ attributes:
+ label: Summary
+ description: |
+ Describe the issue you are experiencing.
+ Include general information, error messages, environments, and so on.
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Examples
+ description: |
+ Provide code examples or a patch with a test (recommended) to clearly indicate the problem.
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Proposed solution
+ description: |
+ Suggest your potential solutions for this issue.
+ - type: textarea
+ attributes:
+ label: Release note
+ description: |
+ Help us to provide meaningful release notes to the community.
+ - type: checkboxes
+ attributes:
+ label: Triage and priority
+ description: |
+ Provide [Severity](https://developer.adobe.com/commerce/contributor/guides/code-contributions/#community-backlog-priority) assessment for the Issue as a Reporter.
+ This information helps us during the Confirmation and Issue triage processes.
+ options:
+ - label: 'Severity: **S0** _- Affects critical data or functionality and leaves users without workaround._'
+ - label: 'Severity: **S1** _- Affects critical data or functionality and forces users to employ a workaround._'
+ - label: 'Severity: **S2** _- Affects non-critical data or functionality and forces users to employ a workaround._'
+ - label: 'Severity: **S3** _- Affects non-critical data or functionality and does not force users to employ a workaround._'
+ - label: 'Severity: **S4** _- Affects aesthetics, professional look and feel, “quality” or “usability”._'
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index 7b6a8d199f28f..0000000000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-name: Feature request
-about: Please consider reporting directly to https://github.com/magento/community-features
-labels: 'feature request'
-
----
-
-
-
-### Description (*)
-
-
-### Expected behavior (*)
-
-
-### Benefits
-
-
-### Additional information
-
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml
new file mode 100644
index 0000000000000..b6b7491ada861
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yaml
@@ -0,0 +1,41 @@
+name: Feature request
+description: Report to https://github.com/magento/community-features
+labels: ['feature request']
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Important: This repository is intended only for Magento 2 Technical Issues.
+ Enter Feature Requests at https://github.com/magento/community-features.
+ Project stakeholders monitor and manage requests.
+ Feature requests entered using this form may be moved to the forum.
+ - type: textarea
+ attributes:
+ label: Description
+ description: |
+ Describe the feature you would like to add.
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Expected behavior
+ description: |
+ What is the expected behavior of this feature?
+ How is it going to work?
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Benefits
+ description: |
+ How do you think this feature would improve Magento?
+ - type: textarea
+ attributes:
+ label: Additional information
+ description: |
+ What other information can you provide about the desired feature?
+ - type: textarea
+ attributes:
+ label: Release note
+ description: |
+ Help us to provide meaningful release notes to the community.
diff --git a/app/bootstrap.php b/app/bootstrap.php
index a67969589818d..8fbe2f770f53b 100644
--- a/app/bootstrap.php
+++ b/app/bootstrap.php
@@ -14,19 +14,20 @@
#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 .
HTML;
}
+ http_response_code(503);
exit(1);
}
diff --git a/app/code/Magento/AdminAdobeIms/Api/Data/ImsWebapiInterface.php b/app/code/Magento/AdminAdobeIms/Api/Data/ImsWebapiInterface.php
index 80038cae72e64..e3c5d11202858 100644
--- a/app/code/Magento/AdminAdobeIms/Api/Data/ImsWebapiInterface.php
+++ b/app/code/Magento/AdminAdobeIms/Api/Data/ImsWebapiInterface.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Api\Data;
diff --git a/app/code/Magento/AdminAdobeIms/Api/Data/ImsWebapiSearchResultsInterface.php b/app/code/Magento/AdminAdobeIms/Api/Data/ImsWebapiSearchResultsInterface.php
index 9aa09f78bffd7..bf947b186b920 100644
--- a/app/code/Magento/AdminAdobeIms/Api/Data/ImsWebapiSearchResultsInterface.php
+++ b/app/code/Magento/AdminAdobeIms/Api/Data/ImsWebapiSearchResultsInterface.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Api\Data;
diff --git a/app/code/Magento/AdminAdobeIms/Api/ImsLogOutInterface.php b/app/code/Magento/AdminAdobeIms/Api/ImsLogOutInterface.php
deleted file mode 100755
index 0b1611171ec95..0000000000000
--- a/app/code/Magento/AdminAdobeIms/Api/ImsLogOutInterface.php
+++ /dev/null
@@ -1,25 +0,0 @@
-adminImsConfig = $adminImsConfig;
- $this->adminImsConnection = $adminImsConnection;
$this->imsCommandOptionService = $imsCommandOptionService;
$this->cacheTypeList = $cacheTypeList;
$this->updateTokensService = $updateTokensService;
+ $this->authorization = $authorization;
+ $this->role = $role ?: ObjectManager::getInstance()->get(Role::class);
+ $this->roleCollection = $roleCollection ?: ObjectManager::getInstance()->get(CollectionFactory::class);
$this->setName('admin:adobe-ims:enable')
->setDescription('Enable Adobe IMS Module.')
@@ -164,6 +184,7 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int
if ($clientId && $clientSecret && $organizationId && $isTwoFactorAuthEnabled) {
$enabled = $this->enableModule($clientId, $clientSecret, $organizationId, $isTwoFactorAuthEnabled);
if ($enabled) {
+ $this->saveImsAuthorizationRole();
$output->writeln(__('Admin Adobe IMS integration is enabled'));
return Cli::RETURN_SUCCESS;
}
@@ -182,6 +203,27 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int
}
}
+ /**
+ * Save new Adobe IMS role
+ *
+ * @return bool
+ * @throws \Exception
+ */
+ private function saveImsAuthorizationRole(): bool
+ {
+ $roleCollection = $this->roleCollection->create()->addFieldToFilter('role_name', 'Adobe Ims');
+ if (!$roleCollection->getSize()) {
+ $this->role->setRoleName('Adobe Ims')
+ ->setUserType((string)UserContextInterface::USER_TYPE_ADMIN)
+ ->setUserId(0)
+ ->setRoleType(Group::ROLE_TYPE)
+ ->setParentId(0)
+ ->save();
+ }
+
+ return true;
+ }
+
/**
* Enable Admin Adobe IMS Module when testConnection was successfully
*
@@ -199,7 +241,7 @@ private function enableModule(
string $organizationId,
bool $isTwoFactorAuthEnabled
): bool {
- $testAuth = $this->adminImsConnection->testAuth($clientId);
+ $testAuth = $this->authorization->testAuth($clientId);
if ($testAuth) {
$this->adminImsConfig->enableModule($clientId, $clientSecret, $organizationId, $isTwoFactorAuthEnabled);
$this->cacheTypeList->cleanType(Config::TYPE_IDENTIFIER);
diff --git a/app/code/Magento/AdminAdobeIms/Console/Command/AdminAdobeImsInfoCommand.php b/app/code/Magento/AdminAdobeIms/Console/Command/AdminAdobeImsInfoCommand.php
index ce89cbbbebde7..6fe3a8c6aeca6 100755
--- a/app/code/Magento/AdminAdobeIms/Console/Command/AdminAdobeImsInfoCommand.php
+++ b/app/code/Magento/AdminAdobeIms/Console/Command/AdminAdobeImsInfoCommand.php
@@ -3,13 +3,12 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Console\Command;
-use Magento\AdminAdobeIms\Model\ImsConnection;
use Magento\AdminAdobeIms\Service\ImsConfig;
+use Magento\AdobeImsApi\Api\AuthorizationInterface;
use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -41,21 +40,21 @@ class AdminAdobeImsInfoCommand extends Command
private ImsConfig $adminImsConfig;
/**
- * @var ImsConnection
+ * @var AuthorizationInterface
*/
- private ImsConnection $adminImsConnection;
+ private AuthorizationInterface $authorization;
/**
* @param ImsConfig $adminImsConfig
- * @param ImsConnection $adminImsConnection
+ * @param AuthorizationInterface $authorization
*/
public function __construct(
ImsConfig $adminImsConfig,
- ImsConnection $adminImsConnection
+ AuthorizationInterface $authorization
) {
parent::__construct();
$this->adminImsConfig = $adminImsConfig;
- $this->adminImsConnection = $adminImsConnection;
+ $this->authorization = $authorization;
$this->setName('admin:adobe-ims:info')
->setDescription('Information of Adobe IMS Module configuration');
@@ -69,7 +68,7 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int
try {
if ($this->adminImsConfig->enabled()) {
$clientId = $this->adminImsConfig->getApiKey();
- if ($this->adminImsConnection->testAuth($clientId)) {
+ if ($this->authorization->testAuth($clientId)) {
$clientSecret = $this->adminImsConfig->getPrivateKey() ? 'configured' : 'not configured';
$output->writeln(self::CLIENT_ID_NAME . ': ' . $clientId);
$output->writeln(self::ORGANIZATION_ID_NAME . ': ' . $this->adminImsConfig->getOrganizationId());
diff --git a/app/code/Magento/AdminAdobeIms/Console/Command/AdminAdobeImsStatusCommand.php b/app/code/Magento/AdminAdobeIms/Console/Command/AdminAdobeImsStatusCommand.php
index 83ffedf3150f5..93ea97959ec16 100755
--- a/app/code/Magento/AdminAdobeIms/Console/Command/AdminAdobeImsStatusCommand.php
+++ b/app/code/Magento/AdminAdobeIms/Console/Command/AdminAdobeImsStatusCommand.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Console\Command;
diff --git a/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsCallback.php b/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsCallback.php
index bf042c0a12e1b..10d43b1552764 100755
--- a/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsCallback.php
+++ b/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsCallback.php
@@ -3,76 +3,58 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Controller\Adminhtml\OAuth;
use Exception;
-use Magento\AdminAdobeIms\Exception\AdobeImsOrganizationAuthorizationException;
-use Magento\AdminAdobeIms\Exception\AdobeImsAuthorizationException;
+
use Magento\AdminAdobeIms\Logger\AdminAdobeImsLogger;
-use Magento\AdminAdobeIms\Service\AdminLoginProcessService;
use Magento\AdminAdobeIms\Service\ImsConfig;
-use Magento\AdminAdobeIms\Service\ImsOrganizationService;
+use Magento\Authorization\Model\UserContextInterface;
use Magento\Backend\App\Action\Context;
-use Magento\AdminAdobeIms\Model\ImsConnection;
use Magento\Backend\Controller\Adminhtml\Auth;
use Magento\Backend\Model\View\Result\Redirect;
use Magento\Framework\App\Action\HttpGetActionInterface;
-use Magento\Framework\Exception\AuthenticationException;
+/**
+ * Callback for handling redirect from Adobe IMS
+ */
class ImsCallback extends Auth implements HttpGetActionInterface
{
public const ACTION_NAME = 'imscallback';
- /**
- * @var ImsConnection
- */
- private ImsConnection $adminImsConnection;
-
/**
* @var ImsConfig
*/
private ImsConfig $adminImsConfig;
/**
- * @var ImsOrganizationService
- */
- private ImsOrganizationService $adminOrganizationService;
-
- /**
- * @var AdminLoginProcessService
+ * @var AdminAdobeImsLogger
*/
- private AdminLoginProcessService $adminLoginProcessService;
+ private AdminAdobeImsLogger $logger;
/**
- * @var AdminAdobeImsLogger
+ * @var UserContextInterface
*/
- private AdminAdobeImsLogger $logger;
+ private UserContextInterface $userContext;
/**
* @param Context $context
- * @param ImsConnection $adminImsConnection
* @param ImsConfig $adminImsConfig
- * @param ImsOrganizationService $adminOrganizationService
- * @param AdminLoginProcessService $adminLoginProcessService
* @param AdminAdobeImsLogger $logger
+ * @param UserContextInterface $userContext
*/
public function __construct(
Context $context,
- ImsConnection $adminImsConnection,
ImsConfig $adminImsConfig,
- ImsOrganizationService $adminOrganizationService,
- AdminLoginProcessService $adminLoginProcessService,
- AdminAdobeImsLogger $logger
+ AdminAdobeImsLogger $logger,
+ UserContextInterface $userContext
) {
parent::__construct($context);
- $this->adminImsConnection = $adminImsConnection;
$this->adminImsConfig = $adminImsConfig;
- $this->adminOrganizationService = $adminOrganizationService;
- $this->adminLoginProcessService = $adminLoginProcessService;
$this->logger = $logger;
+ $this->userContext = $userContext;
}
/**
@@ -92,40 +74,11 @@ public function execute(): Redirect
}
try {
- $code = $this->getRequest()->getParam('code');
-
- if ($code === null) {
- throw new AuthenticationException(__('An authentication error occurred. Verify and try again.'));
- }
-
- //get token from response
- $tokenResponse = $this->adminImsConnection->getTokenResponse($code);
- $accessToken = $tokenResponse->getAccessToken();
-
- //get profile info to check email
- $profile = $this->adminImsConnection->getProfile($accessToken);
- if (empty($profile['email'])) {
- throw new AuthenticationException(__('An authentication error occurred. Verify and try again.'));
+ if ($this->userContext->getUserId()
+ && $this->userContext->getUserType() === UserContextInterface::USER_TYPE_ADMIN
+ ) {
+ return $resultRedirect;
}
-
- //check membership in organization
- $this->adminOrganizationService->checkOrganizationMembership($accessToken);
-
- $this->adminLoginProcessService->execute($tokenResponse, $profile);
- } catch (AdobeImsAuthorizationException $e) {
- $this->logger->error($e->getMessage());
-
- $this->imsErrorMessage(
- 'You don\'t have access to this Commerce instance',
- AdobeImsAuthorizationException::ERROR_MESSAGE
- );
- } catch (AdobeImsOrganizationAuthorizationException $e) {
- $this->logger->error($e->getMessage());
-
- $this->imsErrorMessage(
- 'Unable to sign in with the Adobe ID',
- AdobeImsOrganizationAuthorizationException::ERROR_MESSAGE
- );
} catch (Exception $e) {
$this->logger->error($e->getMessage());
diff --git a/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsReauthCallback.php b/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsReauthCallback.php
index 846b0332f705c..209b2078b175b 100755
--- a/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsReauthCallback.php
+++ b/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsReauthCallback.php
@@ -3,24 +3,20 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Controller\Adminhtml\OAuth;
use Exception;
use Magento\AdminAdobeIms\Logger\AdminAdobeImsLogger;
-use Magento\AdminAdobeIms\Service\AdminReauthProcessService;
+use Magento\AdminAdobeIms\Model\Authorization\AdobeImsAdminTokenUserService;
use Magento\AdminAdobeIms\Service\ImsConfig;
-use Magento\AdminAdobeIms\Service\ImsOrganizationService;
use Magento\Backend\App\Action\Context;
-use Magento\AdminAdobeIms\Model\ImsConnection;
use Magento\Backend\Controller\Adminhtml\Auth;
-use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\Controller\Result\Raw;
use Magento\Framework\Controller\ResultFactory;
-use Magento\Framework\Exception\AuthenticationException;
+use Magento\Framework\Controller\ResultInterface;
class ImsReauthCallback extends Auth implements HttpGetActionInterface
{
@@ -37,52 +33,36 @@ class ImsReauthCallback extends Auth implements HttpGetActionInterface
private const RESPONSE_SUCCESS_CODE = 'success';
private const RESPONSE_ERROR_CODE = 'error';
- /**
- * @var ImsConnection
- */
- private ImsConnection $adminImsConnection;
-
/**
* @var ImsConfig
*/
private ImsConfig $adminImsConfig;
/**
- * @var ImsOrganizationService
- */
- private ImsOrganizationService $adminOrganizationService;
-
- /**
- * @var AdminReauthProcessService
+ * @var AdminAdobeImsLogger
*/
- private AdminReauthProcessService $adminReauthProcessService;
+ private AdminAdobeImsLogger $logger;
/**
- * @var AdminAdobeImsLogger
+ * @var AdobeImsAdminTokenUserService
*/
- private AdminAdobeImsLogger $logger;
+ private AdobeImsAdminTokenUserService $adminTokenUserService;
/**
* @param Context $context
- * @param ImsConnection $adminImsConnection
* @param ImsConfig $adminImsConfig
- * @param ImsOrganizationService $adminOrganizationService
- * @param AdminReauthProcessService $adminReauthProcessService
+ * @param AdobeImsAdminTokenUserService $adminTokenUserService
* @param AdminAdobeImsLogger $logger
*/
public function __construct(
- Context $context,
- ImsConnection $adminImsConnection,
- ImsConfig $adminImsConfig,
- ImsOrganizationService $adminOrganizationService,
- AdminReauthProcessService $adminReauthProcessService,
- AdminAdobeImsLogger $logger
+ Context $context,
+ ImsConfig $adminImsConfig,
+ AdobeImsAdminTokenUserService $adminTokenUserService,
+ AdminAdobeImsLogger $logger
) {
parent::__construct($context);
- $this->adminImsConnection = $adminImsConnection;
$this->adminImsConfig = $adminImsConfig;
- $this->adminOrganizationService = $adminOrganizationService;
- $this->adminReauthProcessService = $adminReauthProcessService;
+ $this->adminTokenUserService = $adminTokenUserService;
$this->logger = $logger;
}
@@ -111,24 +91,7 @@ public function execute(): ResultInterface
}
try {
- $code = $this->getRequest()->getParam('code');
-
- if ($code === null) {
- throw new AuthenticationException(__('An authentication error occurred. Verify and try again.'));
- }
-
- $tokenResponse = $this->adminImsConnection->getTokenResponse($code);
- $accessToken = $tokenResponse->getAccessToken();
-
- $profile = $this->adminImsConnection->getProfile($accessToken);
- if (empty($profile['email'])) {
- throw new AuthenticationException(__('An authentication error occurred. Verify and try again.'));
- }
-
- //check membership in organization
- $this->adminOrganizationService->checkOrganizationMembership($accessToken);
-
- $this->adminReauthProcessService->execute($tokenResponse);
+ $this->adminTokenUserService->processLoginRequest(true);
$response = sprintf(
self::RESPONSE_TEMPLATE,
diff --git a/app/code/Magento/AdminAdobeIms/Exception/AdobeImsAuthorizationException.php b/app/code/Magento/AdminAdobeIms/Exception/AdobeImsAuthorizationException.php
index adeb912923032..a3435ef5f13d4 100755
--- a/app/code/Magento/AdminAdobeIms/Exception/AdobeImsAuthorizationException.php
+++ b/app/code/Magento/AdminAdobeIms/Exception/AdobeImsAuthorizationException.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Exception;
diff --git a/app/code/Magento/AdminAdobeIms/Exception/AdobeImsOrganizationAuthorizationException.php b/app/code/Magento/AdminAdobeIms/Exception/AdobeImsOrganizationAuthorizationException.php
index eba3b811d3148..2109673ca9ae6 100755
--- a/app/code/Magento/AdminAdobeIms/Exception/AdobeImsOrganizationAuthorizationException.php
+++ b/app/code/Magento/AdminAdobeIms/Exception/AdobeImsOrganizationAuthorizationException.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Exception;
@@ -12,10 +11,13 @@
/**
* @api
+ *
+ * @deprecated
+ * @see \Magento\AdobeIms\Exception\AdobeImsOrganizationAuthorizationException
*/
class AdobeImsOrganizationAuthorizationException extends AuthorizationException
{
public const ERROR_MESSAGE = 'The Adobe ID you\'re using does not belong to the organization ' .
- 'that controlling this Commerce instance. Contact your administrator so he can add your Adobe ID ' .
+ 'that controls this Commerce instance. Contact your administrator so he can add your Adobe ID ' .
'to the organization.';
}
diff --git a/app/code/Magento/AdminAdobeIms/Logger/AdminAdobeImsLogger.php b/app/code/Magento/AdminAdobeIms/Logger/AdminAdobeImsLogger.php
index ef0bbc305642b..2c651543acd79 100644
--- a/app/code/Magento/AdminAdobeIms/Logger/AdminAdobeImsLogger.php
+++ b/app/code/Magento/AdminAdobeIms/Logger/AdminAdobeImsLogger.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Logger;
diff --git a/app/code/Magento/AdminAdobeIms/Model/Auth.php b/app/code/Magento/AdminAdobeIms/Model/Auth.php
index a0065594fcfb7..0c3d13fab3e93 100644
--- a/app/code/Magento/AdminAdobeIms/Model/Auth.php
+++ b/app/code/Magento/AdminAdobeIms/Model/Auth.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Model;
diff --git a/app/code/Magento/AdminAdobeIms/Model/Authorization/AdobeImsAdminTokenUserContext.php b/app/code/Magento/AdminAdobeIms/Model/Authorization/AdobeImsAdminTokenUserContext.php
new file mode 100644
index 0000000000000..c85f138669dc1
--- /dev/null
+++ b/app/code/Magento/AdminAdobeIms/Model/Authorization/AdobeImsAdminTokenUserContext.php
@@ -0,0 +1,106 @@
+adminImsConfig = $adminImsConfig;
+ $this->auth = $auth;
+ $this->isTokenValid = $isTokenValid;
+ $this->adminTokenUserService = $adminTokenUserService;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getUserId(): ?int
+ {
+ if (!$this->adminImsConfig->enabled() || $this->isRequestProcessed) {
+ return $this->userId;
+ }
+
+ $session = $this->auth->getAuthStorage();
+
+ if (!empty($session->getAdobeAccessToken())) {
+ $isTokenValid = $this->isTokenValid->validateToken($session->getAdobeAccessToken());
+ if (!$isTokenValid) {
+ throw new AuthenticationException(__('Session Access Token is not valid'));
+ }
+ } else {
+ try {
+ $this->adminTokenUserService->processLoginRequest();
+ } catch (\Exception $e) {
+ throw new AuthenticationException(__('Login request error %1', $e->getMessage()), $e, 0);
+ }
+ }
+
+ $this->userId = (int) $session->getUser()->getUserId();
+ $this->isRequestProcessed = true;
+
+ return $this->userId;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getUserType(): ?int
+ {
+ return UserContextInterface::USER_TYPE_ADMIN;
+ }
+}
diff --git a/app/code/Magento/AdminAdobeIms/Model/Authorization/AdobeImsAdminTokenUserService.php b/app/code/Magento/AdminAdobeIms/Model/Authorization/AdobeImsAdminTokenUserService.php
new file mode 100644
index 0000000000000..9ee688720bed5
--- /dev/null
+++ b/app/code/Magento/AdminAdobeIms/Model/Authorization/AdobeImsAdminTokenUserService.php
@@ -0,0 +1,205 @@
+adminImsConfig = $adminImsConfig;
+ $this->organizationMembership = $organizationMembership;
+ $this->adminLoginProcessService = $adminLoginProcessService;
+ $this->adminReauthProcessService = $adminReauthProcessService;
+ $this->request = $request;
+ $this->token = $token;
+ $this->profile = $profile;
+ $this->tokenResponseFactory = $tokenResponseFactory;
+ $this->saveImsUser = $saveImsUser;
+ }
+
+ /**
+ * Process login request to Admin Adobe IMS.
+ *
+ * @param bool $isReauthorize
+ * @return void
+ * @throws AdobeImsAuthorizationException
+ * @throws AdobeImsOrganizationAuthorizationException
+ * @throws AuthenticationException
+ * @throws AuthorizationException
+ */
+ public function processLoginRequest(bool $isReauthorize = false): void
+ {
+ if ($this->adminImsConfig->enabled()
+ && $this->request->getModuleName() === self::ADOBE_IMS_MODULE_NAME) {
+ try {
+ if ($this->request->getHeader('Authorization')) {
+ $tokenResponse = $this->getRequestedToken();
+ } elseif ($this->request->getParam('code')) {
+ $code = $this->request->getParam('code');
+ $tokenResponse = $this->token->getTokenResponse($code);
+ } else {
+ throw new AuthenticationException(__('Unable to get Access Token. Please try again.'));
+ }
+
+ $this->getLoggedIn($isReauthorize, $tokenResponse);
+ } catch (AdobeImsAuthorizationException $e) {
+ throw new AdobeImsAuthorizationException(
+ __('You don\'t have access to this Commerce instance')
+ );
+ } catch (AdobeImsOrganizationAuthorizationException $e) {
+ throw new AdobeImsOrganizationAuthorizationException(
+ __('Unable to sign in with the Adobe ID')
+ );
+ }
+ } else {
+ throw new AuthenticationException(__('An authentication error occurred. Verify and try again.'));
+ }
+ }
+
+ /**
+ * Get requested token using Authorization header
+ *
+ * @return TokenResponseInterface
+ * @throws AuthenticationException
+ */
+ private function getRequestedToken(): TokenResponseInterface
+ {
+ $authorizationHeaderValue = $this->request->getHeader('Authorization');
+ if (!$authorizationHeaderValue) {
+ throw new AuthenticationException(__('An authentication error occurred. Verify and try again.'));
+ }
+
+ $headerPieces = explode(" ", $authorizationHeaderValue);
+ if (count($headerPieces) !== 2) {
+ throw new AuthenticationException(__('An authentication error occurred. Verify and try again.'));
+ }
+
+ $tokenType = strtolower($headerPieces[0]);
+ if ($tokenType !== self::AUTHORIZATION_METHOD_HEADER_BEARER) {
+ throw new AuthenticationException(__('An authentication error occurred. Verify and try again.'));
+ }
+
+ $tokenResponse['access_token'] = $headerPieces[1];
+ return $this->tokenResponseFactory->create(['data' => $tokenResponse]);
+ }
+
+ /**
+ * Responsible for logging in to Admin Panel
+ *
+ * @param bool $isReauthorize
+ * @param TokenResponseInterface $tokenResponse
+ * @return void
+ * @throws AdobeImsAuthorizationException
+ * @throws AuthenticationException
+ * @throws AuthorizationException
+ */
+ private function getLoggedIn(bool $isReauthorize, TokenResponseInterface $tokenResponse): void
+ {
+ $profile = $this->profile->getProfile($tokenResponse->getAccessToken());
+ if (empty($profile['email'])) {
+ throw new AuthenticationException(__('An authentication error occurred. Verify and try again.'));
+ }
+
+ $this->organizationMembership->checkOrganizationMembership($tokenResponse->getAccessToken());
+
+ if ($isReauthorize) {
+ $this->adminReauthProcessService->execute($tokenResponse);
+ } else {
+ $this->saveImsUser->save($profile);
+ $this->adminLoginProcessService->execute($tokenResponse, $profile);
+ }
+ }
+}
diff --git a/app/code/Magento/AdminAdobeIms/Model/Authorization/AdobeImsTokenUserContext.php b/app/code/Magento/AdminAdobeIms/Model/Authorization/AdobeImsTokenUserContext.php
index fd4445e9826d0..e2c9b93cf7b11 100644
--- a/app/code/Magento/AdminAdobeIms/Model/Authorization/AdobeImsTokenUserContext.php
+++ b/app/code/Magento/AdminAdobeIms/Model/Authorization/AdobeImsTokenUserContext.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Model\Authorization;
diff --git a/app/code/Magento/AdminAdobeIms/Model/Authorization/AdobeImsTokenUserService.php b/app/code/Magento/AdminAdobeIms/Model/Authorization/AdobeImsTokenUserService.php
index e7555996855b5..23239e382ac1b 100644
--- a/app/code/Magento/AdminAdobeIms/Model/Authorization/AdobeImsTokenUserService.php
+++ b/app/code/Magento/AdminAdobeIms/Model/Authorization/AdobeImsTokenUserService.php
@@ -3,17 +3,17 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Model\Authorization;
use Magento\AdminAdobeIms\Api\Data\ImsWebapiInterface;
+use Magento\AdminAdobeIms\Api\Data\ImsWebapiInterfaceFactory;
use Magento\AdminAdobeIms\Api\ImsWebapiRepositoryInterface;
-use Magento\AdminAdobeIms\Api\TokenReaderInterface;
-use Magento\AdminAdobeIms\Model\ImsConnection;
use Magento\AdminAdobeIms\Model\User;
-use Magento\AdminAdobeIms\Api\Data\ImsWebapiInterfaceFactory;
+use Magento\AdobeImsApi\Api\TokenReaderInterface;
+use Magento\AdobeImsApi\Api\GetProfileInterface;
+use Magento\AdobeImsApi\Api\IsTokenValidInterface;
use Magento\Framework\Encryption\EncryptorInterface;
use Magento\Framework\Exception\AuthenticationException;
use Magento\Framework\Exception\AuthorizationException;
@@ -46,9 +46,9 @@ class AdobeImsTokenUserService
private User $adminUser;
/**
- * @var ImsConnection
+ * @var IsTokenValidInterface
*/
- private ImsConnection $adminImsConnection;
+ private IsTokenValidInterface $isTokenValid;
/**
* @var ImsWebapiRepositoryInterface
@@ -65,31 +65,39 @@ class AdobeImsTokenUserService
*/
private DateTime $dateTime;
+ /**
+ * @var GetProfileInterface
+ */
+ private GetProfileInterface $profile;
+
/**
* @param TokenReaderInterface $tokenReader
* @param ImsWebapiRepositoryInterface $imsWebapiRepository
* @param ImsWebapiInterfaceFactory $imsWebapiFactory
* @param User $adminUser
- * @param ImsConnection $adminImsConnection
+ * @param IsTokenValidInterface $isTokenValid
* @param EncryptorInterface $encryptor
* @param DateTime $dateTime
+ * @param GetProfileInterface $profile
*/
public function __construct(
TokenReaderInterface $tokenReader,
ImsWebapiRepositoryInterface $imsWebapiRepository,
ImsWebapiInterfaceFactory $imsWebapiFactory,
User $adminUser,
- ImsConnection $adminImsConnection,
+ IsTokenValidInterface $isTokenValid,
EncryptorInterface $encryptor,
- DateTime $dateTime
+ DateTime $dateTime,
+ GetProfileInterface $profile
) {
$this->tokenReader = $tokenReader;
$this->imsWebapiFactory = $imsWebapiFactory;
$this->adminUser = $adminUser;
- $this->adminImsConnection = $adminImsConnection;
+ $this->isTokenValid = $isTokenValid;
$this->imsWebapiRepository = $imsWebapiRepository;
$this->encryptor = $encryptor;
$this->dateTime = $dateTime;
+ $this->profile = $profile;
}
/**
@@ -151,12 +159,12 @@ private function validateToken(string $token, ImsWebapiInterface $imsWebapiEntit
if ($imsWebapiEntity->getId()) {
$lastCheckTimestamp = $this->dateTime->gmtTimestamp($imsWebapiEntity->getLastCheckTime());
if (($lastCheckTimestamp + self::ACCESS_TOKEN_INTERVAL_CHECK) <= $this->dateTime->gmtTimestamp()) {
- $isTokenValid = $this->adminImsConnection->validateToken($token);
+ $isTokenValid = $this->isTokenValid->validateToken($token);
$imsWebapiEntity->setLastCheckTime($this->dateTime->gmtDate(self::DATE_FORMAT));
$this->imsWebapiRepository->save($imsWebapiEntity);
}
} else {
- $isTokenValid = $this->adminImsConnection->validateToken($token);
+ $isTokenValid = $this->isTokenValid->validateToken($token);
}
if (!$isTokenValid) {
@@ -174,7 +182,7 @@ private function validateToken(string $token, ImsWebapiInterface $imsWebapiEntit
private function getUserProfile(string $bearerToken): array
{
try {
- return $this->adminImsConnection->getProfile($bearerToken);
+ return $this->profile->getProfile($bearerToken);
} catch (\Exception $exception) {
throw new AuthenticationException(__('An authentication error occurred. Verify and try again.'));
}
diff --git a/app/code/Magento/AdminAdobeIms/Model/FlushUserTokens.php b/app/code/Magento/AdminAdobeIms/Model/FlushUserTokens.php
index a5602221a3d6c..e4f80e1ed9269 100644
--- a/app/code/Magento/AdminAdobeIms/Model/FlushUserTokens.php
+++ b/app/code/Magento/AdminAdobeIms/Model/FlushUserTokens.php
@@ -14,6 +14,7 @@
use Magento\Framework\Encryption\Encryptor;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\AdobeImsApi\Api\LogOutInterface;
class FlushUserTokens implements FlushUserTokensInterface
{
@@ -28,9 +29,9 @@ class FlushUserTokens implements FlushUserTokensInterface
private UserContextInterface $userContext;
/**
- * @var LogOut
+ * @var LogOutInterface
*/
- private LogOut $logOut;
+ private LogOutInterface $logOut;
/**
* @var Encryptor
@@ -42,13 +43,13 @@ class FlushUserTokens implements FlushUserTokensInterface
*
* @param ImsWebapiRepositoryInterface $imsWebapiRepository
* @param UserContextInterface $userContext
- * @param LogOut $logOut
+ * @param LogOutInterface $logOut
* @param Encryptor $encryptor
*/
public function __construct(
ImsWebapiRepositoryInterface $imsWebapiRepository,
UserContextInterface $userContext,
- LogOut $logOut,
+ LogOutInterface $logOut,
Encryptor $encryptor
) {
$this->imsWebapiRepository = $imsWebapiRepository;
diff --git a/app/code/Magento/AdminAdobeIms/Model/ImsConnection.php b/app/code/Magento/AdminAdobeIms/Model/ImsConnection.php
deleted file mode 100644
index 5b95893b23430..0000000000000
--- a/app/code/Magento/AdminAdobeIms/Model/ImsConnection.php
+++ /dev/null
@@ -1,236 +0,0 @@
-curlFactory = $curlFactory;
- $this->adminImsConfig = $adminImsConfig;
- $this->json = $json;
- $this->token = $token;
- $this->adminAdobeImsLogger = $adminAdobeImsLogger;
- }
-
- /**
- * Get authorization url
- *
- * @param string|null $clientId
- * @return string
- * @throws InvalidArgumentException
- */
- public function auth(?string $clientId = null): string
- {
- $authUrl = $this->adminImsConfig->getAdminAdobeImsAuthUrl($clientId);
- return $this->getAuthorizationLocation($authUrl);
- }
-
- /**
- * Test if given ClientID is valid and is able to return an authorization URL
- *
- * @param string $clientId
- * @return bool
- * @throws InvalidArgumentException
- */
- public function testAuth(string $clientId): bool
- {
- $location = $this->auth($clientId);
- return $location !== '';
- }
-
- /**
- * Get authorization location from adobeIMS
- *
- * @param string $authUrl
- * @return string
- * @throws InvalidArgumentException
- */
- private function getAuthorizationLocation(string $authUrl): string
- {
- $curl = $this->curlFactory->create();
-
- $curl->addHeader('Content-Type', 'application/x-www-form-urlencoded');
- $curl->addHeader('cache-control', 'no-cache');
- $curl->get($authUrl);
-
- $this->validateResponse($curl);
-
- return $curl->getHeaders()['location'] ?? '';
- }
-
- /**
- * Validate authorization call response
- *
- * @param Curl $curl
- * @return void
- * @throws InvalidArgumentException
- */
- private function validateResponse(Curl $curl): void
- {
- if (isset($curl->getHeaders()['location'])) {
- if (preg_match(
- '/error=([a-z_]+)/i',
- $curl->getHeaders()['location'],
- $error
- )
- && isset($error[0], $error[1])
- ) {
- throw new InvalidArgumentException(
- __('Could not connect to Adobe IMS Service: %1.', $error[1])
- );
- }
- }
-
- if ($curl->getStatus() !== self::HTTP_REDIRECT_CODE) {
- throw new InvalidArgumentException(
- __('Could not get a valid response from Adobe IMS Service.')
- );
- }
- }
-
- /**
- * Verify if access_token is valid
- *
- * @param string|null $token
- * @param string $tokenType
- * @return bool
- * @throws AuthorizationException
- */
- public function validateToken(?string $token, string $tokenType = 'access_token'): bool
- {
- $isTokenValid = false;
-
- if ($token === null) {
- return false;
- }
-
- $curl = $this->curlFactory->create();
-
- $curl->addHeader('Content-Type', 'application/x-www-form-urlencoded');
- $curl->addHeader('cache-control', 'no-cache');
-
- $curl->post(
- $this->adminImsConfig->getValidateTokenUrl($token, $tokenType),
- []
- );
-
- if ($curl->getBody() === '') {
- throw new AuthorizationException(
- __('Could not verify the access_token')
- );
- }
-
- $body = $this->json->unserialize($curl->getBody());
-
- if (isset($body['valid'])) {
- $isTokenValid = (bool)$body['valid'];
- }
-
- if (!$isTokenValid && isset($body['reason'])) {
- $this->adminAdobeImsLogger->info($tokenType . ' is not valid. Reason: ' . $body['reason']);
- }
-
- return $isTokenValid;
- }
-
- /**
- * Get token response
- *
- * @param string $code
- * @return TokenResponseInterface
- * @throws AdobeImsAuthorizationException
- */
- public function getTokenResponse(string $code): TokenResponseInterface
- {
- try {
- return $this->token->execute($code);
- } catch (AuthorizationException $exception) {
- throw new AdobeImsAuthorizationException(
- __($exception->getMessage())
- );
- }
- }
-
- /**
- * Get profile url
- *
- * @param string $code
- * @return array|bool|float|int|mixed|string|null
- * @throws AdobeImsAuthorizationException
- */
- public function getProfile(string $code)
- {
- $curl = $this->curlFactory->create();
-
- $curl->addHeader('Content-Type', 'application/x-www-form-urlencoded');
- $curl->addHeader('cache-control', 'no-cache');
- $curl->addHeader('Authorization', 'Bearer ' . $code);
-
- $curl->get($this->adminImsConfig->getProfileUrl());
-
- if ($curl->getBody() === '') {
- throw new AdobeImsAuthorizationException(
- __('Profile body is empty')
- );
- }
-
- return $this->json->unserialize($curl->getBody());
- }
-}
diff --git a/app/code/Magento/AdminAdobeIms/Model/ImsEmailNotification.php b/app/code/Magento/AdminAdobeIms/Model/ImsEmailNotification.php
index d5276b414a997..4ced72c6b7542 100644
--- a/app/code/Magento/AdminAdobeIms/Model/ImsEmailNotification.php
+++ b/app/code/Magento/AdminAdobeIms/Model/ImsEmailNotification.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Model;
diff --git a/app/code/Magento/AdminAdobeIms/Model/ImsWebapiRepository.php b/app/code/Magento/AdminAdobeIms/Model/ImsWebapiRepository.php
index 90c82eaa4d25a..f1caba6fa5b4c 100644
--- a/app/code/Magento/AdminAdobeIms/Model/ImsWebapiRepository.php
+++ b/app/code/Magento/AdminAdobeIms/Model/ImsWebapiRepository.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Model;
diff --git a/app/code/Magento/AdminAdobeIms/Model/ImsWebapiSearchResults.php b/app/code/Magento/AdminAdobeIms/Model/ImsWebapiSearchResults.php
index 913dc2d1f7b8e..f1594f55f61be 100644
--- a/app/code/Magento/AdminAdobeIms/Model/ImsWebapiSearchResults.php
+++ b/app/code/Magento/AdminAdobeIms/Model/ImsWebapiSearchResults.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Model;
diff --git a/app/code/Magento/AdminAdobeIms/Model/LogOut.php b/app/code/Magento/AdminAdobeIms/Model/LogOut.php
deleted file mode 100644
index 001d3a0794dd2..0000000000000
--- a/app/code/Magento/AdminAdobeIms/Model/LogOut.php
+++ /dev/null
@@ -1,145 +0,0 @@
-logger = $logger;
- $this->curlFactory = $curlFactory;
- $this->adminImsConfig = $adminImsConfig;
- $this->adminImsConnection = $adminImsConnection;
- $this->auth = $auth;
- }
-
- /**
- * @inheritDoc
- */
- public function execute(?string $accessToken = null): bool
- {
- try {
- if ($accessToken === null) {
- $session = $this->auth->getAuthStorage();
- $accessToken = $session->getAdobeAccessToken();
- }
-
- if (empty($accessToken)) {
- return true;
- }
-
- $this->externalLogOut($accessToken);
- return true;
- } catch (\Exception $exception) {
- $this->logger->critical($exception);
- return false;
- }
- }
-
- /**
- * Logout user from Adobe IMS
- *
- * @param string $accessToken
- * @throws LocalizedException
- */
- private function externalLogOut(string $accessToken): void
- {
- if (!$this->checkUserProfile($accessToken)) {
- throw new LocalizedException(
- __('An error occurred during logout operation.')
- );
- }
- $curl = $this->curlFactory->create();
-
- $curl->addHeader('Content-Type', 'application/x-www-form-urlencoded');
- $curl->addHeader('cache-control', 'no-cache');
-
- $curl->post(
- $this->adminImsConfig->getBackendLogoutUrl($accessToken),
- []
- );
-
- if ($curl->getStatus() !== self::HTTP_OK || ($this->checkUserProfile($accessToken))) {
- throw new LocalizedException(
- __('An error occurred during logout operation.')
- );
- }
- }
-
- /**
- * Checks whether user profile could be got by the access token
- * - If the token is invalidated, profile information won't be returned
- *
- * @param string $accessToken
- * @return bool
- */
- private function checkUserProfile(string $accessToken): bool
- {
- try {
- $profile = $this->adminImsConnection->getProfile($accessToken);
- if (!empty($profile['email'])) {
- return true;
- }
- } catch (AdobeImsAuthorizationException $exception) {
- return false;
- }
- return false;
- }
-}
diff --git a/app/code/Magento/AdminAdobeIms/Model/ResourceModel/ImsWebapi.php b/app/code/Magento/AdminAdobeIms/Model/ResourceModel/ImsWebapi.php
index f02161d4a383f..71891f9a8c330 100644
--- a/app/code/Magento/AdminAdobeIms/Model/ResourceModel/ImsWebapi.php
+++ b/app/code/Magento/AdminAdobeIms/Model/ResourceModel/ImsWebapi.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Model\ResourceModel;
diff --git a/app/code/Magento/AdminAdobeIms/Model/ResourceModel/ImsWebapi/Collection.php b/app/code/Magento/AdminAdobeIms/Model/ResourceModel/ImsWebapi/Collection.php
index 1fa7b1b124ffd..d2784c5bed3c0 100644
--- a/app/code/Magento/AdminAdobeIms/Model/ResourceModel/ImsWebapi/Collection.php
+++ b/app/code/Magento/AdminAdobeIms/Model/ResourceModel/ImsWebapi/Collection.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Model\ResourceModel\ImsWebapi;
diff --git a/app/code/Magento/AdminAdobeIms/Model/ResourceModel/User.php b/app/code/Magento/AdminAdobeIms/Model/ResourceModel/User.php
index dfc60679b814f..8f33a5abc3383 100644
--- a/app/code/Magento/AdminAdobeIms/Model/ResourceModel/User.php
+++ b/app/code/Magento/AdminAdobeIms/Model/ResourceModel/User.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Model\ResourceModel;
diff --git a/app/code/Magento/AdminAdobeIms/Model/SaveImsUser.php b/app/code/Magento/AdminAdobeIms/Model/SaveImsUser.php
new file mode 100644
index 0000000000000..43183f10f6eb6
--- /dev/null
+++ b/app/code/Magento/AdminAdobeIms/Model/SaveImsUser.php
@@ -0,0 +1,151 @@
+user = $user;
+ $this->userCollectionFactory = $userCollectionFactory;
+ $this->roleCollectionFactory = $roleCollectionFactory;
+ $this->logger = $logger;
+ $this->adminImsConfig = $adminImsConfig;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function save(array $profile): void
+ {
+ if (!$this->adminImsConfig->enabled() || empty($profile['email'])) {
+ throw new CouldNotSaveException(__('Could not save ims user.'));
+ }
+
+ $username = strtolower(strstr($profile['email'], '@', true));
+ $userCollection = $this->userCollectionFactory->create()
+ ->addFieldToFilter('email', ['eq' => $profile['email']])
+ ->addFieldToFilter('username', ['eq' => $username]);
+
+ if (!$userCollection->getSize()) {
+ $roleId = $this->getImsDefaultRole();
+ if ($roleId > 0) {
+ try {
+ $this->user->setFirstname($profile['first_name'])
+ ->setLastname($profile['last_name'])
+ ->setUsername($username)
+ ->setPassword($this->generateRandomPassword())
+ ->setEmail($profile['email'])
+ ->setRoleType(UserRoleType::ROLE_TYPE)
+ ->setPrivileges("")
+ ->setAssertId(0)
+ ->setRoleId((int)$roleId)
+ ->setPermission('allow')
+ ->save();
+ unset($this->user);
+ } catch (Exception $e) {
+ $this->logger->critical($e->getMessage());
+ throw new CouldNotSaveException(__('Could not save ims user.'));
+ }
+ }
+ }
+ $userCollection->clear();
+ }
+
+ /**
+ * Fetch Default Role "Adobe Ims"
+ *
+ * @return int
+ */
+ private function getImsDefaultRole(): int
+ {
+ $roleId = 0;
+ $roleCollection = $this->roleCollectionFactory->create()
+ ->addFieldToFilter('role_name', ['eq' => self::ADMIN_IMS_ROLE])
+ ->addFieldToSelect('role_id');
+
+ if ($roleCollection->getSize() > 0) {
+ $objRole = $roleCollection->fetchItem();
+ $roleId = (int) $objRole->getId();
+ }
+ $roleCollection->clear();
+
+ return $roleId;
+ }
+
+ /**
+ * Generate random password string
+ *
+ * @return string
+ */
+ private function generateRandomPassword(): string
+ {
+ $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-.';
+ $pass = [];
+ $alphaLength = strlen($characters) - 1;
+ for ($i = 0; $i < 100; $i++) {
+ $n = random_int(0, $alphaLength);
+ $pass[] = $characters[$n];
+ }
+ return implode($pass);
+ }
+}
diff --git a/app/code/Magento/AdminAdobeIms/Model/User.php b/app/code/Magento/AdminAdobeIms/Model/User.php
index e798604420186..b8ec54c171af0 100644
--- a/app/code/Magento/AdminAdobeIms/Model/User.php
+++ b/app/code/Magento/AdminAdobeIms/Model/User.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Model;
diff --git a/app/code/Magento/AdminAdobeIms/Model/UserAuthorizedProxy.php b/app/code/Magento/AdminAdobeIms/Model/UserAuthorizedProxy.php
index b9c291ec30272..cc3087d02b251 100644
--- a/app/code/Magento/AdminAdobeIms/Model/UserAuthorizedProxy.php
+++ b/app/code/Magento/AdminAdobeIms/Model/UserAuthorizedProxy.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Model;
diff --git a/app/code/Magento/AdminAdobeIms/Model/UserAuthorizedSession.php b/app/code/Magento/AdminAdobeIms/Model/UserAuthorizedSession.php
index 56fb89b04dee0..5c2ff966f05ab 100644
--- a/app/code/Magento/AdminAdobeIms/Model/UserAuthorizedSession.php
+++ b/app/code/Magento/AdminAdobeIms/Model/UserAuthorizedSession.php
@@ -3,13 +3,13 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Model;
use Magento\AdobeImsApi\Api\UserAuthorizedInterface;
use Magento\Framework\Exception\AuthorizationException;
+use Magento\AdobeImsApi\Api\IsTokenValidInterface;
/**
* Represent functionality for getting information from session if user is authorised or not
@@ -22,20 +22,20 @@ class UserAuthorizedSession implements UserAuthorizedInterface
private Auth $auth;
/**
- * @var ImsConnection
+ * @var IsTokenValidInterface
*/
- private ImsConnection $adminImsConnection;
+ private IsTokenValidInterface $isTokenValid;
/**
* @param Auth $auth
- * @param ImsConnection $adminImsConnection
+ * @param IsTokenValidInterface $isTokenValid
*/
public function __construct(
Auth $auth,
- ImsConnection $adminImsConnection
+ IsTokenValidInterface $isTokenValid
) {
$this->auth = $auth;
- $this->adminImsConnection = $adminImsConnection;
+ $this->isTokenValid = $isTokenValid;
}
/**
@@ -50,7 +50,7 @@ public function execute(int $adminUserId = null): bool
}
try {
- return $this->adminImsConnection->validateToken($token);
+ return $this->isTokenValid->validateToken($token);
} catch (AuthorizationException $e) {
return false;
}
diff --git a/app/code/Magento/AdminAdobeIms/Observer/AdminAccountCreatedObserver.php b/app/code/Magento/AdminAdobeIms/Observer/AdminAccountCreatedObserver.php
index 92de930ca4e99..e2cc99e5a4cc6 100644
--- a/app/code/Magento/AdminAdobeIms/Observer/AdminAccountCreatedObserver.php
+++ b/app/code/Magento/AdminAdobeIms/Observer/AdminAccountCreatedObserver.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Observer;
diff --git a/app/code/Magento/AdminAdobeIms/Observer/AdminLogoutObserver.php b/app/code/Magento/AdminAdobeIms/Observer/AdminLogoutObserver.php
index 5a07f60c24af7..afded17121c54 100644
--- a/app/code/Magento/AdminAdobeIms/Observer/AdminLogoutObserver.php
+++ b/app/code/Magento/AdminAdobeIms/Observer/AdminLogoutObserver.php
@@ -3,27 +3,26 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Observer;
-use Magento\AdminAdobeIms\Model\LogOut;
+use Magento\AdobeImsApi\Api\LogOutInterface;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
class AdminLogoutObserver implements ObserverInterface
{
/**
- * @var LogOut
+ * @var LogOutInterface
*/
- private LogOut $logOut;
+ private LogOutInterface $logOut;
/**
- * @param LogOut $logOut
+ * @param LogOutInterface $logOut
*/
public function __construct(
- LogOut $logOut
+ LogOutInterface $logOut
) {
$this->logOut = $logOut;
}
diff --git a/app/code/Magento/AdminAdobeIms/Observer/AuthObserver.php b/app/code/Magento/AdminAdobeIms/Observer/AuthObserver.php
index a63c3a1762db4..bb0cf7bf1257b 100644
--- a/app/code/Magento/AdminAdobeIms/Observer/AuthObserver.php
+++ b/app/code/Magento/AdminAdobeIms/Observer/AuthObserver.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Observer;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/AddAdobeImsLayoutHandlePlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/AddAdobeImsLayoutHandlePlugin.php
index 0452d120a9bd5..a6eb22aeaafb0 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/AddAdobeImsLayoutHandlePlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/AddAdobeImsLayoutHandlePlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/AdminForgotPasswordPlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/AdminForgotPasswordPlugin.php
index ec9d96e9be43b..32255faf34501 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/AdminForgotPasswordPlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/AdminForgotPasswordPlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/AdminTokenPlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/AdminTokenPlugin.php
index 04a629855eadc..543e3940d7707 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/AdminTokenPlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/AdminTokenPlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/AdobeImsReauth/AddAdobeImsReAuthButton.php b/app/code/Magento/AdminAdobeIms/Plugin/AdobeImsReauth/AddAdobeImsReAuthButton.php
index 1d009565c5fff..95c274bf14182 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/AdobeImsReauth/AddAdobeImsReAuthButton.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/AdobeImsReauth/AddAdobeImsReAuthButton.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin\AdobeImsReauth;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/BackendAuthSessionPlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/BackendAuthSessionPlugin.php
index 01f15413a3b89..434a953544dbc 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/BackendAuthSessionPlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/BackendAuthSessionPlugin.php
@@ -3,15 +3,14 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
-use Magento\AdminAdobeIms\Model\ImsConnection;
use Magento\AdminAdobeIms\Service\ImsConfig;
use Magento\Backend\Model\Auth\Session;
use Magento\Framework\Stdlib\DateTime\DateTime;
+use Magento\AdobeImsApi\Api\IsTokenValidInterface;
class BackendAuthSessionPlugin
{
@@ -21,9 +20,9 @@ class BackendAuthSessionPlugin
public const ACCESS_TOKEN_INTERVAL_CHECK = 600;
/**
- * @var ImsConnection
+ * @var IsTokenValidInterface
*/
- private ImsConnection $adminImsConnection;
+ private IsTokenValidInterface $isTokenValid;
/**
* @var DateTime
@@ -36,16 +35,16 @@ class BackendAuthSessionPlugin
private ImsConfig $adminImsConfig;
/**
- * @param ImsConnection $adminImsConnection
+ * @param IsTokenValidInterface $isTokenValid
* @param DateTime $dateTime
* @param ImsConfig $adminImsConfig
*/
public function __construct(
- ImsConnection $adminImsConnection,
+ IsTokenValidInterface $isTokenValid,
DateTime $dateTime,
ImsConfig $adminImsConfig
) {
- $this->adminImsConnection = $adminImsConnection;
+ $this->isTokenValid = $isTokenValid;
$this->dateTime = $dateTime;
$this->adminImsConfig = $adminImsConfig;
}
@@ -64,7 +63,7 @@ public function aroundProlong(Session $subject, callable $proceed): void
$lastCheckTime = $subject->getTokenLastCheckTime();
if ($lastCheckTime + self::ACCESS_TOKEN_INTERVAL_CHECK <= $this->dateTime->gmtTimestamp()) {
$accessToken = $subject->getAdobeAccessToken();
- if ($this->adminImsConnection->validateToken($accessToken)) {
+ if ($this->isTokenValid->validateToken($accessToken)) {
$subject->setTokenLastCheckTime($this->dateTime->gmtTimestamp());
} else {
$subject->destroy();
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/Integration/Edit/Tab/AddReAuthVerification.php b/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/Integration/Edit/Tab/AddReAuthVerification.php
index 79aa5e96e7d2c..b781f3f87429b 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/Integration/Edit/Tab/AddReAuthVerification.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/Integration/Edit/Tab/AddReAuthVerification.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin\Block\Adminhtml\Integration\Edit\Tab;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/SignInPlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/SignInPlugin.php
index d02cac303eecb..95fdc5e5f0254 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/SignInPlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/SignInPlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin\Block\Adminhtml;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/System/Account/Edit/AddReAuthVerification.php b/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/System/Account/Edit/AddReAuthVerification.php
index 0742d9ebf1fb1..b5c134d91da32 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/System/Account/Edit/AddReAuthVerification.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/System/Account/Edit/AddReAuthVerification.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin\Block\Adminhtml\System\Account\Edit;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/User/Edit/Tab/AddReAuthVerification.php b/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/User/Edit/Tab/AddReAuthVerification.php
index 9722ced6a8672..eab147edea7ef 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/User/Edit/Tab/AddReAuthVerification.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/User/Edit/Tab/AddReAuthVerification.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin\Block\Adminhtml\User\Edit\Tab;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/User/Role/Tab/AddReAuthVerification.php b/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/User/Role/Tab/AddReAuthVerification.php
index a33b938db4379..9a6656e269fa8 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/User/Role/Tab/AddReAuthVerification.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/Block/Adminhtml/User/Role/Tab/AddReAuthVerification.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin\Block\Adminhtml\User\Role\Tab;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/CheckUserLoginBackendObserverPlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/CheckUserLoginBackendObserverPlugin.php
index 980680b953bc2..52ae888885072 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/CheckUserLoginBackendObserverPlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/CheckUserLoginBackendObserverPlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/DisableAdminLoginAuthPlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/DisableAdminLoginAuthPlugin.php
index 4e3f8d5ef1301..ac2c7d58aa770 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/DisableAdminLoginAuthPlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/DisableAdminLoginAuthPlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/DisableForcedPasswordChangePlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/DisableForcedPasswordChangePlugin.php
index e0d006099ccb3..b7db1ef86b81a 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/DisableForcedPasswordChangePlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/DisableForcedPasswordChangePlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/DisablePasswordResetPlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/DisablePasswordResetPlugin.php
index 70c66bd2a9d44..2465c6dcd6d67 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/DisablePasswordResetPlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/DisablePasswordResetPlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/OtherUserSessionPlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/OtherUserSessionPlugin.php
index 2049571e6ebd0..9e501a10e7eb8 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/OtherUserSessionPlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/OtherUserSessionPlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/PerformIdentityCheckMessagePlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/PerformIdentityCheckMessagePlugin.php
index 046b276926cae..7cc18b4e213bd 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/PerformIdentityCheckMessagePlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/PerformIdentityCheckMessagePlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/RemovePasswordAndUserConfirmationFormFieldsPlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/RemovePasswordAndUserConfirmationFormFieldsPlugin.php
index dd5ad60c32c81..4e1b494658213 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/RemovePasswordAndUserConfirmationFormFieldsPlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/RemovePasswordAndUserConfirmationFormFieldsPlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/RemoveUserValidationRulesPlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/RemoveUserValidationRulesPlugin.php
index 4f369bb3154af..40eee6271dd08 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/RemoveUserValidationRulesPlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/RemoveUserValidationRulesPlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/ReplaceVerifyIdentityWithImsPlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/ReplaceVerifyIdentityWithImsPlugin.php
index 132676ba3b8fb..168b69e37cdd8 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/ReplaceVerifyIdentityWithImsPlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/ReplaceVerifyIdentityWithImsPlugin.php
@@ -3,14 +3,13 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
use Magento\AdminAdobeIms\Model\Auth;
-use Magento\AdminAdobeIms\Model\ImsConnection;
use Magento\AdminAdobeIms\Service\ImsConfig;
+use Magento\AdobeImsApi\Api\IsTokenValidInterface;
use Magento\Framework\Exception\AuthenticationException;
use Magento\Framework\Exception\AuthorizationException;
use Magento\Framework\Exception\NoSuchEntityException;
@@ -24,9 +23,9 @@ class ReplaceVerifyIdentityWithImsPlugin
private ImsConfig $adminImsConfig;
/**
- * @var ImsConnection
+ * @var IsTokenValidInterface
*/
- private ImsConnection $adminImsConnection;
+ private IsTokenValidInterface $isTokenValid;
/**
* @var Auth
@@ -35,16 +34,16 @@ class ReplaceVerifyIdentityWithImsPlugin
/**
* @param ImsConfig $adminImsConfig
- * @param ImsConnection $adminImsConnection
+ * @param IsTokenValidInterface $isTokenValid
* @param Auth $auth
*/
public function __construct(
ImsConfig $adminImsConfig,
- ImsConnection $adminImsConnection,
+ IsTokenValidInterface $isTokenValid,
Auth $auth
) {
$this->adminImsConfig = $adminImsConfig;
- $this->adminImsConnection = $adminImsConnection;
+ $this->isTokenValid = $isTokenValid;
$this->auth = $auth;
}
@@ -105,6 +104,6 @@ private function verifyImsToken(): bool
);
}
- return $this->adminImsConnection->validateToken($reAuthToken);
+ return $this->isTokenValid->validateToken($reAuthToken);
}
}
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/ResetAttemptForBackendObserverPlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/ResetAttemptForBackendObserverPlugin.php
index 3579a8e2a9306..66ecb1cea73a6 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/ResetAttemptForBackendObserverPlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/ResetAttemptForBackendObserverPlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/RevokeAdminAccessTokenPlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/RevokeAdminAccessTokenPlugin.php
index 7390f06fe552d..e8a7f74f3f564 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/RevokeAdminAccessTokenPlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/RevokeAdminAccessTokenPlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
diff --git a/app/code/Magento/AdminAdobeIms/Plugin/UserSavePlugin.php b/app/code/Magento/AdminAdobeIms/Plugin/UserSavePlugin.php
index 5057ab7f301bf..ea4bfa01797dd 100644
--- a/app/code/Magento/AdminAdobeIms/Plugin/UserSavePlugin.php
+++ b/app/code/Magento/AdminAdobeIms/Plugin/UserSavePlugin.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Plugin;
diff --git a/app/code/Magento/AdminAdobeIms/Service/AbstractAdminBaseProcessService.php b/app/code/Magento/AdminAdobeIms/Service/AbstractAdminBaseProcessService.php
index cb97891032e9e..b1ad7bbc19a5d 100644
--- a/app/code/Magento/AdminAdobeIms/Service/AbstractAdminBaseProcessService.php
+++ b/app/code/Magento/AdminAdobeIms/Service/AbstractAdminBaseProcessService.php
@@ -4,7 +4,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Service;
@@ -12,7 +11,7 @@
use Exception;
use Magento\AdminAdobeIms\Exception\AdobeImsAuthorizationException;
use Magento\AdminAdobeIms\Model\Auth;
-use Magento\AdminAdobeIms\Model\LogOut;
+use Magento\AdobeImsApi\Api\LogOutInterface;
use Magento\AdminAdobeIms\Model\User;
use Magento\AdobeImsApi\Api\Data\TokenResponseInterface;
use Magento\Framework\Stdlib\DateTime\DateTime;
@@ -35,20 +34,20 @@ abstract class AbstractAdminBaseProcessService
protected DateTime $dateTime;
/**
- * @var LogOut
+ * @var LogOutInterface
*/
- private LogOut $logOut;
+ private LogOutInterface $logOut;
/**
* @param User $adminUser
* @param Auth $auth
- * @param LogOut $logOut
+ * @param LogOutInterface $logOut
* @param DateTime $dateTime
*/
public function __construct(
User $adminUser,
Auth $auth,
- LogOut $logOut,
+ LogOutInterface $logOut,
DateTime $dateTime
) {
$this->adminUser = $adminUser;
diff --git a/app/code/Magento/AdminAdobeIms/Service/AdminLoginProcessService.php b/app/code/Magento/AdminAdobeIms/Service/AdminLoginProcessService.php
index 65e6301e939cf..6face4e362e5b 100644
--- a/app/code/Magento/AdminAdobeIms/Service/AdminLoginProcessService.php
+++ b/app/code/Magento/AdminAdobeIms/Service/AdminLoginProcessService.php
@@ -4,7 +4,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Service;
diff --git a/app/code/Magento/AdminAdobeIms/Service/AdminNotificationService.php b/app/code/Magento/AdminAdobeIms/Service/AdminNotificationService.php
index d9a101572c4ff..8a62286b8d74c 100644
--- a/app/code/Magento/AdminAdobeIms/Service/AdminNotificationService.php
+++ b/app/code/Magento/AdminAdobeIms/Service/AdminNotificationService.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Service;
diff --git a/app/code/Magento/AdminAdobeIms/Service/AdminReauthProcessService.php b/app/code/Magento/AdminAdobeIms/Service/AdminReauthProcessService.php
index a980d459ba6a1..37a96b53655f6 100644
--- a/app/code/Magento/AdminAdobeIms/Service/AdminReauthProcessService.php
+++ b/app/code/Magento/AdminAdobeIms/Service/AdminReauthProcessService.php
@@ -4,7 +4,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Service;
diff --git a/app/code/Magento/AdminAdobeIms/Service/ImsCommandOptionService.php b/app/code/Magento/AdminAdobeIms/Service/ImsCommandOptionService.php
index b030dd388bf59..ce0c16b4b1bb8 100644
--- a/app/code/Magento/AdminAdobeIms/Service/ImsCommandOptionService.php
+++ b/app/code/Magento/AdminAdobeIms/Service/ImsCommandOptionService.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Service;
diff --git a/app/code/Magento/AdminAdobeIms/Service/ImsCommandValidationService.php b/app/code/Magento/AdminAdobeIms/Service/ImsCommandValidationService.php
index 2728868414e40..d27a7339ee3eb 100644
--- a/app/code/Magento/AdminAdobeIms/Service/ImsCommandValidationService.php
+++ b/app/code/Magento/AdminAdobeIms/Service/ImsCommandValidationService.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Service;
diff --git a/app/code/Magento/AdminAdobeIms/Service/ImsConfig.php b/app/code/Magento/AdminAdobeIms/Service/ImsConfig.php
index 76c2204261096..a9b416204c680 100644
--- a/app/code/Magento/AdminAdobeIms/Service/ImsConfig.php
+++ b/app/code/Magento/AdminAdobeIms/Service/ImsConfig.php
@@ -3,80 +3,34 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Service;
-use Magento\AdminAdobeIms\Controller\Adminhtml\OAuth\ImsCallback;
-use Magento\AdminAdobeIms\Controller\Adminhtml\OAuth\ImsReauthCallback;
use Magento\AdobeIms\Model\Config;
-use Magento\Backend\Model\UrlInterface as BackendUrlInterface;
-use Magento\Config\Model\Config\Backend\Admin\Custom;
-use Magento\Framework\App\Config\Storage\WriterInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
-use Magento\Framework\Encryption\EncryptorInterface;
-use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\UrlInterface;
class ImsConfig extends Config
{
- public const XML_PATH_ENABLED = 'adobe_ims/integration/admin_enabled';
- public const XML_PATH_LOGGING_ENABLED = 'adobe_ims/integration/logging_enabled';
- public const XML_PATH_ORGANIZATION_ID = 'adobe_ims/integration/organization_id';
- public const XML_PATH_API_KEY = 'adobe_ims/integration/api_key';
- public const XML_PATH_PRIVATE_KEY = 'adobe_ims/integration/private_key';
- public const XML_PATH_PROFILE_URL = 'adobe_ims/integration/profile_url';
- public const XML_PATH_NEW_ADMIN_EMAIL_TEMPLATE = 'adobe_ims/email/content_template';
- public const XML_PATH_VALIDATE_TOKEN_URL = 'adobe_ims/integration/validate_token_url';
- public const XML_PATH_LOGOUT_URL = 'adobe_ims/integration/logout_url';
- public const XML_PATH_CERTIFICATE_PATH = 'adobe_ims/integration/certificate_path';
- public const XML_PATH_ADMIN_AUTH_URL_PATTERN = 'adobe_ims/integration/admin/auth_url_pattern';
- public const XML_PATH_ADMIN_REAUTH_URL_PATTERN = 'adobe_ims/integration/admin/reauth_url_pattern';
- public const XML_PATH_ADMIN_ADOBE_IMS_SCOPES = 'adobe_ims/integration/admin/scopes';
- public const XML_PATH_ORGANIZATION_MEMBERSHIP_URL = 'adobe_ims/integration/organization_membership_url';
-
- private const OAUTH_CALLBACK_URL = 'adobe_ims_auth/oauth/';
+ private const XML_PATH_LOGGING_ENABLED = 'adobe_ims/integration/logging_enabled';
+ private const XML_PATH_NEW_ADMIN_EMAIL_TEMPLATE = 'adobe_ims/email/content_template';
/**
* @var ScopeConfigInterface
*/
private ScopeConfigInterface $scopeConfig;
- /**
- * @var WriterInterface
- */
- private WriterInterface $writer;
-
- /**
- * @var EncryptorInterface
- */
- private EncryptorInterface $encryptor;
-
- /**
- * @var BackendUrlInterface
- */
- private BackendUrlInterface $backendUrl;
-
/**
* @param ScopeConfigInterface $scopeConfig
* @param UrlInterface $url
- * @param WriterInterface $writer
- * @param EncryptorInterface $encryptor
- * @param BackendUrlInterface $backendUrl
*/
public function __construct(
ScopeConfigInterface $scopeConfig,
- UrlInterface $url,
- WriterInterface $writer,
- EncryptorInterface $encryptor,
- BackendUrlInterface $backendUrl
+ UrlInterface $url
) {
parent::__construct($scopeConfig, $url);
- $this->writer = $writer;
- $this->encryptor = $encryptor;
$this->scopeConfig = $scopeConfig;
- $this->backendUrl = $backendUrl;
}
/**
@@ -103,199 +57,6 @@ public function loggingEnabled(): bool
);
}
- /**
- * Enable Admin Adobe IMS Module and set Client ID and Client Secret and Organization ID and Two Factor Enabled
- *
- * @param string $clientId
- * @param string $clientSecret
- * @param string $organizationId
- * @param bool $isAdobeIms2FAEnabled
- * @return void
- * @throws LocalizedException
- */
- public function enableModule(
- string $clientId,
- string $clientSecret,
- string $organizationId,
- bool $isAdobeIms2FAEnabled
- ): void {
- if (!$isAdobeIms2FAEnabled) {
- throw new LocalizedException(
- __('2FA is required when enabling the Admin Adobe IMS Module')
- );
- }
-
- $this->updateConfig(
- self::XML_PATH_ENABLED,
- '1'
- );
-
- $this->updateSecureConfig(
- self::XML_PATH_ORGANIZATION_ID,
- $organizationId
- );
-
- $this->updateSecureConfig(
- self::XML_PATH_API_KEY,
- $clientId
- );
-
- $this->updateSecureConfig(
- self::XML_PATH_PRIVATE_KEY,
- $clientSecret
- );
- }
-
- /**
- * Disable Admin Adobe IMS Module and unset Client ID and Client Secret from config
- *
- * @return void
- */
- public function disableModule(): void
- {
- $this->updateConfig(
- self::XML_PATH_ENABLED,
- '0'
- );
-
- $this->deleteConfig(self::XML_PATH_ORGANIZATION_ID);
- $this->deleteConfig(self::XML_PATH_API_KEY);
- $this->deleteConfig(self::XML_PATH_PRIVATE_KEY);
- }
-
- /**
- * Get Profile URL
- *
- * @return string
- */
- public function getProfileUrl(): string
- {
- return str_replace(
- ['#{client_id}'],
- [$this->getApiKey()],
- $this->scopeConfig->getValue(self::XML_PATH_PROFILE_URL)
- );
- }
-
- /**
- * Get Token validation url
- *
- * @param string $code
- * @param string $tokenType
- * @return string
- */
- public function getValidateTokenUrl(string $code, string $tokenType): string
- {
- return str_replace(
- ['#{token}', '#{client_id}', '#{token_type}'],
- [$code, $this->getApiKey(), $tokenType],
- $this->scopeConfig->getValue(self::XML_PATH_VALIDATE_TOKEN_URL)
- );
- }
-
- /**
- * Update config using config writer
- *
- * @param string $path
- * @param string $value
- * @return void
- */
- private function updateConfig(string $path, string $value): void
- {
- $this->writer->save(
- $path,
- $value
- );
- }
-
- /**
- * Update encrypted config setting
- *
- * @param string $path
- * @param string $value
- * @return void
- */
- private function updateSecureConfig(string $path, string $value): void
- {
- $value = str_replace(['\n', '\r'], ["\n", "\r"], $value);
-
- if (!preg_match('/^\*+$/', $value) && !empty($value)) {
- $value = $this->encryptor->encrypt($value);
-
- $this->writer->save(
- $path,
- $value
- );
- }
- }
-
- /**
- * Delete config value
- *
- * @param string $path
- * @return void
- */
- private function deleteConfig(string $path): void
- {
- $this->writer->delete($path);
- }
-
- /**
- * Generate the AdminAdobeIms AuthUrl with given clientID or the ClientID stored in the config
- *
- * @param string|null $clientId
- * @return string
- */
- public function getAdminAdobeImsAuthUrl(?string $clientId): string
- {
- if ($clientId === null) {
- $clientId = $this->getApiKey();
- }
-
- return str_replace(
- ['#{client_id}', '#{redirect_uri}', '#{scope}', '#{locale}'],
- [
- $clientId,
- $this->getAdminAdobeImsCallBackUrl(),
- $this->getScopes(),
- $this->getLocale()
- ],
- $this->scopeConfig->getValue(self::XML_PATH_ADMIN_AUTH_URL_PATTERN)
- );
- }
-
- /**
- * Generate the AdminAdobeIms AuthUrl for reAuth
- *
- * @return string
- */
- public function getAdminAdobeImsReAuthUrl(): string
- {
- return str_replace(
- ['#{client_id}', '#{redirect_uri}', '#{scope}', '#{locale}'],
- [
- $this->getApiKey(),
- $this->getAdminAdobeImsReAuthCallBackUrl(),
- $this->getScopes(),
- $this->getLocale()
- ],
- $this->scopeConfig->getValue(self::XML_PATH_ADMIN_REAUTH_URL_PATTERN)
- );
- }
-
- /**
- * Get scopes for AdobeIms
- *
- * @return string
- */
- private function getScopes(): string
- {
- return implode(
- ',',
- $this->scopeConfig->getValue(self::XML_PATH_ADMIN_ADOBE_IMS_SCOPES)
- );
- }
-
/**
* Get email template for new created admin users
*
@@ -307,89 +68,4 @@ public function getEmailTemplateForNewAdminUsers(): string
self::XML_PATH_NEW_ADMIN_EMAIL_TEMPLATE
);
}
-
- /**
- * Get callback url for AdminAdobeIms Module
- *
- * @return string
- */
- private function getAdminAdobeImsCallBackUrl(): string
- {
- return $this->backendUrl->getUrl(
- self::OAUTH_CALLBACK_URL . ImsCallback::ACTION_NAME
- );
- }
-
- /**
- * Get reAuth callback url for AdminAdobeIms Module
- *
- * @return string
- */
- private function getAdminAdobeImsReAuthCallBackUrl(): string
- {
- return $this->backendUrl->getUrl(
- self::OAUTH_CALLBACK_URL . ImsReauthCallback::ACTION_NAME
- );
- }
-
- /**
- * Get locale
- *
- * @return string
- */
- private function getLocale(): string
- {
- return $this->scopeConfig->getValue(Custom::XML_PATH_GENERAL_LOCALE_CODE);
- }
-
- /**
- * Get BackendLogout URL
- *
- * @param string $accessToken
- * @return string
- */
- public function getBackendLogoutUrl(string $accessToken) : string
- {
- return str_replace(
- ['#{access_token}', '#{client_secret}', '#{client_id}'],
- [$accessToken, $this->getPrivateKey(), $this->getApiKey()],
- $this->scopeConfig->getValue(self::XML_PATH_LOGOUT_URL)
- );
- }
-
- /**
- * Retrieve Organization Id
- *
- * @return string
- */
- public function getOrganizationId(): string
- {
- return $this->scopeConfig->getValue(self::XML_PATH_ORGANIZATION_ID);
- }
-
- /**
- * IMS certificate (public key) location retrieval
- *
- * @param string $fileName
- * @return string
- */
- public function getCertificateUrl(string $fileName): string
- {
- return $this->scopeConfig->getValue(self::XML_PATH_CERTIFICATE_PATH) . $fileName;
- }
-
- /**
- * Get url to check organization membership
- *
- * @param string $orgId
- * @return string
- */
- public function getOrganizationMembershipUrl(string $orgId): string
- {
- return str_replace(
- ['#{org_id}'],
- [$orgId],
- $this->scopeConfig->getValue(self::XML_PATH_ORGANIZATION_MEMBERSHIP_URL)
- );
- }
}
diff --git a/app/code/Magento/AdminAdobeIms/Service/UpdateTokensService.php b/app/code/Magento/AdminAdobeIms/Service/UpdateTokensService.php
index 7db8c3e439ef7..e859269305cf3 100644
--- a/app/code/Magento/AdminAdobeIms/Service/UpdateTokensService.php
+++ b/app/code/Magento/AdminAdobeIms/Service/UpdateTokensService.php
@@ -4,7 +4,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Service;
diff --git a/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminAdobeImsSignInActionGroup.xml b/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminAdobeImsSignInActionGroup.xml
index 86bb9d082fa1d..abeaed22c225e 100644
--- a/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminAdobeImsSignInActionGroup.xml
+++ b/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminAdobeImsSignInActionGroup.xml
@@ -24,7 +24,7 @@
-
+
diff --git a/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminCreateUserWithoutPasswordActionGroup.xml b/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminCreateUserWithoutPasswordActionGroup.xml
index 9c7d5c6bb5a4c..a682821c5bac9 100644
--- a/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminCreateUserWithoutPasswordActionGroup.xml
+++ b/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminCreateUserWithoutPasswordActionGroup.xml
@@ -15,19 +15,38 @@
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminDisableAdobeImsActionGroup.xml b/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminDisableAdobeImsActionGroup.xml
index 1fc6cb9827d6d..973b8f8e260c0 100644
--- a/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminDisableAdobeImsActionGroup.xml
+++ b/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminDisableAdobeImsActionGroup.xml
@@ -14,6 +14,5 @@
-
diff --git a/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminEnableAdobeImsActionGroup.xml b/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminEnableAdobeImsActionGroup.xml
index 908d68b8a7667..ca809e24f3dbb 100644
--- a/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminEnableAdobeImsActionGroup.xml
+++ b/app/code/Magento/AdminAdobeIms/Test/Mftf/ActionGroup/AdminEnableAdobeImsActionGroup.xml
@@ -14,6 +14,5 @@
-
diff --git a/app/code/Magento/AdminAdobeIms/Test/Mftf/Section/AdminAdobeImsSignInSection.xml b/app/code/Magento/AdminAdobeIms/Test/Mftf/Section/AdminAdobeImsSignInSection.xml
index 3ad84469136a8..3581319c86b74 100644
--- a/app/code/Magento/AdminAdobeIms/Test/Mftf/Section/AdminAdobeImsSignInSection.xml
+++ b/app/code/Magento/AdminAdobeIms/Test/Mftf/Section/AdminAdobeImsSignInSection.xml
@@ -11,9 +11,9 @@
diff --git a/app/code/Magento/AdminAdobeIms/Test/Mftf/Section/AdminCreateUserSection.xml b/app/code/Magento/AdminAdobeIms/Test/Mftf/Section/AdminCreateUserSection.xml
new file mode 100644
index 0000000000000..acd65c5e342e1
--- /dev/null
+++ b/app/code/Magento/AdminAdobeIms/Test/Mftf/Section/AdminCreateUserSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/AdminAdobeIms/Test/Mftf/Test/CreateNewUserWithoutPasswordTest.xml b/app/code/Magento/AdminAdobeIms/Test/Mftf/Test/AdminCreateNewAdminUserWithAdobeImsTest.xml
similarity index 74%
rename from app/code/Magento/AdminAdobeIms/Test/Mftf/Test/CreateNewUserWithoutPasswordTest.xml
rename to app/code/Magento/AdminAdobeIms/Test/Mftf/Test/AdminCreateNewAdminUserWithAdobeImsTest.xml
index 88fc3b2cbce61..864e425b053f3 100644
--- a/app/code/Magento/AdminAdobeIms/Test/Mftf/Test/CreateNewUserWithoutPasswordTest.xml
+++ b/app/code/Magento/AdminAdobeIms/Test/Mftf/Test/AdminCreateNewAdminUserWithAdobeImsTest.xml
@@ -7,18 +7,18 @@
-->
-
+
-
-
-
+
+
+
+
Skipped
-
@@ -33,5 +33,7 @@
+
+
diff --git a/app/code/Magento/AdminAdobeIms/Test/Mftf/Test/CallbackWithoutCodeRedirectsToAdminLoginTest.xml b/app/code/Magento/AdminAdobeIms/Test/Mftf/Test/CallbackWithoutCodeRedirectsToAdminLoginTest.xml
index 6376b8a79401b..68ba28fdaa693 100644
--- a/app/code/Magento/AdminAdobeIms/Test/Mftf/Test/CallbackWithoutCodeRedirectsToAdminLoginTest.xml
+++ b/app/code/Magento/AdminAdobeIms/Test/Mftf/Test/CallbackWithoutCodeRedirectsToAdminLoginTest.xml
@@ -22,15 +22,16 @@
-
+
+
-
+
diff --git a/app/code/Magento/AdminAdobeIms/Test/Unit/Command/AdminAdobeImsEnableCommandTest.php b/app/code/Magento/AdminAdobeIms/Test/Unit/Command/AdminAdobeImsEnableCommandTest.php
index 2a86218568f38..195b4be7c9309 100755
--- a/app/code/Magento/AdminAdobeIms/Test/Unit/Command/AdminAdobeImsEnableCommandTest.php
+++ b/app/code/Magento/AdminAdobeIms/Test/Unit/Command/AdminAdobeImsEnableCommandTest.php
@@ -3,17 +3,19 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Test\Unit\Command;
use Exception;
use Magento\AdminAdobeIms\Console\Command\AdminAdobeImsEnableCommand;
-use Magento\AdminAdobeIms\Model\ImsConnection;
-use Magento\AdminAdobeIms\Service\UpdateTokensService;
use Magento\AdminAdobeIms\Service\ImsCommandOptionService;
use Magento\AdminAdobeIms\Service\ImsConfig;
+use Magento\AdminAdobeIms\Service\UpdateTokensService;
+use Magento\AdobeImsApi\Api\AuthorizationInterface;
+use Magento\Authorization\Model\ResourceModel\Role\Collection as RoleCollection;
+use Magento\Authorization\Model\ResourceModel\Role\CollectionFactory;
+use Magento\Authorization\Model\Role;
use Magento\Framework\App\Cache\Type\Config;
use Magento\Framework\App\Cache\TypeListInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
@@ -40,9 +42,9 @@ class AdminAdobeImsEnableCommandTest extends TestCase
private $adminImsConfigMock;
/**
- * @var ImsConnection
+ * @var AuthorizationInterface
*/
- private $adminImsConnectionMock;
+ private $authorizationUrlMock;
/**
* @var ImsCommandOptionService
@@ -64,6 +66,16 @@ class AdminAdobeImsEnableCommandTest extends TestCase
*/
private $questionHelperMock;
+ /**
+ * @var Role
+ */
+ private $role;
+
+ /**
+ * @var CollectionFactory
+ */
+ private $roleCollection;
+
/**
* @var AdminAdobeImsEnableCommand
*/
@@ -74,10 +86,32 @@ protected function setUp(): void
$objectManagerHelper = new ObjectManagerHelper($this);
$this->adminImsConfigMock = $this->createMock(ImsConfig::class);
- $this->adminImsConnectionMock = $this->createMock(ImsConnection::class);
+ $this->authorizationUrlMock = $this->createMock(AuthorizationInterface::class);
$this->imsCommandOptionService = $this->createMock(ImsCommandOptionService::class);
$this->typeListInterface = $this->createMock(TypeListInterface::class);
$this->updateTokensService = $this->createMock(UpdateTokensService::class);
+ $roleCollectionMock = $this->createPartialMock(
+ RoleCollection::class,
+ ['addFieldToFilter', 'getSize']
+ );
+ $roleCollectionMock->method('addFieldToFilter')->willReturnSelf();
+ $this->roleCollection = $this->createPartialMock(
+ CollectionFactory::class,
+ ['create']
+ );
+ $this->roleCollection->method('create')->willReturn(
+ $roleCollectionMock
+ );
+ $this->role = $this->getMockBuilder(Role::class)
+ ->setMethods(['setParentId','setRoleType','setUserId','setRoleName','setUserType','save'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->role->method('setRoleName')->willReturnSelf();
+ $this->role->method('setUserType')->willReturnSelf();
+ $this->role->method('setUserId')->willReturnSelf();
+ $this->role->method('setRoleType')->willReturnSelf();
+ $this->role->method('setParentId')->willReturnSelf();
+ $this->role->method('save')->willReturnSelf();
$this->questionHelperMock = $this->getMockBuilder(QuestionHelper::class)
->disableOriginalConstructor()
@@ -87,10 +121,12 @@ protected function setUp(): void
AdminAdobeImsEnableCommand::class,
[
'adminImsConfig' => $this->adminImsConfigMock,
- 'adminImsConnection' => $this->adminImsConnectionMock,
'imsCommandOptionService' => $this->imsCommandOptionService,
'cacheTypeList' => $this->typeListInterface,
- 'updateTokenService' => $this->updateTokensService
+ 'updateTokenService' => $this->updateTokensService,
+ 'authorization' => $this->authorizationUrlMock,
+ 'role' => $this->role,
+ 'roleCollection' => $this->roleCollection
]
);
}
@@ -127,7 +163,7 @@ public function testAdminAdobeImsModuleEnableWillClearCacheWhenSuccessful(
$this->imsCommandOptionService->method('getClientSecret')->willReturn('clientSecret');
$this->imsCommandOptionService->method('isTwoFactorAuthEnabled')->willReturn($isTwoFactorAuthEnabled);
- $this->adminImsConnectionMock->method('testAuth')
+ $this->authorizationUrlMock->method('testAuth')
->willReturn($testAuthMode);
$this->adminImsConfigMock
diff --git a/app/code/Magento/AdminAdobeIms/Test/Unit/Model/Authorization/AdobeImsAdminTokenUserContextTest.php b/app/code/Magento/AdminAdobeIms/Test/Unit/Model/Authorization/AdobeImsAdminTokenUserContextTest.php
new file mode 100644
index 0000000000000..a5d530e425948
--- /dev/null
+++ b/app/code/Magento/AdminAdobeIms/Test/Unit/Model/Authorization/AdobeImsAdminTokenUserContextTest.php
@@ -0,0 +1,158 @@
+objectManager = new ObjectManager($this);
+
+ $this->adminSession = $this->getMockBuilder(Session::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getUser', 'getId','getAdobeAccessToken'])
+ ->getMock();
+
+ $this->adminImsConfigMock = $this->createMock(ImsConfig::class);
+ $this->auth = $this->createMock(Auth::class);
+ $this->isTokenValid = $this->createMock(IsTokenValidInterface::class);
+ $this->adminTokenUserService = $this->createMock(AdobeImsAdminTokenUserService::class);
+ $this->auth
+ ->method('getAuthStorage')
+ ->willReturn($this->adminSession);
+
+ $this->adminImsConfigMock->expects($this->any())
+ ->method('enabled')
+ ->willReturn(true);
+
+ $this->adobeImsAdminTokenUserContext = $this->objectManager->getObject(
+ AdobeImsAdminTokenUserContext::class,
+ [
+ 'adminImsConfig' => $this->adminImsConfigMock,
+ 'auth' => $this->auth,
+ 'isTokenValid' => $this->isTokenValid,
+ 'adminTokenUserService' => $this->adminTokenUserService,
+ ]
+ );
+ }
+
+ public function testGetUserId()
+ {
+ $userId = 1;
+
+ $this->setupUserId($userId);
+
+ $this->assertEquals($userId, $this->adobeImsAdminTokenUserContext->getUserId());
+ }
+
+ /**
+ * Test exception with invalid access token
+ *
+ * @return void
+ * @throws AuthenticationException
+ */
+ public function testExceptionWhenAccessTokenNotValid(): void
+ {
+ $this->adminSession->expects($this->any())
+ ->method('getAdobeAccessToken')
+ ->willReturn('test');
+
+ $this->isTokenValid
+ ->expects($this->once())
+ ->method('validateToken')
+ ->willReturn(false);
+
+ $this->expectException(AuthenticationException::class);
+ $this->expectExceptionMessage('Session Access Token is not valid');
+
+ $this->adobeImsAdminTokenUserContext->getUserId();
+ }
+
+ public function testGetUserType()
+ {
+ $this->assertEquals(UserContextInterface::USER_TYPE_ADMIN, $this->adobeImsAdminTokenUserContext->getUserType());
+ }
+
+ /**
+ * Setting up User Id
+ *
+ * @param int|null $userId
+ * @return void
+ */
+ public function setupUserId($userId)
+ {
+ $this->adminSession->expects($this->any())
+ ->method('getAdobeAccessToken')
+ ->willReturn(null);
+
+ if ($userId) {
+ $userMock = $this->getMockBuilder(User::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getUserId'])
+ ->getMock();
+
+ $userMock->expects($this->once())
+ ->method('getUserId')
+ ->willReturn($userId);
+
+ $this->adminSession->expects($this->once())
+ ->method('getUser')
+ ->willReturn($userMock);
+ }
+ }
+}
diff --git a/app/code/Magento/AdminAdobeIms/Test/Unit/Model/Authorization/AdobeImsAdminTokenUserServiceTest.php b/app/code/Magento/AdminAdobeIms/Test/Unit/Model/Authorization/AdobeImsAdminTokenUserServiceTest.php
new file mode 100644
index 0000000000000..89a6a7da699ac
--- /dev/null
+++ b/app/code/Magento/AdminAdobeIms/Test/Unit/Model/Authorization/AdobeImsAdminTokenUserServiceTest.php
@@ -0,0 +1,329 @@
+adminImsConfigMock = $this->createMock(ImsConfig::class);
+ $this->token = $this->createMock(GetTokenInterface::class);
+ $this->profile = $this->createMock(GetProfileInterface::class);
+ $this->organizationMembership = $this->createMock(OrganizationMembershipInterface::class);
+ $this->adminLoginProcessService = $this->createMock(AdminLoginProcessService::class);
+ $this->requestInterfaceMock = $this->getMockBuilder(RequestInterface::class)
+ ->setMethods(['getHeader','getParam'])
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $this->adminReauthProcessService = $this->createMock(AdminReauthProcessService::class);
+ $this->tokenResponseFactoryMock = $this->createMock(TokenResponseInterfaceFactory::class);
+ $this->saveImsUser = $this->createMock(SaveImsUserInterface::class);
+ $this->adminImsConfigMock->expects($this->any())
+ ->method('enabled')
+ ->willReturn(true);
+
+ $this->adobeImsAdminTokenUserService = new AdobeImsAdminTokenUserService(
+ $this->adminImsConfigMock,
+ $this->organizationMembership,
+ $this->adminLoginProcessService,
+ $this->adminReauthProcessService,
+ $this->requestInterfaceMock,
+ $this->token,
+ $this->profile,
+ $this->tokenResponseFactoryMock,
+ $this->saveImsUser
+ );
+ }
+
+ /**
+ * Test Process Login Request
+ *
+ * @return void
+ * @param array $responseData
+ * @dataProvider responseDataProvider
+ */
+ public function testProcessLoginRequest(array $responseData): void
+ {
+ $this->requestInterfaceMock->expects($this->exactly(2))
+ ->method('getParam')->with('code')->willReturn(self::CODE);
+
+ $this->requestInterfaceMock->expects($this->once())
+ ->method('getModuleName')->willReturn('adobe_ims_auth');
+
+ $tokenResponse = $this->createMock(TokenResponseInterface::class);
+ $tokenResponse->expects($this->any())
+ ->method('getAccessToken')
+ ->willReturn($responseData['access_token']);
+
+ $this->token->expects($this->once())
+ ->method('getTokenResponse')
+ ->with(self::CODE)
+ ->willReturn($tokenResponse);
+
+ $this->profile->expects($this->once())
+ ->method('getProfile')
+ ->with($responseData['access_token'])
+ ->willReturn($responseData);
+
+ $this->organizationMembership->expects($this->once())
+ ->method('checkOrganizationMembership')
+ ->with($responseData['access_token']);
+
+ $this->saveImsUser->expects($this->once())
+ ->method('save')
+ ->with($responseData);
+
+ $this->adminLoginProcessService->expects($this->once())
+ ->method('execute')
+ ->with($tokenResponse, $responseData);
+
+ $this->adobeImsAdminTokenUserService->processLoginRequest();
+ }
+
+ /**
+ * Test Process Login Request
+ *
+ * @return void
+ * @param array $responseData
+ * @dataProvider responseDataProvider
+ */
+ public function testProcessLoginRequestWithAuthorizationHeader(array $responseData): void
+ {
+ $this->requestInterfaceMock->expects($this->once())
+ ->method('getModuleName')->willReturn('adobe_ims_auth');
+
+ $this->requestInterfaceMock->expects($this->exactly(2))
+ ->method('getHeader')
+ ->with('Authorization')
+ ->willReturn('Bearer kladjflakdjf3423rfzddsf');
+
+ $data = ['access_token' => 'kladjflakdjf3423rfzddsf'];
+
+ $tokenResponse = $this->createMock(TokenResponseInterface::class);
+ $this->tokenResponseFactoryMock->expects($this->once())
+ ->method('create')
+ ->with(['data' => $data])
+ ->willReturn($tokenResponse);
+
+ $tokenResponse->expects($this->any())
+ ->method('getAccessToken')
+ ->willReturn($responseData['access_token']);
+
+ $this->profile->expects($this->once())
+ ->method('getProfile')
+ ->with($data['access_token'])
+ ->willReturn($responseData);
+
+ $this->organizationMembership->expects($this->once())
+ ->method('checkOrganizationMembership')
+ ->with($responseData['access_token']);
+
+ $this->saveImsUser->expects($this->once())
+ ->method('save')
+ ->with($responseData);
+
+ $this->adminLoginProcessService->expects($this->once())
+ ->method('execute')
+ ->with($tokenResponse, $responseData);
+
+ $this->adobeImsAdminTokenUserService->processLoginRequest();
+ }
+
+ /**
+ * Test exception when tried to access from other module
+ *
+ * @return void
+ * @throws AuthenticationException
+ */
+ public function testExceptionWhenTriedToAccessFromOtherModule(): void
+ {
+ $this->requestInterfaceMock->expects($this->once())
+ ->method('getModuleName')->willReturn('Test Module');
+
+ $this->expectException(AuthenticationException::class);
+ $this->expectExceptionMessage('An authentication error occurred. Verify and try again.');
+
+ $this->adobeImsAdminTokenUserService->processLoginRequest();
+ }
+
+ /**
+ * Test exception when profile not found
+ *
+ * @return void
+ * @param array $responseData
+ * @dataProvider responseDataProvider
+ * @throws AuthenticationException
+ */
+ public function testExceptionWhenProfileNotFoundBasedOnAccessToken(array $responseData): void
+ {
+ $this->requestInterfaceMock->expects($this->exactly(2))
+ ->method('getParam')->with('code')->willReturn(self::CODE);
+
+ $this->requestInterfaceMock->expects($this->once())
+ ->method('getModuleName')->willReturn('adobe_ims_auth');
+
+ $tokenResponse = $this->createMock(TokenResponseInterface::class);
+ $tokenResponse->expects($this->any())
+ ->method('getAccessToken')
+ ->willReturn($responseData['access_token']);
+
+ $this->token->expects($this->once())
+ ->method('getTokenResponse')
+ ->with(self::CODE)
+ ->willReturn($tokenResponse);
+
+ $this->profile->expects($this->once())
+ ->method('getProfile')
+ ->with($responseData['access_token'])
+ ->willReturn('');
+
+ $this->expectException(AuthenticationException::class);
+ $this->expectExceptionMessage('An authentication error occurred. Verify and try again.');
+
+ $this->adobeImsAdminTokenUserService->processLoginRequest();
+ }
+
+ /**
+ * Test exception when admin login provided with wrong info
+ *
+ * @return void
+ * @param array $responseData
+ * @dataProvider responseDataProvider
+ * @throws AdobeImsAuthorizationException
+ */
+ public function testExceptionWhenAdminLoginProcessCalledWithWrongInfo(array $responseData): void
+ {
+ $this->requestInterfaceMock->expects($this->exactly(2))
+ ->method('getParam')->with('code')->willReturn(self::CODE);
+
+ $this->requestInterfaceMock->expects($this->once())
+ ->method('getModuleName')->willReturn('adobe_ims_auth');
+
+ $tokenResponse = $this->createMock(TokenResponseInterface::class);
+ $tokenResponse->expects($this->any())
+ ->method('getAccessToken')
+ ->willReturn($responseData['access_token']);
+
+ $this->token->expects($this->once())
+ ->method('getTokenResponse')
+ ->with(self::CODE)
+ ->willReturn($tokenResponse);
+
+ $this->profile->expects($this->once())
+ ->method('getProfile')
+ ->with($responseData['access_token'])
+ ->willReturn($responseData);
+
+ $this->adminLoginProcessService->expects($this->once())
+ ->method('execute')
+ ->with($tokenResponse, $responseData)
+ ->willThrowException(new AdobeImsAuthorizationException(
+ __('You don\'t have access to this Commerce instance')
+ ));
+
+ $this->expectException(AdobeImsAuthorizationException::class);
+ $this->expectExceptionMessage('You don\'t have access to this Commerce instance');
+
+ $this->adobeImsAdminTokenUserService->processLoginRequest();
+ }
+
+ /**
+ * Data provider for response.
+ *
+ * @return array
+ */
+ public function responseDataProvider(): array
+ {
+ return
+ [
+ [
+ 'tokenResponse' => [
+ 'name' => 'Test User',
+ 'email' => 'user@test.com',
+ 'access_token' => 'kladjflakdjf3423rfzddsf',
+ 'refresh_token' => 'kladjflakdjf3423rfzddsf',
+ 'expires_in' => 1642259230998,
+ 'first_name' => 'Test',
+ 'last_name' => 'User'
+ ]
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/AdminAdobeIms/Test/Unit/Model/ImsConnectionTest.php b/app/code/Magento/AdminAdobeIms/Test/Unit/Model/ImsConnectionTest.php
deleted file mode 100644
index 5596b3a3d6c0d..0000000000000
--- a/app/code/Magento/AdminAdobeIms/Test/Unit/Model/ImsConnectionTest.php
+++ /dev/null
@@ -1,95 +0,0 @@
-createMock(ImsConfig::class);
- $adminImsConfigMock
- ->method('getAuthUrl')
- ->willReturn(self::AUTH_URL);
-
- $this->curlFactory = $this->createMock(CurlFactory::class);
-
- $json = $this->createMock(Json::class);
-
- $this->adminImsConnection = $objectManagerHelper->getObject(
- ImsConnection::class,
- [
- 'curlFactory' => $this->curlFactory,
- 'adminImsConfig' => $adminImsConfigMock,
- 'json' => $json,
- ]
- );
- }
-
- public function testAuthThrowsExceptionWhenResponseCodeIs200(): void
- {
- $curlMock = $this->createMock(Curl::class);
- $curlMock->method('getHeaders')
- ->willReturn(['location' => self::AUTH_URL]);
- $curlMock->method('getStatus')
- ->willReturn(200);
-
- $this->curlFactory->method('create')
- ->willReturn($curlMock);
-
- $this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('Could not get a valid response from Adobe IMS Service.');
- $this->adminImsConnection->auth();
- }
-
- public function testAuthThrowsExceptionWhenResponseContainsError(): void
- {
- $curlMock = $this->createMock(Curl::class);
- $curlMock->method('getHeaders')
- ->willReturn(['location' => self::AUTH_URL_ERROR]);
- $curlMock->method('getStatus')
- ->willReturn(302);
-
- $this->curlFactory->method('create')
- ->willReturn($curlMock);
-
- $this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('Could not connect to Adobe IMS Service: invalid_scope.');
- $this->adminImsConnection->auth();
- }
-}
diff --git a/app/code/Magento/AdminAdobeIms/Test/Unit/Model/ImsWebapiTest.php b/app/code/Magento/AdminAdobeIms/Test/Unit/Model/ImsWebapiTest.php
index 6485cb15c6a71..3a86352264d7c 100644
--- a/app/code/Magento/AdminAdobeIms/Test/Unit/Model/ImsWebapiTest.php
+++ b/app/code/Magento/AdminAdobeIms/Test/Unit/Model/ImsWebapiTest.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Test\Unit\Model;
diff --git a/app/code/Magento/AdminAdobeIms/Test/Unit/Plugin/ReplaceVerifyIdentityWithImsPluginTest.php b/app/code/Magento/AdminAdobeIms/Test/Unit/Plugin/ReplaceVerifyIdentityWithImsPluginTest.php
index 4c89b5d350f22..16c780240a03c 100644
--- a/app/code/Magento/AdminAdobeIms/Test/Unit/Plugin/ReplaceVerifyIdentityWithImsPluginTest.php
+++ b/app/code/Magento/AdminAdobeIms/Test/Unit/Plugin/ReplaceVerifyIdentityWithImsPluginTest.php
@@ -8,9 +8,9 @@
namespace Magento\AdminAdobeIms\Test\Unit\Plugin;
use Magento\AdminAdobeIms\Model\Auth;
-use Magento\AdminAdobeIms\Model\ImsConnection;
use Magento\AdminAdobeIms\Plugin\ReplaceVerifyIdentityWithImsPlugin;
use Magento\AdminAdobeIms\Service\ImsConfig;
+use Magento\AdobeImsApi\Api\IsTokenValidInterface;
use Magento\Backend\Model\Auth\StorageInterface;
use Magento\Framework\Exception\AuthenticationException;
use Magento\Framework\Exception\AuthorizationException;
@@ -43,9 +43,9 @@ class ReplaceVerifyIdentityWithImsPluginTest extends TestCase
private $adminImsConfigMock;
/**
- * @var ImsConnection|MockObject
+ * @var IsTokenValidInterface|MockObject
*/
- private $adminImsConnectionMock;
+ private $isTokenValid;
/**
* @return void
@@ -63,13 +63,13 @@ protected function setUp(): void
->getMock();
$this->adminImsConfigMock = $this->createMock(ImsConfig::class);
- $this->adminImsConnectionMock = $this->createMock(ImsConnection::class);
+ $this->isTokenValid = $this->createMock(IsTokenValidInterface::class);
$this->plugin = $objectManagerHelper->getObject(
ReplaceVerifyIdentityWithImsPlugin::class,
[
'adminImsConfig' => $this->adminImsConfigMock,
- 'adminImsConnection' => $this->adminImsConnectionMock,
+ 'isTokenValid' => $this->isTokenValid,
'auth' => $this->authMock,
]
);
@@ -101,7 +101,7 @@ public function testAroundVerifyIdentityCallsProceedWhenModuleIsDisabled(): void
return $expectedResult;
};
- $this->adminImsConnectionMock
+ $this->isTokenValid
->expects($this->never())
->method('validateToken');
@@ -139,7 +139,7 @@ public function testAroundVerifyIdentityVerifiesAccessTokenWhenModuleIsEnabled()
$subject = $this->createMock(User::class);
- $this->adminImsConnectionMock
+ $this->isTokenValid
->expects($this->once())
->method('validateToken')
->willReturn(true);
@@ -184,7 +184,7 @@ public function testAroundVerifyIdentityThrowsExceptionOnInvalidToken(): void
$subject = $this->createMock(User::class);
- $this->adminImsConnectionMock
+ $this->isTokenValid
->expects($this->once())
->method('validateToken')
->willReturn(false);
@@ -233,7 +233,7 @@ public function testAroundVerifyIdentityThrowsExceptionOnEmptyToken(): void
$subject = $this->createMock(User::class);
- $this->adminImsConnectionMock
+ $this->isTokenValid
->expects($this->never())
->method('validateToken');
diff --git a/app/code/Magento/AdminAdobeIms/Test/Unit/Service/AdminLoginProcessServiceTest.php b/app/code/Magento/AdminAdobeIms/Test/Unit/Service/AdminLoginProcessServiceTest.php
index 4fa0aa562f09b..859432ee4551a 100644
--- a/app/code/Magento/AdminAdobeIms/Test/Unit/Service/AdminLoginProcessServiceTest.php
+++ b/app/code/Magento/AdminAdobeIms/Test/Unit/Service/AdminLoginProcessServiceTest.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Test\Unit\Service;
@@ -11,9 +10,9 @@
use Exception;
use Magento\AdminAdobeIms\Exception\AdobeImsAuthorizationException;
use Magento\AdminAdobeIms\Model\Auth;
-use Magento\AdminAdobeIms\Model\LogOut;
use Magento\AdminAdobeIms\Model\User;
use Magento\AdminAdobeIms\Service\AdminLoginProcessService;
+use Magento\AdobeIms\Model\LogOut;
use Magento\AdobeImsApi\Api\Data\TokenResponseInterface;
use Magento\Backend\Model\Auth\StorageInterface;
use Magento\Framework\Stdlib\DateTime\DateTime;
@@ -70,8 +69,7 @@ protected function setUp(): void
$session = $this->getMockBuilder(StorageInterface::class)
->addMethods(['setAdobeAccessToken', 'setTokenLastCheckTime'])
- ->getMockForAbstractClass()
- ;
+ ->getMockForAbstractClass();
$session
->method('setAdobeAccessToken')
->willReturnSelf();
diff --git a/app/code/Magento/AdminAdobeIms/Test/Unit/Service/ImsCommandOptionServiceTest.php b/app/code/Magento/AdminAdobeIms/Test/Unit/Service/ImsCommandOptionServiceTest.php
index 1d715d951fd1c..857cf2efbdbdf 100644
--- a/app/code/Magento/AdminAdobeIms/Test/Unit/Service/ImsCommandOptionServiceTest.php
+++ b/app/code/Magento/AdminAdobeIms/Test/Unit/Service/ImsCommandOptionServiceTest.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdminAdobeIms\Test\Unit\Service;
diff --git a/app/code/Magento/AdminAdobeIms/ViewModel/LinkViewModel.php b/app/code/Magento/AdminAdobeIms/ViewModel/LinkViewModel.php
index 94c6d6c92f89a..c5e3929e8e4bb 100644
--- a/app/code/Magento/AdminAdobeIms/ViewModel/LinkViewModel.php
+++ b/app/code/Magento/AdminAdobeIms/ViewModel/LinkViewModel.php
@@ -7,7 +7,7 @@
namespace Magento\AdminAdobeIms\ViewModel;
-use Magento\AdminAdobeIms\Model\ImsConnection;
+use Magento\AdobeImsApi\Api\AuthorizationInterface;
use Magento\Framework\Exception\InvalidArgumentException;
use Magento\Framework\Message\ManagerInterface as MessageManagerInterface;
use Magento\Framework\View\Element\Block\ArgumentInterface;
@@ -31,12 +31,12 @@ class LinkViewModel implements ArgumentInterface
private MessageManagerInterface $messageManager;
/**
- * @param ImsConnection $connection
+ * @param AuthorizationInterface $authorization
* @param LoggerInterface $logger
* @param MessageManagerInterface $messageManager
*/
public function __construct(
- ImsConnection $connection,
+ AuthorizationInterface $authorization,
LoggerInterface $logger,
MessageManagerInterface $messageManager
) {
@@ -44,7 +44,7 @@ public function __construct(
$this->messageManager = $messageManager;
try {
- $this->authUrl = $connection->auth();
+ $this->authUrl = $authorization->getAuthUrl();
} catch (InvalidArgumentException $e) {
$this->logger->error($e->getMessage());
$this->authUrl = null;
diff --git a/app/code/Magento/AdminAdobeIms/composer.json b/app/code/Magento/AdminAdobeIms/composer.json
index 0da1aa2549305..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": "*",
@@ -20,9 +20,6 @@
"magento/module-jwt-user-token": "*",
"magento/module-security": "*"
},
- "suggest": {
- "magento/module-theme": "*"
- },
"type": "magento2-module",
"license": [
"OSL-3.0",
diff --git a/app/code/Magento/AdminAdobeIms/etc/adminhtml/di.xml b/app/code/Magento/AdminAdobeIms/etc/adminhtml/di.xml
index 0ea6eeaf8f0c5..d31abbf60219c 100644
--- a/app/code/Magento/AdminAdobeIms/etc/adminhtml/di.xml
+++ b/app/code/Magento/AdminAdobeIms/etc/adminhtml/di.xml
@@ -84,4 +84,15 @@
+
+
+
+
+ -
+
- Magento\AdminAdobeIms\Model\Authorization\AdobeImsAdminTokenUserContext\Proxy
+ - 20
+
+
+
+
diff --git a/app/code/Magento/AdminAdobeIms/etc/config.xml b/app/code/Magento/AdminAdobeIms/etc/config.xml
index 9e9726d083f4f..6d338b5bd608c 100644
--- a/app/code/Magento/AdminAdobeIms/etc/config.xml
+++ b/app/code/Magento/AdminAdobeIms/etc/config.xml
@@ -11,8 +11,8 @@
0
-
-
+
+
AdobeID
openid
@@ -21,15 +21,14 @@
org.read
- 0
-
- https://ims-na1.adobelogin.com/ims/token
-
-
-
-
-
+
+
+
+
+
+ https://graph.identity.adobe.com
+ https://static.adobelogin.com
admin_adobe_ims_email_header_template
diff --git a/app/code/Magento/AdminAdobeIms/etc/di.xml b/app/code/Magento/AdminAdobeIms/etc/di.xml
index 6f3b05f6f2bb6..5da3e654b2e75 100644
--- a/app/code/Magento/AdminAdobeIms/etc/di.xml
+++ b/app/code/Magento/AdminAdobeIms/etc/di.xml
@@ -11,6 +11,7 @@
+
@@ -65,9 +66,6 @@
type="Magento\AdminAdobeIms\Plugin\DisableAdminLoginAuthPlugin"/>
-
-
-
diff --git a/app/code/Magento/AdminAdobeIms/i18n/en_US.csv b/app/code/Magento/AdminAdobeIms/i18n/en_US.csv
new file mode 100644
index 0000000000000..2f62e7c9109d1
--- /dev/null
+++ b/app/code/Magento/AdminAdobeIms/i18n/en_US.csv
@@ -0,0 +1,52 @@
+"Admin Adobe IMS integration is disabled","Admin Adobe IMS integration is disabled"
+"Admin Adobe IMS integration is enabled","Admin Adobe IMS integration is enabled"
+"The Client ID, Client Secret, Organization ID and 2FA are required when enabling the Admin Adobe IMS Module","The Client ID, Client Secret, Organization ID and 2FA are required when enabling the Admin Adobe IMS Module"
+"Module is disabled","Module is disabled"
+"Admin Adobe IMS integration is %1","Admin Adobe IMS integration is %1"
+"Adobe Sign-In is disabled.","Adobe Sign-In is disabled."
+"Authorization was successful","Authorization was successful"
+"Session Access Token is not valid","Session Access Token is not valid"
+"Login request error %1","Login request error %1"
+"An authentication error occurred. Verify and try again.","An authentication error occurred. Verify and try again."
+"You don't have access to this Commerce instance","You don't have access to this Commerce instance"
+"Unable to sign in with the Adobe ID","Unable to sign in with the Adobe ID"
+"Could not save ims token.","Could not save ims token."
+"Could not find ims token id: %id.","Could not find ims token id: %id."
+"Could not delete ims tokens for admin user id %1.","Could not delete ims tokens for admin user id %1."
+"Could not save ims user.","Could not save ims user."
+"The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later.","The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later."
+"More permissions are needed to access this.","More permissions are needed to access this."
+"Please sign in with Adobe ID","Please sign in with Adobe ID"
+"Admin token generation is disabled. Please use Adobe IMS ACCESS_TOKEN.","Admin token generation is disabled. Please use Adobe IMS ACCESS_TOKEN."
+"Identity Verification","Identity Verification"
+"Verify Identity with Adobe IMS","Verify Identity with Adobe IMS"
+"Confirm Identity","Confirm Identity"
+"To apply changes you need to verify your Adobe identity.","To apply changes you need to verify your Adobe identity."
+"Identity Verified with Adobe IMS","Identity Verified with Adobe IMS"
+"Please perform the AdobeIms reAuth and try again.","Please perform the AdobeIms reAuth and try again."
+"Use the same email user has in Adobe IMS organization.","Use the same email user has in Adobe IMS organization."
+"The tokens couldn't be revoked.","The tokens couldn't be revoked."
+"No matching admin user found for Adobe ID.","No matching admin user found for Adobe ID."
+"This field is required to enable the Admin Adobe IMS Module","This field is required to enable the Admin Adobe IMS Module"
+"No valid Organization ID provided","No valid Organization ID provided"
+"No valid Client ID provided","No valid Client ID provided"
+"No valid Client Secret provided","No valid Client Secret provided"
+"The ims token wasn't found.","The ims token wasn't found."
+"Sign in to access the Adobe Commerce for your organization.","Sign in to access the Adobe Commerce for your organization."
+"Sign In","Sign In"
+"This Commerce instance is managed by an organization. Contact your organization administrator to request access.","This Commerce instance is managed by an organization. Contact your organization administrator to request access."
+"Sign in with Adobe ID","Sign in with Adobe ID"
+Footer,Footer
+"User Guides","User Guides"
+"Customer Support","Customer Support"
+Forums,Forums
+Header,Header
+"%user_name, you now have access to Adobe Commerce","%user_name, you now have access to Adobe Commerce"
+"Your administrator at %store_name has given you access to Adobe Commerce","Your administrator at %store_name has given you access to Adobe Commerce"
+"Get started","Get started"
+"Here are a few links to help you get up and running:","Here are a few links to help you get up and running:"
+Documentation,Documentation
+"Release notes","Release notes"
+"If you have any questions about access to Adobe Commerce, contact your administrator or your Adobe account team for more information.","If you have any questions about access to Adobe Commerce, contact your administrator or your Adobe account team for more information."
+"Enable Logging for Admin Adobe IMS Module","Enable Logging for Admin Adobe IMS Module"
+"Adobe Commerce","Adobe Commerce"
diff --git a/app/code/Magento/AdminAdobeIms/registration.php b/app/code/Magento/AdminAdobeIms/registration.php
index 94bafa317d9f6..81fe72eb260c7 100644
--- a/app/code/Magento/AdminAdobeIms/registration.php
+++ b/app/code/Magento/AdminAdobeIms/registration.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
use Magento\Framework\Component\ComponentRegistrar;
diff --git a/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/AdobeStock_232925587.png b/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/AdobeStock_232925587.png
index 1c9e42b94f4dc..396c9bd39fca0 100644
Binary files a/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/AdobeStock_232925587.png and b/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/AdobeStock_232925587.png differ
diff --git a/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/AdobeStock_232925587@2x.png b/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/AdobeStock_232925587@2x.png
index 60baa1853ffbf..c5ddacefb9197 100644
Binary files a/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/AdobeStock_232925587@2x.png and b/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/AdobeStock_232925587@2x.png differ
diff --git a/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/adobe-commerce-dark.png b/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/adobe-commerce-dark.png
index 6fa975161dc80..61278e3b84e7b 100644
Binary files a/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/adobe-commerce-dark.png and b/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/adobe-commerce-dark.png differ
diff --git a/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/adobe-commerce-light.png b/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/adobe-commerce-light.png
index e5045d77e15a2..8118aab5303ed 100644
Binary files a/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/adobe-commerce-light.png and b/app/code/Magento/AdminAdobeIms/view/adminhtml/web/images/adobe-commerce-light.png differ
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/Model/Feed.php b/app/code/Magento/AdminNotification/Model/Feed.php
index e5329d35c8b0e..07f717f2880db 100644
--- a/app/code/Magento/AdminNotification/Model/Feed.php
+++ b/app/code/Magento/AdminNotification/Model/Feed.php
@@ -5,6 +5,7 @@
*/
namespace Magento\AdminNotification\Model;
+use Laminas\Http\Request;
use Magento\AdminNotification\Model\InboxFactory;
use Magento\Backend\App\ConfigInterface;
use Magento\Framework\App\DeploymentConfig;
@@ -227,7 +228,7 @@ public function getFeedData()
{
/** @var Curl $curl */
$curl = $this->curlFactory->create();
- $curl->setConfig(
+ $curl->setOptions(
[
'timeout' => 2,
'useragent' => $this->productMetadata->getName()
@@ -236,7 +237,7 @@ public function getFeedData()
'referer' => $this->urlBuilder->getUrl('*/*/*')
]
);
- $curl->write(\Zend_Http_Client::GET, $this->getFeedUrl(), '1.0');
+ $curl->write(Request::METHOD_GET, $this->getFeedUrl(), '1.0');
$data = $curl->read();
$data = preg_split('/^\r?$/m', $data, 2);
$data = trim($data[1] ?? '');
diff --git a/app/code/Magento/AdminNotification/Model/System/Message/Security.php b/app/code/Magento/AdminNotification/Model/System/Message/Security.php
index 7cbdd68727b94..9aabb13b5ab7a 100644
--- a/app/code/Magento/AdminNotification/Model/System/Message/Security.php
+++ b/app/code/Magento/AdminNotification/Model/System/Message/Security.php
@@ -6,18 +6,28 @@
namespace Magento\AdminNotification\Model\System\Message;
+use Laminas\Http\Request;
+use Laminas\Http\Response;
+use Magento\Backend\App\ConfigInterface;
+use Magento\Framework\App\CacheInterface;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\HTTP\Adapter\Curl;
+use Magento\Framework\HTTP\Adapter\CurlFactory;
+use Magento\Framework\Notification\MessageInterface;
+use Magento\Framework\Phrase;
use Magento\Store\Model\Store;
+use Throwable;
/**
* @api
* @since 100.0.2
*/
-class Security implements \Magento\Framework\Notification\MessageInterface
+class Security implements MessageInterface
{
/**
* Cache key for saving verification result
*/
- const VERIFICATION_RESULT_CACHE_KEY = 'configuration_files_access_level_verification';
+ public const VERIFICATION_RESULT_CACHE_KEY = 'configuration_files_access_level_verification';
/**
* File path for verification
@@ -34,36 +44,36 @@ class Security implements \Magento\Framework\Notification\MessageInterface
private $_verificationTimeOut = 2;
/**
- * @var \Magento\Framework\App\CacheInterface
+ * @var CacheInterface
*/
protected $_cache;
/**
- * @var \Magento\Backend\App\ConfigInterface
+ * @var ConfigInterface
*/
protected $_backendConfig;
/**
- * @var \Magento\Framework\App\Config\ScopeConfigInterface
+ * @var ScopeConfigInterface
*/
protected $_config;
/**
- * @var \Magento\Framework\HTTP\Adapter\CurlFactory
+ * @var CurlFactory
*/
protected $_curlFactory;
/**
- * @param \Magento\Framework\App\CacheInterface $cache
- * @param \Magento\Backend\App\ConfigInterface $backendConfig
- * @param \Magento\Framework\App\Config\ScopeConfigInterface $config
- * @param \Magento\Framework\HTTP\Adapter\CurlFactory $curlFactory
+ * @param CacheInterface $cache
+ * @param ConfigInterface $backendConfig
+ * @param ScopeConfigInterface $config
+ * @param CurlFactory $curlFactory
*/
public function __construct(
- \Magento\Framework\App\CacheInterface $cache,
- \Magento\Backend\App\ConfigInterface $backendConfig,
- \Magento\Framework\App\Config\ScopeConfigInterface $config,
- \Magento\Framework\HTTP\Adapter\CurlFactory $curlFactory
+ CacheInterface $cache,
+ ConfigInterface $backendConfig,
+ ScopeConfigInterface $config,
+ CurlFactory $curlFactory
) {
$this->_cache = $cache;
$this->_backendConfig = $backendConfig;
@@ -100,12 +110,12 @@ private function _isFileAccessible()
{
$unsecureBaseURL = $this->_config->getValue(Store::XML_PATH_UNSECURE_BASE_URL, 'default');
- /** @var $http \Magento\Framework\HTTP\Adapter\Curl */
+ /** @var $http Curl */
$http = $this->_curlFactory->create();
- $http->setConfig(['timeout' => $this->_verificationTimeOut]);
- $http->write(\Zend_Http_Client::POST, $unsecureBaseURL . $this->_filePath);
+ $http->setOptions(['timeout' => $this->_verificationTimeOut]);
+ $http->write(Request::METHOD_POST, $unsecureBaseURL . $this->_filePath);
$responseBody = $http->read();
- $responseCode = \Zend_Http_Response::extractCode($responseBody);
+ $responseCode = $this->extractCodeFromResponse($responseBody);
$http->close();
return $responseCode == 200;
@@ -134,7 +144,7 @@ public function isDisplayed()
/**
* Retrieve message text
*
- * @return \Magento\Framework\Phrase
+ * @return Phrase
*/
public function getText()
{
@@ -151,6 +161,24 @@ public function getText()
*/
public function getSeverity()
{
- return \Magento\Framework\Notification\MessageInterface::SEVERITY_CRITICAL;
+ return MessageInterface::SEVERITY_CRITICAL;
+ }
+
+ /**
+ * Extract the response code from a response string
+ *
+ * @param string $responseString
+ *
+ * @return false|int
+ */
+ private function extractCodeFromResponse(string $responseString)
+ {
+ try {
+ $responseCode = Response::fromString($responseString)->getStatusCode();
+ } catch (Throwable $e) {
+ $responseCode = false;
+ }
+
+ return $responseCode;
}
}
diff --git a/app/code/Magento/AdminNotification/Test/Unit/Model/FeedTest.php b/app/code/Magento/AdminNotification/Test/Unit/Model/FeedTest.php
index b401467f04a02..434c4a5cb54bf 100644
--- a/app/code/Magento/AdminNotification/Test/Unit/Model/FeedTest.php
+++ b/app/code/Magento/AdminNotification/Test/Unit/Model/FeedTest.php
@@ -173,7 +173,7 @@ public function testCheckUpdate(bool $callInbox, string $curlRequest): void
$this->curlFactory
->method('create')
->willReturn($this->curl);
- $this->curl->expects($this->once())->method('setConfig')->with($configValues)->willReturnSelf();
+ $this->curl->expects($this->once())->method('setOptions')->with($configValues)->willReturnSelf();
$this->curl->expects($this->once())->method('read')->willReturn($curlRequest);
$this->backendConfig->expects($this->once())->method('isSetFlag')->willReturn(false);
$this->backendConfig
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/Controller/Adminhtml/OAuth/Callback.php b/app/code/Magento/AdobeIms/Controller/Adminhtml/OAuth/Callback.php
index 55f86062685e8..dcbe1f6e94f2a 100644
--- a/app/code/Magento/AdobeIms/Controller/Adminhtml/OAuth/Callback.php
+++ b/app/code/Magento/AdobeIms/Controller/Adminhtml/OAuth/Callback.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdobeIms\Controller\Adminhtml\OAuth;
diff --git a/app/code/Magento/AdobeIms/Exception/AdobeImsOrganizationAuthorizationException.php b/app/code/Magento/AdobeIms/Exception/AdobeImsOrganizationAuthorizationException.php
new file mode 100644
index 0000000000000..2d32870c7312a
--- /dev/null
+++ b/app/code/Magento/AdobeIms/Exception/AdobeImsOrganizationAuthorizationException.php
@@ -0,0 +1,20 @@
+curlFactory = $curlFactory;
+ $this->imsConfig = $imsConfig;
+ $this->parameters = $parameters;
+ $this->uri = $uri;
+ }
+
+ /**
+ * Get authorization url
+ *
+ * @param string|null $clientId
+ * @return string
+ * @throws InvalidArgumentException
+ */
+ public function getAuthUrl(?string $clientId = null): string
+ {
+ $authUrl = $this->imsConfig->getAdminAdobeImsAuthUrl($clientId);
+ $imsUrl = $this->getAuthorizationLocation($authUrl);
+ $this->validateRedirectUrls($authUrl, $imsUrl);
+
+ return $imsUrl;
+ }
+
+ /**
+ * Test if given ClientID is valid and is able to return an authorization URL
+ *
+ * @param string $clientId
+ * @return bool
+ * @throws InvalidArgumentException
+ */
+ public function testAuth(string $clientId): bool
+ {
+ $location = $this->getAuthUrl($clientId);
+ return $location !== '';
+ }
+
+ /**
+ * Get authorization location from adobeIMS
+ *
+ * @param string $authUrl
+ * @return string
+ * @throws InvalidArgumentException
+ */
+ private function getAuthorizationLocation(string $authUrl): string
+ {
+ $curl = $this->curlFactory->create();
+
+ $curl->addHeader('Content-Type', 'application/x-www-form-urlencoded');
+ $curl->addHeader('cache-control', 'no-cache');
+ $curl->get($authUrl);
+
+ $this->validateResponse($curl);
+
+ return $curl->getHeaders()['location'] ?? '';
+ }
+
+ /**
+ * Validate authorization call response
+ *
+ * @param Curl $curl
+ * @return void
+ * @throws InvalidArgumentException
+ */
+ private function validateResponse(Curl $curl): void
+ {
+ if (isset($curl->getHeaders()['location'])) {
+ if (preg_match(
+ '/error=([a-z_]+)/i',
+ $curl->getHeaders()['location'],
+ $error
+ )
+ && isset($error[0], $error[1])
+ ) {
+ throw new InvalidArgumentException(
+ __('Could not connect to Adobe IMS Service: %1.', $error[1])
+ );
+ }
+ }
+
+ if ($curl->getStatus() !== self::HTTP_REDIRECT_CODE) {
+ throw new InvalidArgumentException(
+ __('Could not get a valid response from Adobe IMS Service.')
+ );
+ }
+ }
+
+ /**
+ * Validate current host and IMS returned host to make sure credentials belongs to correct project.
+ *
+ * @param string $authUrl
+ * @param string $imsUrl
+ * @throws InvalidArgumentException
+ */
+ private function validateRedirectUrls(string $authUrl, string $imsUrl)
+ {
+ $imsRedirectUrlHost = $this->getRedirectUrlHost($imsUrl);
+ $currentRedirectHost = $this->getRedirectUrlHost($authUrl);
+ if (!($imsRedirectUrlHost && $currentRedirectHost) || !($imsRedirectUrlHost === $currentRedirectHost)) {
+ throw new InvalidArgumentException(
+ __('Could not get a valid response from Adobe IMS Service.')
+ );
+ }
+ }
+
+ /**
+ * Get host from redirect Url
+ *
+ * @param string $imsUrl
+ * @return string|null
+ */
+ private function getRedirectUrlHost(string $imsUrl): ?string
+ {
+ $this->uri->parse($imsUrl);
+ $this->parameters->fromString($this->uri->getQuery());
+ $urlParams = $this->parameters->toArray();
+ if (!isset($urlParams['redirect_uri'])) {
+ foreach ($urlParams as $param => $value) {
+ if ($param === 'callback' || $param === 'uc_callback') {
+ $this->getRedirectUrlHost($value);
+ } elseif ($this->redirectHost) {
+ break;
+ }
+ }
+ } elseif (isset($urlParams['redirect_uri'])) {
+ $this->uri->parse($urlParams['redirect_uri']);
+ $this->redirectHost = $this->uri->getHost();
+ }
+ return $this->redirectHost;
+ }
+}
diff --git a/app/code/Magento/AdobeIms/Model/Config.php b/app/code/Magento/AdobeIms/Model/Config.php
index 3559a70d2e393..278ffbc2ca0f5 100644
--- a/app/code/Magento/AdobeIms/Model/Config.php
+++ b/app/code/Magento/AdobeIms/Model/Config.php
@@ -8,8 +8,13 @@
namespace Magento\AdobeIms\Model;
use Magento\AdobeImsApi\Api\ConfigInterface;
+use Magento\Backend\Model\UrlInterface as BackendUrlInterface;
use Magento\Config\Model\Config\Backend\Admin\Custom;
use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\Config\Storage\WriterInterface;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Encryption\EncryptorInterface;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\UrlInterface;
/**
@@ -17,13 +22,31 @@
*/
class Config implements ConfigInterface
{
+ private const XML_CONFIG_PATH = 'adobe_ims/integration/';
+ public const XML_PATH_ENABLED = 'adobe_ims/integration/admin_enabled';
+ private const XML_PATH_ORGANIZATION_ID = 'adobe_ims/integration/organization_id';
private const XML_PATH_API_KEY = 'adobe_ims/integration/api_key';
private const XML_PATH_PRIVATE_KEY = 'adobe_ims/integration/private_key';
private const XML_PATH_TOKEN_URL = 'adobe_ims/integration/token_url';
private const XML_PATH_AUTH_URL_PATTERN = 'adobe_ims/integration/auth_url_pattern';
- private const XML_PATH_LOGOUT_URL_PATTERN = 'adobe_ims/integration/logout_url';
private const XML_PATH_IMAGE_URL_PATTERN = 'adobe_ims/integration/image_url';
private const OAUTH_CALLBACK_URL = 'adobe_ims/oauth/callback';
+ private const XML_PATH_PROFILE_URL = 'adobe_ims/integration/profile_url';
+ private const XML_PATH_VALIDATE_TOKEN_URL = 'adobe_ims/integration/validate_token_url';
+ private const XML_PATH_ADMIN_AUTH_URL_PATTERN = 'adobe_ims/integration/admin/auth_url_pattern';
+ private const XML_PATH_ADMIN_REAUTH_URL_PATTERN = 'adobe_ims/integration/admin/reauth_url_pattern';
+ private const OAUTH_CALLBACK_IMS_URL = 'adobe_ims_auth/oauth/';
+ private const XML_PATH_ADMIN_ADOBE_IMS_SCOPES = 'adobe_ims/integration/admin/scopes';
+ private const XML_PATH_ADOBE_IMS_SCOPES = 'adobe_ims/integration/scopes';
+ private const XML_PATH_LOGOUT_URL = 'adobe_ims/integration/logout_url';
+ public const XML_PATH_ADMIN_LOGOUT_URL = 'adobe_ims/integration/admin_logout_url';
+ private const XML_PATH_CERTIFICATE_PATH = 'adobe_ims/integration/certificate_path';
+ private const XML_PATH_ORGANIZATION_MEMBERSHIP_URL = 'adobe_ims/integration/organization_membership_url';
+ /**
+ * AdminAdobeIms callback urls
+ */
+ private const IMS_CALLBACK = 'imscallback';
+ private const IMS_REAUTH_CALLBACK = 'imsreauthcallback';
/**
* @var ScopeConfigInterface
@@ -35,18 +58,45 @@ class Config implements ConfigInterface
*/
private $url;
+ /**
+ * @var WriterInterface
+ */
+ private WriterInterface $writer;
+
+ /**
+ * @var EncryptorInterface
+ */
+ private EncryptorInterface $encryptor;
+
+ /**
+ * @var BackendUrlInterface
+ */
+ private BackendUrlInterface $backendUrl;
+
/**
* Config constructor.
*
* @param ScopeConfigInterface $scopeConfig
* @param UrlInterface $url
+ * @param WriterInterface|null $writer
+ * @param EncryptorInterface|null $encryptor
+ * @param BackendUrlInterface|null $backendUrl
*/
public function __construct(
ScopeConfigInterface $scopeConfig,
- UrlInterface $url
+ UrlInterface $url,
+ WriterInterface $writer = null,
+ EncryptorInterface $encryptor = null,
+ BackendUrlInterface $backendUrl = null
) {
$this->scopeConfig = $scopeConfig;
$this->url = $url;
+ $this->writer = $writer ?? ObjectManager::getInstance()
+ ->get(WriterInterface::class);
+ $this->encryptor = $encryptor ?? ObjectManager::getInstance()
+ ->get(EncryptorInterface::class);
+ $this->backendUrl = $backendUrl ?? ObjectManager::getInstance()
+ ->get(BackendUrlInterface::class);
}
/**
@@ -70,7 +120,11 @@ public function getPrivateKey(): string
*/
public function getTokenUrl(): string
{
- return $this->scopeConfig->getValue(self::XML_PATH_TOKEN_URL);
+ return str_replace(
+ ['#{imsUrl}'],
+ [$this->getImsUrl()],
+ $this->scopeConfig->getValue(self::XML_PATH_TOKEN_URL)
+ );
}
/**
@@ -79,8 +133,14 @@ public function getTokenUrl(): string
public function getAuthUrl(): string
{
return str_replace(
- ['#{client_id}', '#{redirect_uri}', '#{locale}'],
- [$this->getApiKey(), $this->getCallBackUrl(), $this->getLocale()],
+ ['#{imsUrl}','#{client_id}', '#{redirect_uri}', '#{scope}', '#{locale}'],
+ [
+ $this->getImsUrl(),
+ $this->getApiKey(),
+ $this->getCallBackUrl(),
+ $this->getScopes(),
+ $this->getLocale(),
+ ],
$this->scopeConfig->getValue(self::XML_PATH_AUTH_URL_PATTERN) ?? ''
);
}
@@ -108,10 +168,14 @@ private function getLocale(): string
*/
public function getLogoutUrl(string $accessToken, string $redirectUrl = '') : string
{
+ // there is no success response with empty redirect url
+ if ($redirectUrl === '') {
+ $redirectUrl = 'self';
+ }
return str_replace(
- ['#{access_token}', '#{redirect_uri}'],
- [$accessToken, $redirectUrl],
- $this->scopeConfig->getValue(self::XML_PATH_LOGOUT_URL_PATTERN) ?? ''
+ ['#{imsUrl}', '#{access_token}', '#{redirect_uri}'],
+ [$this->getImsUrl(), $accessToken, $redirectUrl],
+ $this->scopeConfig->getValue(self::XML_PATH_LOGOUT_URL) ?? ''
);
}
@@ -121,9 +185,307 @@ public function getLogoutUrl(string $accessToken, string $redirectUrl = '') : st
public function getProfileImageUrl(): string
{
return str_replace(
- ['#{api_key}'],
- [$this->getApiKey()],
+ ['#{imageUrl}', '#{api_key}'],
+ [$this->getImsUrl('imageUrl'), $this->getApiKey()],
$this->scopeConfig->getValue(self::XML_PATH_IMAGE_URL_PATTERN) ?? ''
);
}
+
+ /**
+ * Get Profile URL
+ *
+ * @return string
+ */
+ public function getProfileUrl(): string
+ {
+ return str_replace(
+ ['#{imsUrl}', '#{client_id}'],
+ [$this->getImsUrl(), $this->getApiKey()],
+ $this->scopeConfig->getValue(self::XML_PATH_PROFILE_URL)
+ );
+ }
+
+ /**
+ * Get Token validation url
+ *
+ * @param string $code
+ * @param string $tokenType
+ * @return string
+ */
+ public function getValidateTokenUrl(string $code, string $tokenType): string
+ {
+ return str_replace(
+ ['#{imsUrl}', '#{token}', '#{client_id}', '#{token_type}'],
+ [$this->getImsUrl(), $code, $this->getApiKey(), $tokenType],
+ $this->scopeConfig->getValue(self::XML_PATH_VALIDATE_TOKEN_URL)
+ );
+ }
+
+ /**
+ * Generate the AdminAdobeIms AuthUrl with given clientID or the ClientID stored in the config
+ *
+ * @param string|null $clientId
+ * @return string
+ */
+ public function getAdminAdobeImsAuthUrl(?string $clientId): string
+ {
+ if ($clientId === null) {
+ $clientId = $this->getApiKey();
+ }
+
+ return str_replace(
+ ['#{imsUrl}', '#{client_id}', '#{redirect_uri}', '#{scope}', '#{locale}'],
+ [
+ $this->getImsUrl(),
+ $clientId,
+ $this->getAdminAdobeImsCallBackUrl(),
+ $this->getAdminScopes(),
+ $this->getLocale()
+ ],
+ $this->scopeConfig->getValue(self::XML_PATH_ADMIN_AUTH_URL_PATTERN)
+ );
+ }
+
+ /**
+ * Generate the AdminAdobeIms AuthUrl for reAuth
+ *
+ * @return string
+ */
+ public function getAdminAdobeImsReAuthUrl(): string
+ {
+ return str_replace(
+ ['#{imsUrl}', '#{client_id}', '#{redirect_uri}', '#{scope}', '#{locale}'],
+ [
+ $this->getImsUrl(),
+ $this->getApiKey(),
+ $this->getAdminAdobeImsReAuthCallBackUrl(),
+ $this->getAdminScopes(),
+ $this->getLocale()
+ ],
+ $this->scopeConfig->getValue(self::XML_PATH_ADMIN_REAUTH_URL_PATTERN)
+ );
+ }
+
+ /**
+ * Get BackendLogout URL
+ *
+ * @param string $accessToken
+ * @return string
+ */
+ public function getBackendLogoutUrl(string $accessToken) : string
+ {
+ return str_replace(
+ ['#{imsUrl}', '#{access_token}', '#{client_secret}', '#{client_id}'],
+ [$this->getImsUrl(), $accessToken, $this->getPrivateKey(), $this->getApiKey()],
+ $this->scopeConfig->getValue(self::XML_PATH_ADMIN_LOGOUT_URL)
+ );
+ }
+
+ /**
+ * IMS certificate (public key) location retrieval
+ *
+ * @param string $fileName
+ * @return string
+ */
+ public function getCertificateUrl(string $fileName): string
+ {
+ return str_replace(
+ ['#{certificateUrl}'],
+ [$this->getImsUrl('certificateUrl')],
+ $this->scopeConfig->getValue(self::XML_PATH_CERTIFICATE_PATH) . $fileName
+ );
+ }
+
+ /**
+ * Get url to check organization membership
+ *
+ * @param string $orgId
+ * @return string
+ */
+ public function getOrganizationMembershipUrl(string $orgId): string
+ {
+ return str_replace(
+ ['#{organizationMembershipUrl}', '#{org_id}'],
+ [$this->getImsUrl('organizationMembershipUrl'), $orgId],
+ $this->scopeConfig->getValue(self::XML_PATH_ORGANIZATION_MEMBERSHIP_URL)
+ );
+ }
+
+ /**
+ * Get scopes for AdobeIms
+ *
+ * @return string
+ */
+ private function getScopes(): string
+ {
+ return implode(
+ ',',
+ $this->scopeConfig->getValue(self::XML_PATH_ADOBE_IMS_SCOPES)
+ );
+ }
+
+ /**
+ * Get scopes for AdobeIms
+ *
+ * @return string
+ */
+ private function getAdminScopes(): string
+ {
+ return implode(
+ ',',
+ $this->scopeConfig->getValue(self::XML_PATH_ADMIN_ADOBE_IMS_SCOPES)
+ );
+ }
+
+ /**
+ * Get ims Urls
+ *
+ * @param string $urlType
+ * @return string
+ */
+ private function getImsUrl(string $urlType = 'imsUrl'): string
+ {
+ return $this->scopeConfig->getValue(self::XML_CONFIG_PATH . $urlType);
+ }
+
+ /**
+ * Enable Admin Adobe IMS Module and set Client ID and Client Secret and Organization ID and Two Factor Enabled
+ *
+ * @param string $clientId
+ * @param string $clientSecret
+ * @param string $organizationId
+ * @param bool $isAdobeIms2FAEnabled
+ * @return void
+ * @throws LocalizedException
+ */
+ public function enableModule(
+ string $clientId,
+ string $clientSecret,
+ string $organizationId,
+ bool $isAdobeIms2FAEnabled
+ ): void {
+ if (!$isAdobeIms2FAEnabled) {
+ throw new LocalizedException(
+ __('2FA is required when enabling the Admin Adobe IMS Module')
+ );
+ }
+
+ $this->updateConfig(
+ self::XML_PATH_ENABLED,
+ '1'
+ );
+
+ $this->updateSecureConfig(
+ self::XML_PATH_ORGANIZATION_ID,
+ $organizationId
+ );
+
+ $this->updateSecureConfig(
+ self::XML_PATH_API_KEY,
+ $clientId
+ );
+
+ $this->updateSecureConfig(
+ self::XML_PATH_PRIVATE_KEY,
+ $clientSecret
+ );
+ }
+
+ /**
+ * Disable Admin Adobe IMS Module and unset Client ID and Client Secret from config
+ *
+ * @return void
+ */
+ public function disableModule(): void
+ {
+ $this->updateConfig(
+ self::XML_PATH_ENABLED,
+ '0'
+ );
+
+ $this->deleteConfig(self::XML_PATH_ORGANIZATION_ID);
+ $this->deleteConfig(self::XML_PATH_API_KEY);
+ $this->deleteConfig(self::XML_PATH_PRIVATE_KEY);
+ }
+
+ /**
+ * Get callback url for AdminAdobeIms Module
+ *
+ * @return string
+ */
+ private function getAdminAdobeImsCallBackUrl(): string
+ {
+ return $this->backendUrl->getUrl(
+ self::OAUTH_CALLBACK_IMS_URL . self::IMS_CALLBACK
+ );
+ }
+
+ /**
+ * Get reAuth callback url for AdminAdobeIms Module
+ *
+ * @return string
+ */
+ private function getAdminAdobeImsReAuthCallBackUrl(): string
+ {
+ return $this->backendUrl->getUrl(
+ self::OAUTH_CALLBACK_IMS_URL . self::IMS_REAUTH_CALLBACK
+ );
+ }
+
+ /**
+ * Update config using config writer
+ *
+ * @param string $path
+ * @param string $value
+ * @return void
+ */
+ private function updateConfig(string $path, string $value): void
+ {
+ $this->writer->save(
+ $path,
+ $value
+ );
+ }
+
+ /**
+ * Update encrypted config setting
+ *
+ * @param string $path
+ * @param string $value
+ * @return void
+ */
+ private function updateSecureConfig(string $path, string $value): void
+ {
+ $value = str_replace(['\n', '\r'], ["\n", "\r"], $value);
+
+ if (!preg_match('/^\*+$/', $value) && !empty($value)) {
+ $value = $this->encryptor->encrypt($value);
+
+ $this->writer->save(
+ $path,
+ $value
+ );
+ }
+ }
+
+ /**
+ * Delete config value
+ *
+ * @param string $path
+ * @return void
+ */
+ private function deleteConfig(string $path): void
+ {
+ $this->writer->delete($path);
+ }
+
+ /**
+ * Retrieve Organization Id
+ *
+ * @return string
+ */
+ public function getOrganizationId(): string
+ {
+ return $this->scopeConfig->getValue(self::XML_PATH_ORGANIZATION_ID);
+ }
}
diff --git a/app/code/Magento/AdobeIms/Model/GetAccessToken.php b/app/code/Magento/AdobeIms/Model/GetAccessToken.php
index 3dc41d42d5ab2..4a5c4a49b9b9c 100644
--- a/app/code/Magento/AdobeIms/Model/GetAccessToken.php
+++ b/app/code/Magento/AdobeIms/Model/GetAccessToken.php
@@ -55,7 +55,9 @@ public function execute(int $adminUserId = null): ?string
{
try {
$adminUserId = $adminUserId ?? (int) $this->userContext->getUserId();
- return $this->userProfileRepository->getByUserId($adminUserId)->getAccessToken();
+ return $this->encryptor->decrypt(
+ $this->userProfileRepository->getByUserId($adminUserId)->getAccessToken()
+ );
} catch (NoSuchEntityException $exception) {
return null;
}
diff --git a/app/code/Magento/AdobeIms/Model/GetProfile.php b/app/code/Magento/AdobeIms/Model/GetProfile.php
new file mode 100644
index 0000000000000..c325f8aa78f47
--- /dev/null
+++ b/app/code/Magento/AdobeIms/Model/GetProfile.php
@@ -0,0 +1,72 @@
+imsConfig = $imsConfig;
+ $this->curlFactory = $curlFactory;
+ $this->json = $json;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getProfile(string $code)
+ {
+ $curl = $this->curlFactory->create();
+
+ $curl->addHeader('Content-Type', 'application/x-www-form-urlencoded');
+ $curl->addHeader('cache-control', 'no-cache');
+ $curl->addHeader('Authorization', 'Bearer ' . $code);
+
+ $curl->get($this->imsConfig->getProfileUrl());
+
+ if ($curl->getBody() === '') {
+ throw new AuthorizationException(
+ __('Profile body is empty')
+ );
+ }
+
+ return $this->json->unserialize($curl->getBody());
+ }
+}
diff --git a/app/code/Magento/AdobeIms/Model/GetToken.php b/app/code/Magento/AdobeIms/Model/GetToken.php
index 8a2ef8e07c873..09723a6c72095 100644
--- a/app/code/Magento/AdobeIms/Model/GetToken.php
+++ b/app/code/Magento/AdobeIms/Model/GetToken.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdobeIms\Model;
@@ -87,4 +86,12 @@ public function execute(string $code): TokenResponseInterface
return $this->tokenResponseFactory->create(['data' => $response]);
}
+
+ /**
+ * @inheritdoc
+ */
+ public function getTokenResponse(string $code): TokenResponseInterface
+ {
+ return $this->execute($code);
+ }
}
diff --git a/app/code/Magento/AdobeIms/Model/IsTokenValid.php b/app/code/Magento/AdobeIms/Model/IsTokenValid.php
new file mode 100644
index 0000000000000..e457e61aeb58d
--- /dev/null
+++ b/app/code/Magento/AdobeIms/Model/IsTokenValid.php
@@ -0,0 +1,101 @@
+curlFactory = $curlFactory;
+ $this->config = $config;
+ $this->json = $json;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Validate token
+ *
+ * @param string|null $token
+ * @param string $tokenType
+ * @return bool
+ * @throws AuthorizationException
+ */
+ public function validateToken(?string $token, string $tokenType = 'access_token'): bool
+ {
+ $isTokenValid = false;
+
+ if ($token === null) {
+ return false;
+ }
+
+ $curl = $this->curlFactory->create();
+
+ $curl->addHeader('Content-Type', 'application/x-www-form-urlencoded');
+ $curl->addHeader('cache-control', 'no-cache');
+
+ $curl->post(
+ $this->config->getValidateTokenUrl($token, $tokenType),
+ []
+ );
+
+ if ($curl->getBody() === '') {
+ throw new AuthorizationException(
+ __('Could not verify the access_token')
+ );
+ }
+
+ $body = $this->json->unserialize($curl->getBody());
+
+ if (isset($body['valid'])) {
+ $isTokenValid = (bool)$body['valid'];
+ }
+
+ if (!$isTokenValid && isset($body['reason'])) {
+ $this->logger->info($tokenType . ' is not valid. Reason: ' . $body['reason']);
+ }
+
+ return $isTokenValid;
+ }
+}
diff --git a/app/code/Magento/AdobeIms/Model/LogOut.php b/app/code/Magento/AdobeIms/Model/LogOut.php
index db90ccf88f0e8..16f410d0c27d2 100644
--- a/app/code/Magento/AdobeIms/Model/LogOut.php
+++ b/app/code/Magento/AdobeIms/Model/LogOut.php
@@ -7,10 +7,13 @@
namespace Magento\AdobeIms\Model;
+use Magento\AdobeImsApi\Api\ConfigInterface;
use Magento\AdobeImsApi\Api\FlushUserTokensInterface;
use Magento\AdobeImsApi\Api\GetAccessTokenInterface;
+use Magento\AdobeImsApi\Api\GetProfileInterface;
use Magento\AdobeImsApi\Api\LogOutInterface;
-use Magento\AdobeImsApi\Api\ConfigInterface;
+use Magento\Backend\Model\Auth;
+use Magento\Framework\Exception\AuthorizationException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\HTTP\Client\CurlFactory;
use Psr\Log\LoggerInterface;
@@ -20,6 +23,11 @@
*/
class LogOut implements LogOutInterface
{
+ /**
+ * Successful result code.
+ */
+ private const HTTP_OK = 200;
+
/**
* Successful result code.
*/
@@ -50,39 +58,60 @@ class LogOut implements LogOutInterface
*/
private $flushUserTokens;
+ /**
+ * @var GetProfileInterface
+ */
+ private GetProfileInterface $profile;
+
+ /**
+ * @var Auth
+ */
+ private Auth $auth;
+
/**
* @param LoggerInterface $logger
* @param ConfigInterface $config
* @param CurlFactory $curlFactory
* @param GetAccessTokenInterface $getAccessToken
* @param FlushUserTokensInterface $flushUserTokens
+ * @param GetProfileInterface $profile
+ * @param Auth $auth
*/
public function __construct(
LoggerInterface $logger,
ConfigInterface $config,
CurlFactory $curlFactory,
GetAccessTokenInterface $getAccessToken,
- FlushUserTokensInterface $flushUserTokens
+ FlushUserTokensInterface $flushUserTokens,
+ GetProfileInterface $profile,
+ Auth $auth
) {
$this->logger = $logger;
$this->config = $config;
$this->curlFactory = $curlFactory;
$this->getAccessToken = $getAccessToken;
$this->flushUserTokens = $flushUserTokens;
+ $this->profile = $profile;
+ $this->auth = $auth;
}
/**
* @inheritDoc
*/
- public function execute() : bool
+ public function execute(?string $accessToken = null) : bool
{
try {
- $accessToken = $this->getAccessToken->execute();
-
+ if ($accessToken === null) {
+ $session = $this->auth->getAuthStorage();
+ $accessToken = $session->getAdobeAccessToken();
+ }
+ if (!empty($accessToken)) {
+ return $this->logoutAdminFromIms($accessToken);
+ }
+ $accessToken = $accessToken ?? $this->getAccessToken->execute();
if (empty($accessToken)) {
return true;
}
-
$this->externalLogOut($accessToken);
$this->flushUserTokens->execute();
return true;
@@ -111,4 +140,56 @@ private function externalLogOut(string $accessToken): void
);
}
}
+
+ /**
+ * Logout admin from Adobe IMS
+ *
+ * @param string $accessToken
+ * @return bool
+ * @throws LocalizedException
+ */
+ private function logoutAdminFromIms(string $accessToken): bool
+ {
+ if (!$this->checkUserProfile($accessToken)) {
+ throw new LocalizedException(
+ __('An error occurred during logout operation.')
+ );
+ }
+ $curl = $this->curlFactory->create();
+
+ $curl->addHeader('Content-Type', 'application/x-www-form-urlencoded');
+ $curl->addHeader('cache-control', 'no-cache');
+
+ $curl->post(
+ $this->config->getBackendLogoutUrl($accessToken),
+ []
+ );
+
+ if ($curl->getStatus() !== self::HTTP_OK || ($this->checkUserProfile($accessToken))) {
+ throw new LocalizedException(
+ __('An error occurred during logout operation.')
+ );
+ }
+ return true;
+ }
+
+ /**
+ * Check whether user profile could be retrieved by the access token
+ * - If the token is invalidated, profile information won't be returned
+ *
+ * @param string $accessToken
+ * @return bool
+ */
+ private function checkUserProfile(string $accessToken): bool
+ {
+ try {
+ $profile = $this->profile->getProfile($accessToken);
+ if (!empty($profile['email'])) {
+ return true;
+ }
+ } catch (AuthorizationException $exception) {
+ return false;
+ }
+ return false;
+ }
}
diff --git a/app/code/Magento/AdobeIms/Model/OAuth/TokenResponse.php b/app/code/Magento/AdobeIms/Model/OAuth/TokenResponse.php
index 62cc0ee659ce4..d70eef19d2b6a 100644
--- a/app/code/Magento/AdobeIms/Model/OAuth/TokenResponse.php
+++ b/app/code/Magento/AdobeIms/Model/OAuth/TokenResponse.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdobeIms\Model\OAuth;
diff --git a/app/code/Magento/AdminAdobeIms/Service/ImsOrganizationService.php b/app/code/Magento/AdobeIms/Model/OrganizationMembership.php
similarity index 71%
rename from app/code/Magento/AdminAdobeIms/Service/ImsOrganizationService.php
rename to app/code/Magento/AdobeIms/Model/OrganizationMembership.php
index c562ce8a482a6..56822a2284e1b 100644
--- a/app/code/Magento/AdminAdobeIms/Service/ImsOrganizationService.php
+++ b/app/code/Magento/AdobeIms/Model/OrganizationMembership.php
@@ -3,20 +3,24 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
-namespace Magento\AdminAdobeIms\Service;
+namespace Magento\AdobeIms\Model;
-use Magento\AdminAdobeIms\Exception\AdobeImsOrganizationAuthorizationException;
+use Magento\AdobeIms\Exception\AdobeImsOrganizationAuthorizationException;
+use Magento\AdobeImsApi\Api\ConfigInterface;
+use Magento\AdobeImsApi\Api\OrganizationMembershipInterface;
use Magento\Framework\HTTP\Client\CurlFactory;
-class ImsOrganizationService
+/**
+ * Check if user is a member of Adobe Organization
+ */
+class OrganizationMembership implements OrganizationMembershipInterface
{
/**
- * @var ImsConfig
+ * @var ConfigInterface
*/
- private ImsConfig $adminImsConfig;
+ private ConfigInterface $imsConfig;
/**
* @var CurlFactory
@@ -24,27 +28,23 @@ class ImsOrganizationService
private CurlFactory $curlFactory;
/**
- * @param ImsConfig $adminImsConfig
+ * @param ConfigInterface $imsConfig
* @param CurlFactory $curlFactory
*/
public function __construct(
- ImsConfig $adminImsConfig,
+ ConfigInterface $imsConfig,
CurlFactory $curlFactory
) {
- $this->adminImsConfig = $adminImsConfig;
+ $this->imsConfig = $imsConfig;
$this->curlFactory = $curlFactory;
}
/**
- * Check if user is a member of Adobe Organization
- *
- * @param string $access_token
- * @return void
- * @throws AdobeImsOrganizationAuthorizationException
+ * @inheritDoc
*/
public function checkOrganizationMembership(string $access_token): void
{
- $configuredOrganizationId = $this->adminImsConfig->getOrganizationId();
+ $configuredOrganizationId = $this->imsConfig->getOrganizationId();
if ($configuredOrganizationId === '' || !$access_token) {
throw new AdobeImsOrganizationAuthorizationException(
@@ -59,9 +59,8 @@ public function checkOrganizationMembership(string $access_token): void
$curl->addHeader('cache-control', 'no-cache');
$curl->addHeader('Authorization', 'Bearer ' . $access_token);
- $orgCheckUrl = $this->adminImsConfig->getOrganizationMembershipUrl($configuredOrganizationId);
+ $orgCheckUrl = $this->imsConfig->getOrganizationMembershipUrl($configuredOrganizationId);
$curl->get($orgCheckUrl);
-
if ($curl->getBody() === '') {
throw new AdobeImsOrganizationAuthorizationException(
__('Could not check Organization Membership. Response is empty.')
@@ -69,7 +68,6 @@ public function checkOrganizationMembership(string $access_token): void
}
$response = $curl->getBody();
-
if ($response !== 'true') {
throw new AdobeImsOrganizationAuthorizationException(
__('User is not a member of configured Adobe Organization.')
diff --git a/app/code/Magento/AdobeIms/Model/ResourceModel/UserProfile.php b/app/code/Magento/AdobeIms/Model/ResourceModel/UserProfile.php
index 4c6edabf3d083..705f52fc5e2b5 100644
--- a/app/code/Magento/AdobeIms/Model/ResourceModel/UserProfile.php
+++ b/app/code/Magento/AdobeIms/Model/ResourceModel/UserProfile.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdobeIms\Model\ResourceModel;
diff --git a/app/code/Magento/AdobeIms/Model/ResourceModel/UserProfile/Collection.php b/app/code/Magento/AdobeIms/Model/ResourceModel/UserProfile/Collection.php
index 9d401575a1842..a62617529ffb8 100644
--- a/app/code/Magento/AdobeIms/Model/ResourceModel/UserProfile/Collection.php
+++ b/app/code/Magento/AdobeIms/Model/ResourceModel/UserProfile/Collection.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdobeIms\Model\ResourceModel\UserProfile;
diff --git a/app/code/Magento/AdminAdobeIms/Model/TokenReader.php b/app/code/Magento/AdobeIms/Model/TokenReader.php
similarity index 92%
rename from app/code/Magento/AdminAdobeIms/Model/TokenReader.php
rename to app/code/Magento/AdobeIms/Model/TokenReader.php
index 62a725fdf7c9a..68347d5afb202 100644
--- a/app/code/Magento/AdminAdobeIms/Model/TokenReader.php
+++ b/app/code/Magento/AdobeIms/Model/TokenReader.php
@@ -5,26 +5,26 @@
*/
declare(strict_types=1);
-namespace Magento\AdminAdobeIms\Model;
+namespace Magento\AdobeIms\Model;
-use Magento\AdminAdobeIms\Api\TokenReaderInterface;
-use Magento\AdminAdobeIms\Service\ImsConfig;
+use Magento\AdobeImsApi\Api\ConfigInterface;
+use Magento\AdobeImsApi\Api\TokenReaderInterface;
use Magento\Framework\App\CacheInterface;
use Magento\Framework\Exception\AuthenticationException;
use Magento\Framework\Exception\AuthorizationException;
use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Exception\InvalidArgumentException;
use Magento\Framework\Filesystem\Driver\File;
+use Magento\Framework\Jwt\Exception\JwtException;
use Magento\Framework\Jwt\Jwk;
use Magento\Framework\Jwt\JwkFactory;
use Magento\Framework\Jwt\Jws\JwsSignatureJwks;
use Magento\Framework\Jwt\JwtManagerInterface;
-use Magento\Framework\Jwt\Exception\JwtException;
use Magento\Framework\Jwt\Payload\ClaimsPayloadInterface;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Framework\Stdlib\DateTime\DateTime;
-use Psr\Log\LoggerInterface;
use Magento\Integration\Helper\Oauth\Data as OauthHelper;
+use Psr\Log\LoggerInterface;
/**
* Adobe Ims Token Reader
@@ -57,9 +57,9 @@ class TokenReader implements TokenReaderInterface
private CacheInterface $cache;
/**
- * @var ImsConfig
+ * @var ConfigInterface
*/
- private ImsConfig $adminImsConfig;
+ private ConfigInterface $imsConfig;
/**
* @var JwkFactory
@@ -94,7 +94,7 @@ class TokenReader implements TokenReaderInterface
/**
* @param JwtManagerInterface $jwtManager
* @param CacheInterface $cache
- * @param ImsConfig $adminImsConfig
+ * @param ConfigInterface $imsConfig
* @param JwkFactory $jwkFactory
* @param LoggerInterface $logger
* @param DateTime $dateTime
@@ -105,7 +105,7 @@ class TokenReader implements TokenReaderInterface
public function __construct(
JwtManagerInterface $jwtManager,
CacheInterface $cache,
- ImsConfig $adminImsConfig,
+ ConfigInterface $imsConfig,
JwkFactory $jwkFactory,
LoggerInterface $logger,
DateTime $dateTime,
@@ -115,7 +115,7 @@ public function __construct(
) {
$this->jwtManager = $jwtManager;
$this->cache = $cache;
- $this->adminImsConfig = $adminImsConfig;
+ $this->imsConfig = $imsConfig;
$this->jwkFactory = $jwkFactory;
$this->logger = $logger;
$this->dateTime = $dateTime;
@@ -182,9 +182,9 @@ private function getJWK(string $token)
[$header] = explode(".", (string)$token);
$decodedAdobeImsHeader = $this->json->unserialize(
- // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
base64_decode($header)
- // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
+ // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
);
if (!isset($decodedAdobeImsHeader[self::HEADER_ATTRIBUTE_X5U])) {
@@ -195,7 +195,7 @@ private function getJWK(string $token)
$this->setCertificateCacheId($certificateFileName);
if (!$certificateValue = $this->loadCertificateFromCache()) {
- $certificateUrl = $this->adminImsConfig->getCertificateUrl($certificateFileName);
+ $certificateUrl = $this->imsConfig->getCertificateUrl($certificateFileName);
try {
$certificateValue = $this->driver->fileGetContents($certificateUrl);
} catch (FileSystemException $exception) {
diff --git a/app/code/Magento/AdobeIms/Model/UserAuthorized.php b/app/code/Magento/AdobeIms/Model/UserAuthorized.php
index 09070f69c1ed8..48eb8a29a69a8 100644
--- a/app/code/Magento/AdobeIms/Model/UserAuthorized.php
+++ b/app/code/Magento/AdobeIms/Model/UserAuthorized.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdobeIms\Model;
diff --git a/app/code/Magento/AdobeIms/Model/UserProfileRepository.php b/app/code/Magento/AdobeIms/Model/UserProfileRepository.php
index b22f9a96c4b3b..6cf84602fb385 100644
--- a/app/code/Magento/AdobeIms/Model/UserProfileRepository.php
+++ b/app/code/Magento/AdobeIms/Model/UserProfileRepository.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdobeIms\Model;
diff --git a/app/code/Magento/AdobeIms/Test/Integration/DbSchemaTest.php b/app/code/Magento/AdobeIms/Test/Integration/DbSchemaTest.php
index 0317af4f665a2..987d04c9f306b 100644
--- a/app/code/Magento/AdobeIms/Test/Integration/DbSchemaTest.php
+++ b/app/code/Magento/AdobeIms/Test/Integration/DbSchemaTest.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdobeIms\Test\Integration;
diff --git a/app/code/Magento/AdobeIms/Test/Integration/Model/ConfigTest.php b/app/code/Magento/AdobeIms/Test/Integration/Model/ConfigTest.php
index a67773a174a1f..b7d07b33f3267 100644
--- a/app/code/Magento/AdobeIms/Test/Integration/Model/ConfigTest.php
+++ b/app/code/Magento/AdobeIms/Test/Integration/Model/ConfigTest.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdobeIms\Test\Integration\Model;
@@ -17,7 +16,7 @@
*/
class ConfigTest extends TestCase
{
- private const SCOPES = ['openid', 'creative_sdk', 'email', 'profile'];
+ private const SCOPES = ['creative_sdk', 'openid', 'email', 'profile'];
private const LOCALE = 'en_US';
private const REDIRECT_URL_PATTERN = '/redirect_uri=[a-zA-Z0-9\/:._]*\/adobe_ims\/oauth\/callback/';
diff --git a/app/code/Magento/AdobeIms/Test/Unit/Controller/Adminhtml/User/LogoutTest.php b/app/code/Magento/AdobeIms/Test/Unit/Controller/Adminhtml/User/LogoutTest.php
index 256850897a3ee..776da1fb4e593 100644
--- a/app/code/Magento/AdobeIms/Test/Unit/Controller/Adminhtml/User/LogoutTest.php
+++ b/app/code/Magento/AdobeIms/Test/Unit/Controller/Adminhtml/User/LogoutTest.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdobeIms\Test\Unit\Controller\Adminhtml\User;
diff --git a/app/code/Magento/AdobeIms/Test/Unit/Controller/Adminhtml/User/ProfileTest.php b/app/code/Magento/AdobeIms/Test/Unit/Controller/Adminhtml/User/ProfileTest.php
index 520c14c149b8d..d15dd8e3ed233 100644
--- a/app/code/Magento/AdobeIms/Test/Unit/Controller/Adminhtml/User/ProfileTest.php
+++ b/app/code/Magento/AdobeIms/Test/Unit/Controller/Adminhtml/User/ProfileTest.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdobeIms\Test\Unit\Controller\Adminhtml\User;
diff --git a/app/code/Magento/AdobeIms/Test/Unit/Model/AuthorizationTest.php b/app/code/Magento/AdobeIms/Test/Unit/Model/AuthorizationTest.php
new file mode 100644
index 0000000000000..704d791f1bc0f
--- /dev/null
+++ b/app/code/Magento/AdobeIms/Test/Unit/Model/AuthorizationTest.php
@@ -0,0 +1,157 @@
+createMock(ConfigInterface::class);
+ $imsConfigMock
+ ->method('getAuthUrl')
+ ->willReturn(self::AUTH_URL);
+ $this->curlFactory = $this->createMock(CurlFactory::class);
+ $this->parametersMock = $this->createMock(Parameters::class);
+ $this->uriMock = $this->createMock(Uri::class);
+ $urlParts = [];
+ $url = self::AUTH_URL;
+ $this->uriMock->expects($this->any())
+ ->method('parse')
+ ->willReturnCallback(
+ function ($url) use (&$urlParts) {
+ $urlParts = parse_url($url);
+ }
+ );
+ $this->uriMock->expects($this->any())
+ ->method('getHost')
+ ->willReturnCallback(
+ function () use (&$urlParts) {
+ return array_key_exists('host', $urlParts) ? $urlParts['host'] : '';
+ }
+ );
+ $this->uriMock->expects($this->any())
+ ->method('getQuery')
+ ->willReturnCallback(
+ function () {
+ return 'callback=' . self::REDIRECT_URL;
+ }
+ );
+ $this->parametersMock->method('fromString')
+ ->with('callback=' . self::REDIRECT_URL)
+ ->willReturnSelf();
+ $this->parametersMock->method('toArray')
+ ->willReturn([
+ 'redirect_uri' => self::REDIRECT_URL
+ ]);
+ $this->authorizationUrl = $objectManagerHelper->getObject(
+ Authorization::class,
+ [
+ 'curlFactory' => $this->curlFactory,
+ 'imsConfig' => $imsConfigMock,
+ 'parameters' => $this->parametersMock,
+ 'uri' => $this->uriMock
+ ]
+ );
+ }
+
+ /**
+ * Test IMS host belongs to correct project
+ */
+ public function testAuthUrlValidateImsHostBelongsToCorrectProject(): void
+ {
+ $curlMock = $this->createMock(Curl::class);
+ $curlMock->method('getHeaders')
+ ->willReturn(['location' => self::AUTH_URL]);
+ $curlMock->method('getStatus')
+ ->willReturn(302);
+
+ $this->curlFactory->method('create')
+ ->willReturn($curlMock);
+
+ $this->assertEquals($this->authorizationUrl->getAuthUrl(), self::AUTH_URL);
+ }
+
+ /**
+ * Test auth throws exception code when response code is 200
+ */
+ public function testAuthThrowsExceptionWhenResponseCodeIs200(): void
+ {
+ $curlMock = $this->createMock(Curl::class);
+ $curlMock->method('getHeaders')
+ ->willReturn(['location' => self::AUTH_URL]);
+ $curlMock->method('getStatus')
+ ->willReturn(200);
+
+ $this->curlFactory->method('create')
+ ->willReturn($curlMock);
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Could not get a valid response from Adobe IMS Service.');
+ $this->authorizationUrl->getAuthUrl();
+ }
+
+ /**
+ * Test auth throws exception code when response contains error
+ */
+ public function testAuthThrowsExceptionWhenResponseContainsError(): void
+ {
+ $curlMock = $this->createMock(Curl::class);
+ $curlMock->method('getHeaders')
+ ->willReturn(['location' => self::AUTH_URL_ERROR]);
+ $curlMock->method('getStatus')
+ ->willReturn(302);
+
+ $this->curlFactory->method('create')
+ ->willReturn($curlMock);
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Could not connect to Adobe IMS Service: invalid_scope.');
+ $this->authorizationUrl->getAuthUrl();
+ }
+}
diff --git a/app/code/Magento/AdobeIms/Test/Unit/Model/ConfigTest.php b/app/code/Magento/AdobeIms/Test/Unit/Model/ConfigTest.php
index 821b77c57246c..348316986b209 100644
--- a/app/code/Magento/AdobeIms/Test/Unit/Model/ConfigTest.php
+++ b/app/code/Magento/AdobeIms/Test/Unit/Model/ConfigTest.php
@@ -19,7 +19,7 @@
*/
class ConfigTest extends TestCase
{
-
+ private const XML_CONFIG_PATH = 'adobe_ims/integration/';
/**
* API key constants
*/
@@ -43,6 +43,7 @@ class ConfigTest extends TestCase
*/
private const LOCALE_CODE = 'en_US';
private const XML_PATH_AUTH_URL_PATTERN = 'adobe_ims/integration/auth_url_pattern';
+ private const AUTH_URL = 'https://auth-url.com/pattern';
private const AUTH_URL_PATTERN = 'https://auth-url.com/pattern' .
'?client_id=#{client_id}&redirect_uri=#{redirect_uri}&locale=#{locale}';
@@ -55,6 +56,7 @@ class ConfigTest extends TestCase
* Logout URL constants
*/
private const XML_PATH_LOGOUT_URL_PATTERN = 'adobe_ims/integration/logout_url';
+ private const LOGOUT_URL = 'https://logout-url.com/pattern';
private const LOGOUT_URL_PATTERN = 'https://logout-url.com/pattern' .
'?access_token=#{access_token}&redirect_uri=#{redirect_uri}';
private const REDIRECT_URI = 'REDIRECT_URI';
@@ -65,6 +67,7 @@ class ConfigTest extends TestCase
*/
private const XML_PATH_IMAGE_URL_PATTERN = 'adobe_ims/integration/image_url';
private const IMAGE_URL_PATTERN = 'https://image-url.com/pattern?api_key=#{api_key}';
+ private const IMAGE_URL = 'https://image-url.com/pattern';
/**
* Default profile image URL constants
@@ -72,6 +75,8 @@ class ConfigTest extends TestCase
private const XML_PATH_DEFAULT_PROFILE_IMAGE = 'adobe_ims/integration/default_profile_image';
private const IMAGE_URL_DEFAULT = 'https://image-url.com/default';
+ private const XML_PATH_ADOBE_IMS_SCOPES = 'adobe_ims/integration/scopes';
+
/**
* @var Config
*/
@@ -128,8 +133,16 @@ public function testGetPrivateKey(): void
public function testGetTokenUrl(): void
{
$this->scopeConfigMock->method('getValue')
- ->with(self::XML_PATH_TOKEN_URL)
- ->willReturn(self::TOKEN_URL);
+ ->willReturnMap([
+ [
+ self::XML_PATH_TOKEN_URL, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null,
+ self::TOKEN_URL
+ ],
+ [
+ self::XML_CONFIG_PATH . 'imsUrl', ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null,
+ self::TOKEN_URL
+ ],
+ ]);
$this->assertEquals(self::TOKEN_URL, $this->config->getTokenUrl());
}
@@ -141,10 +154,18 @@ public function testGetAuthUrl(): void
{
$this->scopeConfigMock->method('getValue')
->willReturnMap([
+ [
+ self::XML_CONFIG_PATH . 'imsUrl', ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null,
+ self::AUTH_URL
+ ],
[
self::XML_PATH_API_KEY, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null,
self::API_KEY
],
+ [
+ self::XML_PATH_ADOBE_IMS_SCOPES , ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null,
+ ['openid']
+ ],
[
Custom::XML_PATH_GENERAL_LOCALE_CODE, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null,
self::LOCALE_CODE
@@ -183,8 +204,16 @@ public function testGetCallBackUrl(): void
public function testGetLogoutUrl(): void
{
$this->scopeConfigMock->method('getValue')
- ->with(self::XML_PATH_LOGOUT_URL_PATTERN)
- ->willReturn(self::LOGOUT_URL_PATTERN);
+ ->willReturnMap([
+ [
+ self::XML_PATH_LOGOUT_URL_PATTERN, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null,
+ self::LOGOUT_URL_PATTERN
+ ],
+ [
+ self::XML_CONFIG_PATH . 'imsUrl', ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null,
+ self::LOGOUT_URL
+ ],
+ ]);
$this->assertEquals(
'https://logout-url.com/pattern?access_token=' . self::ACCCESS_TOKEN .
@@ -200,6 +229,10 @@ public function testGetProfileImageUrl(): void
{
$this->scopeConfigMock->method('getValue')
->willReturnMap([
+ [
+ self::XML_CONFIG_PATH . 'imageUrl', ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null,
+ self::IMAGE_URL
+ ],
[
self::XML_PATH_IMAGE_URL_PATTERN, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null,
self::IMAGE_URL_PATTERN
diff --git a/app/code/Magento/AdobeIms/Test/Unit/Model/GetAccessTokenTest.php b/app/code/Magento/AdobeIms/Test/Unit/Model/GetAccessTokenTest.php
index 6a3639ebc6c5b..a6fd4d9655c3e 100644
--- a/app/code/Magento/AdobeIms/Test/Unit/Model/GetAccessTokenTest.php
+++ b/app/code/Magento/AdobeIms/Test/Unit/Model/GetAccessTokenTest.php
@@ -72,6 +72,13 @@ public function testExecute(?string $token): void
->willReturn($userProfileMock);
$userProfileMock->expects($this->once())->method('getAccessToken')->willReturn($token);
+ $decryptedToken = $token ?? '';
+
+ $this->encryptor->expects($this->once())
+ ->method('decrypt')
+ ->with($token)
+ ->willReturn($decryptedToken);
+
$this->assertEquals($token, $this->getAccessToken->execute());
}
diff --git a/app/code/Magento/AdminAdobeIms/Test/Unit/Service/ImsOrganizationServiceTest.php b/app/code/Magento/AdobeIms/Test/Unit/Model/GetOrganizationsTest.php
similarity index 60%
rename from app/code/Magento/AdminAdobeIms/Test/Unit/Service/ImsOrganizationServiceTest.php
rename to app/code/Magento/AdobeIms/Test/Unit/Model/GetOrganizationsTest.php
index 167fcdb85d5e9..fefac94c05d39 100644
--- a/app/code/Magento/AdminAdobeIms/Test/Unit/Service/ImsOrganizationServiceTest.php
+++ b/app/code/Magento/AdobeIms/Test/Unit/Model/GetOrganizationsTest.php
@@ -3,53 +3,52 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
-namespace Magento\AdminAdobeIms\Test\Unit\Service;
+namespace Magento\AdobeIms\Test\Unit\Model;
-use Magento\AdminAdobeIms\Exception\AdobeImsOrganizationAuthorizationException;
-use Magento\AdminAdobeIms\Service\ImsConfig;
-use Magento\AdminAdobeIms\Service\ImsOrganizationService;
+use Magento\AdobeImsApi\Api\ConfigInterface;
+use Magento\AdobeIms\Model\OrganizationMembership;
+use Magento\Framework\Exception\AuthorizationException;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
use PHPUnit\Framework\TestCase;
-class ImsOrganizationServiceTest extends TestCase
+class GetOrganizationsTest extends TestCase
{
private const VALID_ORGANIZATION_ID = '12121212ABCD1211AA11ABCD';
private const INVALID_ORGANIZATION_ID = '12121212ABCD1211AA11XXXX';
/**
- * @var ImsOrganizationService
+ * @var OrganizationMembership
*/
private $imsOrganizationService;
/**
- * @var ImsConfig
+ * @var ConfigInterface
*/
- private $adminImsConfigMock;
+ private $imsConfigMock;
protected function setUp(): void
{
$objectManagerHelper = new ObjectManagerHelper($this);
- $this->adminImsConfigMock = $this->createMock(ImsConfig::class);
+ $this->imsConfigMock = $this->createMock(ConfigInterface::class);
$this->imsOrganizationService = $objectManagerHelper->getObject(
- ImsOrganizationService::class,
+ OrganizationMembership::class,
[
- 'adminImsConfig' => $this->adminImsConfigMock
+ 'imsConfig' => $this->imsConfigMock
]
);
}
public function testCheckOrganizationMembershipThrowsExceptionWhenProfileNotAssignedToOrg()
{
- $this->adminImsConfigMock
+ $this->imsConfigMock
->method('getOrganizationId')
->willReturn('');
- $this->expectException(AdobeImsOrganizationAuthorizationException::class);
+ $this->expectException(AuthorizationException::class);
$this->expectExceptionMessage('Can\'t check user membership in organization.');
$this->imsOrganizationService->checkOrganizationMembership('my_token');
diff --git a/app/code/Magento/AdobeIms/Test/Unit/Model/GetProfileTest.php b/app/code/Magento/AdobeIms/Test/Unit/Model/GetProfileTest.php
new file mode 100644
index 0000000000000..8483e20437594
--- /dev/null
+++ b/app/code/Magento/AdobeIms/Test/Unit/Model/GetProfileTest.php
@@ -0,0 +1,75 @@
+configMock = $this->createMock(ConfigInterface::class);
+ $this->curlFactoryMock = $this->createMock(CurlFactory::class);
+ $this->jsonMock = $this->createMock(Json::class);
+ $this->profile = new GetProfile(
+ $this->configMock,
+ $this->curlFactoryMock,
+ $this->jsonMock
+ );
+ }
+
+ /**
+ * Test validate token
+ */
+ public function testGetProfile()
+ {
+ $curl = $this->createMock(Curl::class);
+ $this->curlFactoryMock->expects($this->once())
+ ->method('create')
+ ->willReturn($curl);
+ $curl->expects($this->exactly(3))
+ ->method('addHeader')
+ ->willReturn(null);
+ $this->configMock->expects($this->once())
+ ->method('getProfileUrl')
+ ->willReturn('http://www.some.url.com');
+ $curl->expects($this->exactly(2))
+ ->method('getBody')
+ ->willReturn(null);
+ $data = ['email' => 'test@email.com', 'name' => 'Name'];
+ $this->jsonMock->expects($this->once())
+ ->method('unserialize')
+ ->willReturn($data);
+ $this->assertEquals($data, $this->profile->getProfile('ftXdatRdsafga'));
+ }
+}
diff --git a/app/code/Magento/AdobeIms/Test/Unit/Model/IsTokenValidTest.php b/app/code/Magento/AdobeIms/Test/Unit/Model/IsTokenValidTest.php
new file mode 100644
index 0000000000000..6bede456f5bd1
--- /dev/null
+++ b/app/code/Magento/AdobeIms/Test/Unit/Model/IsTokenValidTest.php
@@ -0,0 +1,86 @@
+configMock = $this->createMock(ConfigInterface::class);
+ $this->curlFactoryMock = $this->createMock(CurlFactory::class);
+ $this->jsonMock = $this->createMock(Json::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->isValidToken = new IsTokenValid(
+ $this->curlFactoryMock,
+ $this->configMock,
+ $this->jsonMock,
+ $this->logger
+ );
+ }
+
+ /**
+ * Test validate token
+ */
+ public function testValidateToken()
+ {
+ $curl = $this->createMock(Curl::class);
+ $this->curlFactoryMock->expects($this->once())
+ ->method('create')
+ ->willReturn($curl);
+ $curl->expects($this->exactly(2))
+ ->method('addHeader')
+ ->willReturn(null);
+ $this->configMock->expects($this->once())
+ ->method('getValidateTokenUrl')
+ ->willReturn('http://www.some.url.com');
+ $curl->expects($this->once())
+ ->method('post')
+ ->willReturn(null);
+ $curl->expects($this->exactly(2))
+ ->method('getBody')
+ ->willReturn(null);
+ $data = ['valid' => 1];
+ $this->jsonMock->expects($this->once())
+ ->method('unserialize')
+ ->willReturn($data);
+ $this->assertTrue($this->isValidToken->validateToken('ftXdatRdsafga'));
+ }
+}
diff --git a/app/code/Magento/AdobeIms/Test/Unit/Model/LogOutTest.php b/app/code/Magento/AdobeIms/Test/Unit/Model/LogOutTest.php
index fa6c7c1bcb9f1..6435893a4566e 100644
--- a/app/code/Magento/AdobeIms/Test/Unit/Model/LogOutTest.php
+++ b/app/code/Magento/AdobeIms/Test/Unit/Model/LogOutTest.php
@@ -3,19 +3,18 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdobeIms\Test\Unit\Model;
use Exception;
+use Magento\AdobeIms\Model\GetProfile;
use Magento\AdobeIms\Model\LogOut;
+use Magento\Backend\Model\Auth\StorageInterface;
use Magento\AdobeImsApi\Api\ConfigInterface;
-use Magento\AdobeImsApi\Api\Data\UserProfileInterface;
use Magento\AdobeImsApi\Api\FlushUserTokensInterface;
use Magento\AdobeImsApi\Api\GetAccessTokenInterface;
-use Magento\AdobeImsApi\Api\UserProfileRepositoryInterface;
-use Magento\Authorization\Model\UserContextInterface;
+use Magento\Backend\Model\Auth;
use Magento\Framework\HTTP\Client\Curl;
use Magento\Framework\HTTP\Client\CurlFactory;
use PHPUnit\Framework\MockObject\MockObject;
@@ -60,6 +59,16 @@ class LogOutTest extends TestCase
*/
private $model;
+ /**
+ * @var Auth|MockObject
+ */
+ private $auth;
+
+ /**
+ * @var GetProfile|MockObject
+ */
+ private $profile;
+
/**
* @inheritdoc
*/
@@ -70,12 +79,16 @@ protected function setUp(): void
$this->loggerInterfaceMock = $this->createMock(LoggerInterface::class);
$this->getToken = $this->createMock(GetAccessTokenInterface::class);
$this->flushTokens = $this->createMock(FlushUserTokensInterface::class);
+ $this->profile = $this->createMock(GetProfile::class);
+ $this->auth = $this->createMock(Auth::class);
$this->model = new LogOut(
$this->loggerInterfaceMock,
$this->configInterfaceMock,
$this->curlFactoryMock,
$this->getToken,
- $this->flushTokens
+ $this->flushTokens,
+ $this->profile,
+ $this->auth
);
}
@@ -104,7 +117,15 @@ public function testExecute(): void
$this->flushTokens->expects($this->once())
->method('execute');
-
+ $session = $this->getMockBuilder(StorageInterface::class)
+ ->addMethods(['getAdobeAccessToken'])
+ ->getMockForAbstractClass();
+ $session->expects($this->once())
+ ->method('getAdobeAccessToken')
+ ->willReturn(null);
+ $this->auth->expects($this->once())
+ ->method('getAuthStorage')
+ ->willReturn($session);
$this->assertEquals(true, $this->model->execute());
}
@@ -135,7 +156,15 @@ public function testExecuteWithError(): void
$this->flushTokens->expects($this->never())
->method('execute');
-
+ $session = $this->getMockBuilder(StorageInterface::class)
+ ->addMethods(['getAdobeAccessToken'])
+ ->getMockForAbstractClass();
+ $session->expects($this->once())
+ ->method('getAdobeAccessToken')
+ ->willReturn(null);
+ $this->auth->expects($this->once())
+ ->method('getAuthStorage')
+ ->willReturn($session);
$this->assertEquals(false, $this->model->execute());
}
@@ -161,7 +190,15 @@ public function testExecuteWithException(): void
$curl->expects($this->once())
->method('getStatus')
->willReturn(self::HTTP_FOUND);
-
+ $session = $this->getMockBuilder(StorageInterface::class)
+ ->addMethods(['getAdobeAccessToken'])
+ ->getMockForAbstractClass();
+ $session->expects($this->once())
+ ->method('getAdobeAccessToken')
+ ->willReturn(null);
+ $this->auth->expects($this->once())
+ ->method('getAuthStorage')
+ ->willReturn($session);
$this->flushTokens->expects($this->once())
->method('execute')
->willThrowException(new Exception('Could not save user profile.'));
diff --git a/app/code/Magento/AdobeIms/Test/Unit/Model/LoginTest.php b/app/code/Magento/AdobeIms/Test/Unit/Model/LoginTest.php
index dcef273bac179..14e2c07bfc407 100644
--- a/app/code/Magento/AdobeIms/Test/Unit/Model/LoginTest.php
+++ b/app/code/Magento/AdobeIms/Test/Unit/Model/LoginTest.php
@@ -8,14 +8,11 @@
namespace Magento\AdobeIms\Test\Unit\Model;
use Magento\AdobeIms\Model\LogIn;
-use Magento\AdobeIms\Model\OAuth\TokenResponse;
-use Magento\AdobeImsApi\Api\Data\TokenResponseInterfaceFactory;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ScopeResolverInterface;
use Magento\Framework\Locale\ResolverInterface;
use Magento\Framework\Stdlib\DateTime\Intl\DateFormatterFactory;
use Magento\Framework\Stdlib\DateTime\Timezone;
-use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use PHPUnit\Framework\TestCase;
use Magento\AdobeImsApi\Api\Data\TokenResponseInterface;
use Magento\AdobeImsApi\Api\Data\UserProfileInterface;
diff --git a/app/code/Magento/AdobeIms/Test/Unit/Model/UserProfileTest.php b/app/code/Magento/AdobeIms/Test/Unit/Model/UserProfileTest.php
index 39d71de69606f..326f727e7a2de 100644
--- a/app/code/Magento/AdobeIms/Test/Unit/Model/UserProfileTest.php
+++ b/app/code/Magento/AdobeIms/Test/Unit/Model/UserProfileTest.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\AdobeIms\Test\Unit\Model;
diff --git a/app/code/Magento/AdobeIms/composer.json b/app/code/Magento/AdobeIms/composer.json
index 872c29ffc97b4..9a3d8f27a87d2 100644
--- a/app/code/Magento/AdobeIms/composer.json
+++ b/app/code/Magento/AdobeIms/composer.json
@@ -2,13 +2,14 @@
"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": "*",
"magento/module-backend": "*",
"magento/module-config": "*",
- "magento/module-user": "*"
+ "magento/module-user": "*",
+ "magento/module-integration": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/AdobeIms/etc/config.xml b/app/code/Magento/AdobeIms/etc/config.xml
index b6cbbb24e351a..bec85f0c7f979 100644
--- a/app/code/Magento/AdobeIms/etc/config.xml
+++ b/app/code/Magento/AdobeIms/etc/config.xml
@@ -11,10 +11,19 @@
- https://ims-na1.adobelogin.com/ims/token
-
-
-
+ #{imsUrl}/ims/token
+
+
+
+ https://ims-na1.adobelogin.com
+ https://cc-api-behance.adobe.io
+
+ creative_sdk
+ openid
+ email
+ profile
+
+ 0
diff --git a/app/code/Magento/AdobeIms/etc/di.xml b/app/code/Magento/AdobeIms/etc/di.xml
index 5d6f2accaea3d..97d4cf75aa0a3 100644
--- a/app/code/Magento/AdobeIms/etc/di.xml
+++ b/app/code/Magento/AdobeIms/etc/di.xml
@@ -17,4 +17,9 @@
+
+
+
+
+
diff --git a/app/code/Magento/AdobeImsApi/Api/AuthorizationInterface.php b/app/code/Magento/AdobeImsApi/Api/AuthorizationInterface.php
new file mode 100644
index 0000000000000..cd851950eca7a
--- /dev/null
+++ b/app/code/Magento/AdobeImsApi/Api/AuthorizationInterface.php
@@ -0,0 +1,34 @@
+
* @SuppressWarnings(PHPMD.TooManyFields)
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class AdvancedPricing extends \Magento\CatalogImportExport\Model\Export\Product
{
- const ENTITY_ADVANCED_PRICING = 'advanced_pricing';
+ public const ENTITY_ADVANCED_PRICING = 'advanced_pricing';
/**
* @var \Magento\CatalogImportExport\Model\Import\Product\StoreResolver
@@ -568,10 +567,10 @@ protected function getTierPrices(array $listSku, $table)
if (isset($price[0]) && !empty($price[0]) || isset($price[1]) && !empty($price[1])) {
$select->orWhere('ap.percentage_value IS NOT NULL');
}
- if (isset($updatedAtFrom) && !empty($updatedAtFrom)) {
+ if (isset($updatedAtFrom)) {
$select->where('cpe.updated_at >= ?', $updatedAtFrom);
}
- if (isset($updatedAtTo) && !empty($updatedAtTo)) {
+ if (isset($updatedAtTo)) {
$select->where('cpe.updated_at <= ?', $updatedAtTo);
}
$exportData = $this->_connection->fetchAll($select);
diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php
index 30b2535853e35..49de9650f0d3c 100644
--- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php
+++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php
@@ -21,22 +21,22 @@
*/
class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
{
- const VALUE_ALL_GROUPS = 'ALL GROUPS';
- const VALUE_ALL_WEBSITES = 'All Websites';
- const COL_SKU = 'sku';
- const COL_TIER_PRICE_WEBSITE = 'tier_price_website';
- const COL_TIER_PRICE_CUSTOMER_GROUP = 'tier_price_customer_group';
- const COL_TIER_PRICE_QTY = 'tier_price_qty';
- const COL_TIER_PRICE = 'tier_price';
- const COL_TIER_PRICE_PERCENTAGE_VALUE = 'percentage_value';
- const COL_TIER_PRICE_TYPE = 'tier_price_value_type';
- const TIER_PRICE_TYPE_FIXED = 'Fixed';
- const TIER_PRICE_TYPE_PERCENT = 'Discount';
- const TABLE_TIER_PRICE = 'catalog_product_entity_tier_price';
- const DEFAULT_ALL_GROUPS_GROUPED_PRICE_VALUE = '0';
- const ENTITY_TYPE_CODE = 'advanced_pricing';
- const VALIDATOR_MAIN = 'validator';
- const VALIDATOR_WEBSITE = 'validator_website';
+ public const VALUE_ALL_GROUPS = 'ALL GROUPS';
+ public const VALUE_ALL_WEBSITES = 'All Websites';
+ public const COL_SKU = 'sku';
+ public const COL_TIER_PRICE_WEBSITE = 'tier_price_website';
+ public const COL_TIER_PRICE_CUSTOMER_GROUP = 'tier_price_customer_group';
+ public const COL_TIER_PRICE_QTY = 'tier_price_qty';
+ public const COL_TIER_PRICE = 'tier_price';
+ public const COL_TIER_PRICE_PERCENTAGE_VALUE = 'percentage_value';
+ public const COL_TIER_PRICE_TYPE = 'tier_price_value_type';
+ public const TIER_PRICE_TYPE_FIXED = 'Fixed';
+ public const TIER_PRICE_TYPE_PERCENT = 'Discount';
+ public const TABLE_TIER_PRICE = 'catalog_product_entity_tier_price';
+ public const DEFAULT_ALL_GROUPS_GROUPED_PRICE_VALUE = '0';
+ public const ENTITY_TYPE_CODE = 'advanced_pricing';
+ public const VALIDATOR_MAIN = 'validator';
+ public const VALIDATOR_WEBSITE = 'validator_website';
/**
* @deprecated
@@ -76,9 +76,7 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract
protected $needColumnCheck = true;
/**
- * Valid column names.
- *
- * @array
+ * @var array
*/
protected $validColumnNames = [
self::COL_SKU,
@@ -144,8 +142,6 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract
protected $_permanentAttributes = [self::COL_SKU];
/**
- * Catalog product entity
- *
* @var string
*/
protected $_catalogProductEntity;
@@ -156,8 +152,6 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract
protected $dateTime;
/**
- * Product entity link field
- *
* @var string
*/
private $productEntityLinkField;
@@ -272,7 +266,6 @@ public function getEntityTypeCode()
* @param array $rowData
* @param int $rowNum
* @return bool
- * @throws \Zend_Validate_Exception
*/
public function validateRow(array $rowData, $rowNum)
{
@@ -323,7 +316,6 @@ protected function _importData()
} elseif (\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND == $this->getBehavior()) {
$this->saveAdvancedPricing();
}
-
return true;
}
@@ -349,7 +341,7 @@ public function deleteAdvancedPricing()
{
$this->_cachedSkuToDelete = null;
$listSku = [];
- while ($bunch = $this->_dataSourceModel->getNextBunch()) {
+ while ($bunch = $this->_dataSourceModel->getNextUniqueBunch($this->getIds())) {
foreach ($bunch as $rowNum => $rowData) {
$this->validateRow($rowData, $rowNum);
if (!$this->getErrorAggregator()->isRowInvalid($rowNum)) {
@@ -396,7 +388,8 @@ protected function saveAndReplaceAdvancedPrices()
}
$listSku = [];
$tierPrices = [];
- while ($bunch = $this->_dataSourceModel->getNextBunch()) {
+ while ($bunch = $this->_dataSourceModel->getNextUniqueBunch($this->getIds())) {
+ $bunchTierPrices = [];
foreach ($bunch as $rowNum => $rowData) {
if (!$this->validateRow($rowData, $rowNum)) {
$this->addRowError(ValidatorInterface::ERROR_SKU_IS_EMPTY, $rowNum);
@@ -410,7 +403,7 @@ protected function saveAndReplaceAdvancedPrices()
$rowSku = $rowData[self::COL_SKU];
$listSku[] = $rowSku;
if (!empty($rowData[self::COL_TIER_PRICE_WEBSITE])) {
- $tierPrices[$rowSku][] = [
+ $tierPrice = [
'all_groups' => $rowData[self::COL_TIER_PRICE_CUSTOMER_GROUP] == self::VALUE_ALL_GROUPS,
'customer_group_id' => $this->getCustomerGroupId(
$rowData[self::COL_TIER_PRICE_CUSTOMER_GROUP]
@@ -422,21 +415,28 @@ protected function saveAndReplaceAdvancedPrices()
? $rowData[self::COL_TIER_PRICE] : null,
'website_id' => $this->getWebSiteId($rowData[self::COL_TIER_PRICE_WEBSITE])
];
+ if (\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND == $behavior) {
+ $bunchTierPrices[$rowSku][] = $tierPrice;
+ }
+ if (\Magento\ImportExport\Model\Import::BEHAVIOR_REPLACE == $behavior) {
+ $tierPrices[$rowSku][] = $tierPrice;
+ }
}
}
if (\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND == $behavior) {
- $this->processCountExistingPrices($tierPrices, self::TABLE_TIER_PRICE)
- ->processCountNewPrices($tierPrices);
+ $this->processCountExistingPrices($bunchTierPrices, self::TABLE_TIER_PRICE)
+ ->processCountNewPrices($bunchTierPrices);
- $this->saveProductPrices($tierPrices, self::TABLE_TIER_PRICE);
- if ($listSku) {
- $this->setUpdatedAt($listSku);
- }
+ $this->saveProductPrices($bunchTierPrices, self::TABLE_TIER_PRICE);
}
}
- if (\Magento\ImportExport\Model\Import::BEHAVIOR_REPLACE == $behavior) {
+ if (\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND == $behavior) {
+ if ($listSku) {
+ $this->setUpdatedAt($listSku);
+ }
+ } elseif (\Magento\ImportExport\Model\Import::BEHAVIOR_REPLACE == $behavior) {
if ($listSku) {
$this->processCountNewPrices($tierPrices);
if ($this->deleteProductTierPrices(array_unique($listSku), self::TABLE_TIER_PRICE)) {
@@ -445,6 +445,7 @@ protected function saveAndReplaceAdvancedPrices()
}
}
}
+ $this->finalizeCount();
return $this;
}
@@ -649,11 +650,18 @@ protected function processCountNewPrices(array $tierPrices)
foreach ($tierPrices as $productPrices) {
$this->countItemsCreated += count($productPrices);
}
- $this->countItemsCreated -= $this->countItemsUpdated;
return $this;
}
+ /**
+ * Finalize count of new and existing records
+ */
+ protected function finalizeCount()
+ {
+ $this->countItemsCreated -= $this->countItemsUpdated;
+ }
+
/**
* Get product entity link field
*
diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator.php
index d939a3f7c392e..172778a1ba256 100644
--- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator.php
+++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing/Validator.php
@@ -6,7 +6,7 @@
namespace Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing;
use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface;
-use \Magento\Framework\Validator\AbstractValidator;
+use Magento\Framework\Validator\AbstractValidator;
class Validator extends AbstractValidator implements RowValidatorInterface
{
@@ -28,7 +28,6 @@ public function __construct($validators = [])
*
* @param array $value
* @return bool
- * @throws \Zend_Validate_Exception
*/
public function isValid($value)
{
@@ -44,8 +43,7 @@ public function isValid($value)
}
/**
- * @param \Magento\CatalogImportExport\Model\Import\Product $context
- * @return $this
+ * @inheritdoc
*/
public function init($context)
{
diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php
index 9469807ca59b4..0f8c1d5c19e16 100644
--- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php
+++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php
@@ -39,8 +39,8 @@ class AdvancedPricingTest extends AbstractImportTestCase
/**
* DB Table data
*/
- const TABLE_NAME = 'tableName';
- const LINK_FIELD = 'linkField';
+ public const TABLE_NAME = 'tableName';
+ public const LINK_FIELD = 'linkField';
/**
* @var ResourceFactory|MockObject
@@ -328,7 +328,7 @@ public function testSaveAndReplaceAdvancedPricesAddRowErrorCall(): void
]
];
$this->dataSourceModel
- ->method('getNextBunch')
+ ->method('getNextUniqueBunch')
->willReturnOnConsecutiveCalls($testBunch);
$this->advancedPricing->expects($this->once())->method('validateRow')->willReturn(false);
$this->advancedPricing->method('saveProductPrices')->willReturnSelf();
@@ -400,7 +400,7 @@ public function testSaveAndReplaceAdvancedPricesAppendBehaviourDataAndCalls(
->method('getBehavior')
->willReturn(Import::BEHAVIOR_APPEND);
$this->dataSourceModel
- ->method('getNextBunch')
+ ->method('getNextUniqueBunch')
->willReturnOnConsecutiveCalls($data);
$advancedPricing->method('validateRow')->willReturn(true);
@@ -524,7 +524,7 @@ public function testSaveAndReplaceAdvancedPricesReplaceBehaviourInternalCalls():
Import::BEHAVIOR_REPLACE
);
$this->dataSourceModel
- ->method('getNextBunch')
+ ->method('getNextUniqueBunch')
->willReturnOnConsecutiveCalls($data);
$this->advancedPricing->expects($this->once())->method('validateRow')->willReturn(true);
@@ -577,7 +577,7 @@ public function testDeleteAdvancedPricingFormListSkuToDelete(): void
];
$this->dataSourceModel
- ->method('getNextBunch')
+ ->method('getNextUniqueBunch')
->willReturnOnConsecutiveCalls($data);
$this->advancedPricing->method('validateRow')->willReturn(true);
$expectedSkuList = ['sku value'];
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/README.md b/app/code/Magento/AdvancedSearch/README.md
index accbca83b57e4..49cafc827d7cb 100644
--- a/app/code/Magento/AdvancedSearch/README.md
+++ b/app/code/Magento/AdvancedSearch/README.md
@@ -7,7 +7,7 @@ The Magento_AdvancedSearch module introduces advanced search functionality and p
Before disabling or uninstalling this module, note that the following modules depends on this module:
- Magento_Elasticsearch
-- Magento_Elasticsearch6
+- Magento_Elasticsearch7
For information about module installation in Magento 2, see [Enable or disable modules](https://devdocs.magento.com/guides/v2.4/install-gde/install/cli/install-cli-subcommands-enable.html).
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/AmqpStore/LICENSE.txt b/app/code/Magento/AmqpStore/LICENSE.txt
deleted file mode 100644
index 49525fd99da9c..0000000000000
--- a/app/code/Magento/AmqpStore/LICENSE.txt
+++ /dev/null
@@ -1,48 +0,0 @@
-
-Open Software License ("OSL") v. 3.0
-
-This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work:
-
-Licensed under the Open Software License version 3.0
-
- 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following:
-
- 1. to reproduce the Original Work in copies, either alone or as part of a collective work;
-
- 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work;
-
- 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License;
-
- 4. to perform the Original Work publicly; and
-
- 5. to display the Original Work publicly.
-
- 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works.
-
- 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work.
-
- 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license.
-
- 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c).
-
- 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.
-
- 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer.
-
- 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation.
-
- 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c).
-
- 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.
-
- 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License.
-
- 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.
-
- 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.
-
- 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
-
- 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.
-
- 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.
\ No newline at end of file
diff --git a/app/code/Magento/AmqpStore/LICENSE_AFL.txt b/app/code/Magento/AmqpStore/LICENSE_AFL.txt
deleted file mode 100644
index f39d641b18a19..0000000000000
--- a/app/code/Magento/AmqpStore/LICENSE_AFL.txt
+++ /dev/null
@@ -1,48 +0,0 @@
-
-Academic Free License ("AFL") v. 3.0
-
-This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work:
-
-Licensed under the Academic Free License version 3.0
-
- 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following:
-
- 1. to reproduce the Original Work in copies, either alone or as part of a collective work;
-
- 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work;
-
- 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License;
-
- 4. to perform the Original Work publicly; and
-
- 5. to display the Original Work publicly.
-
- 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works.
-
- 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work.
-
- 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license.
-
- 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c).
-
- 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.
-
- 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer.
-
- 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation.
-
- 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c).
-
- 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.
-
- 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License.
-
- 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.
-
- 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.
-
- 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
-
- 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.
-
- 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.
diff --git a/app/code/Magento/AmqpStore/Plugin/AsynchronousOperations/MassConsumerEnvelopeCallback.php b/app/code/Magento/AmqpStore/Plugin/AsynchronousOperations/MassConsumerEnvelopeCallback.php
deleted file mode 100644
index e05004cea78cd..0000000000000
--- a/app/code/Magento/AmqpStore/Plugin/AsynchronousOperations/MassConsumerEnvelopeCallback.php
+++ /dev/null
@@ -1,101 +0,0 @@
-storeManager = $storeManager;
- $this->envelopeFactory = $envelopeFactory;
- $this->logger = $logger;
- }
-
- /**
- * Check if amqpProperties['application_headers'] have 'store_id' and use it to setCurrentStore
- * Restore original store value in consumer process after execution.
- * Reject queue messages because of wrong store_id.
- *
- * @param SubjectMassConsumerEnvelopeCallback $subject
- * @param callable $proceed
- * @param EnvelopeInterface $message
- * @return void
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
- */
- public function aroundExecute(
- SubjectMassConsumerEnvelopeCallback $subject,
- callable $proceed,
- EnvelopeInterface $message
- ) {
- $amqpProperties = $message->getProperties();
- if (isset($amqpProperties['application_headers'])) {
- $headers = $amqpProperties['application_headers'];
- if ($headers instanceof AMQPTable) {
- $headers = $headers->getNativeData();
- }
- if (isset($headers['store_id'])) {
- $storeId = $headers['store_id'];
- try {
- $currentStoreId = $this->storeManager->getStore()->getId();
- } catch (NoSuchEntityException $e) {
- $this->logger->error(
- sprintf(
- "Can't set currentStoreId during processing queue. Message rejected. Error %s.",
- $e->getMessage()
- )
- );
- $subject->getQueue()->reject($message, false, $e->getMessage());
- return;
- }
- if (isset($storeId) && $storeId !== $currentStoreId) {
- $this->storeManager->setCurrentStore($storeId);
- }
- }
- }
- $proceed($message);
- if (isset($storeId, $currentStoreId) && $storeId !== $currentStoreId) {
- $this->storeManager->setCurrentStore($currentStoreId);//restore original store value
- }
- }
-}
diff --git a/app/code/Magento/AmqpStore/Plugin/Framework/Amqp/Bulk/Exchange.php b/app/code/Magento/AmqpStore/Plugin/Framework/Amqp/Bulk/Exchange.php
deleted file mode 100644
index 37960a64d3861..0000000000000
--- a/app/code/Magento/AmqpStore/Plugin/Framework/Amqp/Bulk/Exchange.php
+++ /dev/null
@@ -1,116 +0,0 @@
-storeManager = $storeManager;
- $this->envelopeFactory = $envelopeFactory;
- $this->logger = $logger;
- }
-
- /**
- * Set current store_id in amqpProperties['application_headers']
- * so consumer may check store_id and execute operation in correct store scope.
- * Prevent publishing inconsistent messages because of store_id not defined or wrong.
- *
- * @param SubjectExchange $subject
- * @param string $topic
- * @param EnvelopeInterface[] $envelopes
- * @return array
- * @throws AMQPInvalidArgumentException
- * @throws \LogicException
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
- */
- public function beforeEnqueue(SubjectExchange $subject, $topic, array $envelopes)
- {
- try {
- $storeId = $this->storeManager->getStore()->getId();
- } catch (NoSuchEntityException $e) {
- $errorMessage = sprintf(
- "Can't get current storeId and inject to amqp message. Error %s.",
- $e->getMessage()
- );
- $this->logger->error($errorMessage);
- throw new \LogicException($errorMessage);
- }
-
- $updatedEnvelopes = [];
- foreach ($envelopes as $envelope) {
- $properties = $envelope->getProperties();
- if (!isset($properties)) {
- $properties = [];
- }
- if (isset($properties['application_headers'])) {
- $headers = $properties['application_headers'];
- if ($headers instanceof AMQPTable) {
- try {
- $headers->set('store_id', $storeId);
- } catch (AMQPInvalidArgumentException $ea) {
- $errorMessage = sprintf("Can't set storeId to amqp message. Error %s.", $ea->getMessage());
- $this->logger->error($errorMessage);
- throw new AMQPInvalidArgumentException($errorMessage);
- }
- $properties['application_headers'] = $headers;
- }
- } else {
- $properties['application_headers'] = new AMQPTable(['store_id' => $storeId]);
- }
- $updatedEnvelopes[] = $this->envelopeFactory->create(
- [
- 'body' => $envelope->getBody(),
- 'properties' => $properties
- ]
- );
- }
- if (!empty($updatedEnvelopes)) {
- $envelopes = $updatedEnvelopes;
- }
- return [$topic, $envelopes];
- }
-}
diff --git a/app/code/Magento/AmqpStore/README.md b/app/code/Magento/AmqpStore/README.md
deleted file mode 100644
index 202c1982620e8..0000000000000
--- a/app/code/Magento/AmqpStore/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Magento_AmqpStore module
-
-The Magento_AmqpStore module provides the ability to specify a store before publishing messages with the Advanced Message Queuing Protocol (AMQP).
-
-## Extensibility
-
-Extension developers can interact with the Magento_AmqpStore module using plugins. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.4/extension-dev-guide/plugins.html).
-
-[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.4/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_AmqpStore module.
diff --git a/app/code/Magento/AmqpStore/composer.json b/app/code/Magento/AmqpStore/composer.json
deleted file mode 100644
index 4d4685dda0b51..0000000000000
--- a/app/code/Magento/AmqpStore/composer.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "name": "magento/module-amqp-store",
- "description": "N/A",
- "config": {
- "sort-packages": true
- },
- "require": {
- "magento/framework": "*",
- "magento/framework-amqp": "*",
- "magento/module-store": "*",
- "php": "~7.4.0||~8.1.0"
- },
- "suggest": {
- "magento/module-asynchronous-operations": "*",
- "magento/framework-message-queue": "*"
- },
- "type": "magento2-module",
- "license": [
- "OSL-3.0",
- "AFL-3.0"
- ],
- "autoload": {
- "files": [
- "registration.php"
- ],
- "psr-4": {
- "Magento\\AmqpStore\\": ""
- }
- }
-}
diff --git a/app/code/Magento/AmqpStore/etc/di.xml b/app/code/Magento/AmqpStore/etc/di.xml
deleted file mode 100644
index 3bbbebd249535..0000000000000
--- a/app/code/Magento/AmqpStore/etc/di.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/app/code/Magento/AmqpStore/etc/module.xml b/app/code/Magento/AmqpStore/etc/module.xml
deleted file mode 100644
index 085b97b5e2f96..0000000000000
--- a/app/code/Magento/AmqpStore/etc/module.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/app/code/Magento/Analytics/Model/Connector/Http/Client/Curl.php b/app/code/Magento/Analytics/Model/Connector/Http/Client/Curl.php
index f81cb065aa2ad..60bc47c14e763 100644
--- a/app/code/Magento/Analytics/Model/Connector/Http/Client/Curl.php
+++ b/app/code/Magento/Analytics/Model/Connector/Http/Client/Curl.php
@@ -5,6 +5,7 @@
*/
namespace Magento\Analytics\Model\Connector\Http\Client;
+use Laminas\Http\Response;
use Magento\Analytics\Model\Connector\Http\ConverterInterface;
use Psr\Log\LoggerInterface;
use Magento\Framework\HTTP\Adapter\CurlFactory;
@@ -56,11 +57,12 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function request($method, $url, array $body = [], array $headers = [], $version = '1.1')
{
- $response = new \Zend_Http_Response(0, []);
+ $response = new Response();
+ $response->setCustomStatusCode(Response::STATUS_CODE_CUSTOM);
try {
$curl = $this->curlFactory->create();
@@ -93,6 +95,8 @@ public function request($method, $url, array $body = [], array $headers = [], $v
}
/**
+ * Apply content type header from converter
+ *
* @param array $headers
*
* @return array
diff --git a/app/code/Magento/Analytics/Model/Connector/Http/ClientInterface.php b/app/code/Magento/Analytics/Model/Connector/Http/ClientInterface.php
index a1e1f057684f6..3618dc9d3e135 100644
--- a/app/code/Magento/Analytics/Model/Connector/Http/ClientInterface.php
+++ b/app/code/Magento/Analytics/Model/Connector/Http/ClientInterface.php
@@ -5,10 +5,12 @@
*/
namespace Magento\Analytics\Model\Connector\Http;
+use Laminas\Http\Response;
+
/**
* An interface for an HTTP client.
*
- * Sends requests via a proper adapter.
+ * Send requests via a proper adapter.
*/
interface ClientInterface
{
@@ -23,7 +25,7 @@ interface ClientInterface
* @param array $headers
* @param string $version
*
- * @return \Zend_Http_Response
+ * @return Response
*/
public function request($method, $url, array $body = [], array $headers = [], $version = '1.1');
}
diff --git a/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php b/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php
index e32b37ef239c9..eb1b2ac324ec6 100644
--- a/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php
+++ b/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php
@@ -5,6 +5,8 @@
*/
namespace Magento\Analytics\Model\Connector\Http;
+use Laminas\Http\Response;
+
/**
* Extract result from http response. Call response handler by status.
*/
@@ -13,12 +15,12 @@ class ResponseResolver
/**
* @var ConverterInterface
*/
- private $converter;
+ private ConverterInterface $converter;
/**
* @var array
*/
- private $responseHandlers;
+ private array $responseHandlers;
/**
* @param ConverterInterface $converter
@@ -33,24 +35,27 @@ public function __construct(ConverterInterface $converter, array $responseHandle
/**
* Get result from $response.
*
- * @param \Zend_Http_Response $response
+ * @param Response $response
* @return bool|string
*/
- public function getResult(\Zend_Http_Response $response)
+ public function getResult(Response $response)
{
$result = false;
$converterMediaType = $this->converter->getContentMediaType();
/** Content-Type header may not only contain media-type declaration */
- if ($response->getBody()
- && is_int(strripos($response->getHeader('Content-Type'), (string) $converterMediaType))) {
- $responseBody = $this->converter->fromBody($response->getBody());
+ $responseBody = $response->getBody();
+ $contentType = $response->getHeaders()->has('Content-Type') ?
+ $response->getHeaders()->get('Content-Type')->getFieldValue() :
+ '';
+ if ($responseBody && is_int(strripos($contentType, $converterMediaType))) {
+ $responseBody = $this->converter->fromBody($responseBody);
} else {
$responseBody = [];
}
- if (array_key_exists($response->getStatus(), $this->responseHandlers)) {
- $result = $this->responseHandlers[$response->getStatus()]->handleResponse($responseBody);
+ if (array_key_exists($response->getStatusCode(), $this->responseHandlers)) {
+ $result = $this->responseHandlers[$response->getStatusCode()]->handleResponse($responseBody);
}
return $result;
diff --git a/app/code/Magento/Analytics/Model/Connector/NotifyDataChangedCommand.php b/app/code/Magento/Analytics/Model/Connector/NotifyDataChangedCommand.php
index f1a8ea6460f9d..4681cd4de6da1 100644
--- a/app/code/Magento/Analytics/Model/Connector/NotifyDataChangedCommand.php
+++ b/app/code/Magento/Analytics/Model/Connector/NotifyDataChangedCommand.php
@@ -5,12 +5,12 @@
*/
namespace Magento\Analytics\Model\Connector;
+use Laminas\Http\Request;
use Magento\Analytics\Model\AnalyticsToken;
+use Magento\Analytics\Model\Connector\Http\ResponseResolver;
use Magento\Framework\App\Config\ScopeConfigInterface;
-use Magento\Framework\HTTP\ZendClient;
-use Psr\Log\LoggerInterface;
use Magento\Store\Model\Store;
-use Magento\Analytics\Model\Connector\Http\ResponseResolver;
+use Psr\Log\LoggerInterface;
/**
* Command notifies MBI about that data collection was finished.
@@ -79,7 +79,7 @@ public function execute()
$result = false;
if ($this->analyticsToken->isTokenExist()) {
$response = $this->httpClient->request(
- ZendClient::POST,
+ Request::METHOD_POST,
$this->config->getValue($this->notifyDataChangedUrlPath),
[
"access-token" => $this->analyticsToken->getToken(),
diff --git a/app/code/Magento/Analytics/Model/Connector/OTPRequest.php b/app/code/Magento/Analytics/Model/Connector/OTPRequest.php
index c05357400d075..16636ec08141c 100644
--- a/app/code/Magento/Analytics/Model/Connector/OTPRequest.php
+++ b/app/code/Magento/Analytics/Model/Connector/OTPRequest.php
@@ -5,10 +5,10 @@
*/
namespace Magento\Analytics\Model\Connector;
+use Laminas\Http\Request;
use Magento\Analytics\Model\AnalyticsToken;
use Magento\Analytics\Model\Connector\Http\ResponseResolver;
use Magento\Framework\App\Config\ScopeConfigInterface;
-use Magento\Framework\HTTP\ZendClient;
use Magento\Store\Model\Store;
use Psr\Log\LoggerInterface;
@@ -91,7 +91,7 @@ public function call()
if ($this->analyticsToken->isTokenExist()) {
$response = $this->httpClient->request(
- ZendClient::POST,
+ Request::METHOD_POST,
$this->config->getValue($this->otpUrlConfigPath),
[
"access-token" => $this->analyticsToken->getToken(),
@@ -105,7 +105,9 @@ public function call()
sprintf(
'Obtaining of an OTP from the MBI service has been failed: %s. Content-Type: %s',
!empty($response->getBody()) ? $response->getBody() : 'Response body is empty',
- $response->getHeader('Content-Type')
+ $response->getHeaders()->has('Content-Type') ?
+ $response->getHeaders()->get('Content-Type')->getFieldValue() :
+ ''
)
);
}
diff --git a/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php b/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php
index e35c9bb42bc43..40e4f42fdf025 100644
--- a/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php
+++ b/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php
@@ -5,13 +5,13 @@
*/
namespace Magento\Analytics\Model\Connector;
+use Laminas\Http\Request;
use Magento\Analytics\Model\AnalyticsToken;
use Magento\Analytics\Model\Connector\Http\ResponseResolver;
use Magento\Analytics\Model\IntegrationManager;
use Magento\Framework\App\Config\ScopeConfigInterface;
-use Psr\Log\LoggerInterface;
-use Magento\Framework\HTTP\ZendClient;
use Magento\Store\Model\Store;
+use Psr\Log\LoggerInterface;
/**
* SignUp merchant for Free Tier project
@@ -98,7 +98,7 @@ public function execute()
if ($integrationToken) {
$this->integrationManager->activateIntegration();
$response = $this->httpClient->request(
- ZendClient::POST,
+ Request::METHOD_POST,
$this->config->getValue($this->signUpUrlPath),
[
"token" => $integrationToken->getData('token'),
@@ -113,7 +113,9 @@ public function execute()
'Subscription for MBI service has been failed. An error occurred during token exchange: %s.'
. ' Content-Type: %s',
!empty($response->getBody()) ? $response->getBody() : 'Response body is empty',
- $response->getHeader('Content-Type')
+ $response->getHeaders()->has('Content-Type') ?
+ $response->getHeaders()->get('Content-Type')->getFieldValue() :
+ ''
)
);
}
diff --git a/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php b/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php
index 59878ff9c0814..54b1af15adb98 100644
--- a/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php
+++ b/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php
@@ -5,12 +5,12 @@
*/
namespace Magento\Analytics\Model\Connector;
+use Laminas\Http\Request;
use Magento\Analytics\Model\AnalyticsToken;
use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler;
use Magento\Analytics\Model\Connector\Http\ResponseResolver;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\FlagManager;
-use Magento\Framework\HTTP\ZendClient;
use Magento\Store\Model\Store;
use Psr\Log\LoggerInterface;
@@ -88,7 +88,7 @@ public function execute()
$result = false;
if ($this->analyticsToken->isTokenExist()) {
$response = $this->httpClient->request(
- ZendClient::PUT,
+ Request::METHOD_PUT,
$this->config->getValue($this->updateUrlPath),
[
"url" => $this->flagManager
@@ -103,7 +103,9 @@ public function execute()
sprintf(
'Update of the subscription for MBI service has been failed: %s. Content-Type: %s',
!empty($response->getBody()) ? $response->getBody() : 'Response body is empty',
- $response->getHeader('Content-Type')
+ $response->getHeaders()->has('Content-Type') ?
+ $response->getHeaders()->get('Content-Type')->getFieldValue() :
+ ''
)
);
}
diff --git a/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.xml b/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.xml
index ac4fca843a36b..c50341769318d 100644
--- a/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.xml
+++ b/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.xml
@@ -15,6 +15,6 @@
-
+
diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php
index 29ba2489f3c34..5e429db67db7c 100644
--- a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php
+++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php
@@ -7,12 +7,14 @@
namespace Magento\Analytics\Test\Unit\Model\Connector\Http\Client;
+use Laminas\Http\Exception\RuntimeException;
+use Laminas\Http\Request;
+use Laminas\Http\Response;
use Magento\Analytics\Model\Connector\Http\Client\Curl;
use Magento\Analytics\Model\Connector\Http\ConverterInterface;
use Magento\Analytics\Model\Connector\Http\JsonConverter;
use Magento\Framework\HTTP\Adapter\CurlFactory;
use Magento\Framework\HTTP\ResponseFactory;
-use Magento\Framework\HTTP\ZendClient;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -94,7 +96,7 @@ public function getTestData()
'version' => '1.1',
'body'=> ['name' => 'value'],
'url' => 'http://www.mystore.com',
- 'method' => ZendClient::POST
+ 'method' => Request::METHOD_POST
]
]
];
@@ -103,13 +105,15 @@ public function getTestData()
/**
* @param array $data
* @return void
- * @throws \Zend_Http_Exception
+ * @throws RuntimeException
* @dataProvider getTestData
*/
public function testRequestSuccess(array $data)
{
$responseString = 'This is response.';
- $response = new \Zend_Http_Response(201, [], $responseString);
+ $response = new Response();
+ $response->setStatusCode(Response::STATUS_CODE_201);
+ $response->setContent($responseString);
$this->curlAdapterMock->expects($this->once())
->method('write')
->with(
@@ -140,12 +144,13 @@ public function testRequestSuccess(array $data)
/**
* @param array $data
* @return void
- * @throws \Zend_Http_Exception
+ * @throws RuntimeException
* @dataProvider getTestData
*/
public function testRequestError(array $data)
{
- $response = new \Zend_Http_Response(0, []);
+ $response = new Response();
+ $response->setCustomStatusCode(Response::STATUS_CODE_CUSTOM);
$this->curlAdapterMock->expects($this->once())
->method('write')
->with(
diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php
index 10a987e44af63..8f74822d6a1c4 100644
--- a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php
+++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php
@@ -7,6 +7,8 @@
namespace Magento\Analytics\Test\Unit\Model\Connector\Http;
+use Laminas\Http\Exception\RuntimeException;
+use Laminas\Http\Response;
use Magento\Analytics\Model\Connector\Http\ConverterInterface;
use Magento\Analytics\Model\Connector\Http\ResponseHandlerInterface;
use Magento\Analytics\Model\Connector\Http\ResponseResolver;
@@ -66,16 +68,18 @@ protected function setUp(): void
/**
* @return void
- * @throws \Zend_Http_Exception
+ * @throws RuntimeException
*/
public function testGetResultHandleResponseSuccess()
{
$expectedBody = ['test' => 'testValue'];
- $response = new \Zend_Http_Response(201, ['Content-Type' => 'application/json'], json_encode($expectedBody));
+ $response = new Response();
+ $response->setStatusCode(Response::STATUS_CODE_201);
+ $response->getHeaders()->addHeaderLine('Content-Type', 'application/json');
+ $response->setContent(json_encode($expectedBody));
$this->converterMock
->method('getContentMediaType')
->willReturn('application/json');
-
$this->successResponseHandlerMock
->expects($this->once())
->method('handleResponse')
@@ -92,12 +96,15 @@ public function testGetResultHandleResponseSuccess()
/**
* @return void
- * @throws \Zend_Http_Exception
+ * @throws RuntimeException
*/
public function testGetResultHandleResponseUnexpectedContentType()
{
$expectedBody = 'testString';
- $response = new \Zend_Http_Response(201, ['Content-Type' => 'plain/text'], $expectedBody);
+ $response = new Response();
+ $response->setStatusCode(Response::STATUS_CODE_201);
+ $response->getHeaders()->addHeaderLine('Content-Type', 'plain/text');
+ $response->setContent($expectedBody);
$this->converterMock
->method('getContentMediaType')
->willReturn('application/json');
diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/NotifyDataChangedCommandTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/NotifyDataChangedCommandTest.php
index 501142079b5d7..3fb5009b56b19 100644
--- a/app/code/Magento/Analytics/Test/Unit/Model/Connector/NotifyDataChangedCommandTest.php
+++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/NotifyDataChangedCommandTest.php
@@ -7,6 +7,8 @@
namespace Magento\Analytics\Test\Unit\Model\Connector;
+use Laminas\Http\Request;
+use Laminas\Http\Response;
use Magento\Analytics\Model\AnalyticsToken;
use Magento\Analytics\Model\Connector\Http\ClientInterface;
use Magento\Analytics\Model\Connector\Http\JsonConverter;
@@ -14,7 +16,6 @@
use Magento\Analytics\Model\Connector\Http\ResponseResolver;
use Magento\Analytics\Model\Connector\NotifyDataChangedCommand;
use Magento\Framework\App\Config\ScopeConfigInterface;
-use Magento\Framework\HTTP\ZendClient;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use PHPUnit\Framework\MockObject\MockObject;
@@ -103,13 +104,15 @@ public function testExecuteSuccess()
$this->analyticsTokenMock->expects($this->once())
->method('getToken')
->willReturn($token);
+ $response = new Response();
+ $response->setStatusCode(Response::STATUS_CODE_201);
$this->httpClientMock->expects($this->once())
->method('request')
->with(
- ZendClient::POST,
+ Request::METHOD_POST,
$configVal,
['access-token' => $token, 'url' => $configVal]
- )->willReturn(new \Zend_Http_Response(201, []));
+ )->willReturn($response);
$this->assertTrue($this->notifyDataChangedCommand->execute());
}
diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/OTPRequestTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/OTPRequestTest.php
index b5c12f40090f2..e435b07211f96 100644
--- a/app/code/Magento/Analytics/Test/Unit/Model/Connector/OTPRequestTest.php
+++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/OTPRequestTest.php
@@ -7,12 +7,13 @@
namespace Magento\Analytics\Test\Unit\Model\Connector;
+use Laminas\Http\Request;
+use Laminas\Http\Response;
use Magento\Analytics\Model\AnalyticsToken;
use Magento\Analytics\Model\Connector\Http\ClientInterface;
use Magento\Analytics\Model\Connector\Http\ResponseResolver;
use Magento\Analytics\Model\Connector\OTPRequest;
use Magento\Framework\App\Config\ScopeConfigInterface;
-use Magento\Framework\HTTP\ZendClient;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
@@ -87,7 +88,7 @@ private function getTestData()
'otp' => 'thisisotp',
'url' => 'http://www.mystore.com',
'access-token' => 'thisisaccesstoken',
- 'method' => ZendClient::POST,
+ 'method' => Request::METHOD_POST,
'body'=> ['access-token' => 'thisisaccesstoken','url' => 'http://www.mystore.com'],
];
}
@@ -110,6 +111,8 @@ public function testCallSuccess()
->method('getValue')
->willReturn($data['url']);
+ $response = new Response();
+ $response->setStatusCode(Response::STATUS_CODE_201);
$this->httpClientMock->expects($this->once())
->method('request')
->with(
@@ -117,7 +120,7 @@ public function testCallSuccess()
$data['url'],
$data['body']
)
- ->willReturn(new \Zend_Http_Response(201, []));
+ ->willReturn($response);
$this->responseResolverMock->expects($this->once())
->method('getResult')
->willReturn($data['otp']);
@@ -161,6 +164,8 @@ public function testCallNoOtp()
->method('getValue')
->willReturn($data['url']);
+ $response = new Response();
+ $response->setCustomStatusCode(Response::STATUS_CODE_CUSTOM);
$this->httpClientMock->expects($this->once())
->method('request')
->with(
@@ -168,7 +173,7 @@ public function testCallNoOtp()
$data['url'],
$data['body']
)
- ->willReturn(new \Zend_Http_Response(0, []));
+ ->willReturn($response);
$this->responseResolverMock->expects($this->once())
->method('getResult')
diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php
index 9f87418038591..2b5a11d552b3d 100644
--- a/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php
+++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php
@@ -7,13 +7,15 @@
namespace Magento\Analytics\Test\Unit\Model\Connector;
+use Laminas\Http\Exception\RuntimeException;
+use Laminas\Http\Request;
+use Laminas\Http\Response;
use Magento\Analytics\Model\AnalyticsToken;
use Magento\Analytics\Model\Connector\Http\ClientInterface;
use Magento\Analytics\Model\Connector\Http\ResponseResolver;
use Magento\Analytics\Model\Connector\SignUpCommand;
use Magento\Analytics\Model\IntegrationManager;
use Magento\Framework\App\Config\ScopeConfigInterface;
-use Magento\Framework\HTTP\ZendClient;
use Magento\Integration\Model\Oauth\Token as IntegrationToken;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -85,7 +87,7 @@ protected function setUp(): void
}
/**
- * @throws \Zend_Http_Exception
+ * @throws RuntimeException
* @return void
*/
public function testExecuteSuccess()
@@ -105,7 +107,9 @@ public function testExecuteSuccess()
->method('getData')
->with('token')
->willReturn($data['integration-token']);
- $httpResponse = new \Zend_Http_Response(201, [], '{"access-token": "' . $data['access-token'] . '"}');
+ $response = new Response();
+ $response->setStatusCode(Response::STATUS_CODE_201);
+ $response->setContent('{"access-token": "' . $data['access-token'] . '"}');
$this->httpClientMock->expects($this->once())
->method('request')
->with(
@@ -113,10 +117,10 @@ public function testExecuteSuccess()
$data['url'],
$data['body']
)
- ->willReturn($httpResponse);
+ ->willReturn($response);
$this->responseResolverMock
->method('getResult')
- ->with($httpResponse)
+ ->with($response)
->willReturn(true);
$this->assertTrue($this->signUpCommand->execute());
}
@@ -135,7 +139,7 @@ public function testExecuteFailureCannotGenerateToken()
}
/**
- * @throws \Zend_Http_Exception
+ * @throws RuntimeException
* @return void
*/
public function testExecuteFailureResponseIsEmpty()
@@ -146,10 +150,11 @@ public function testExecuteFailureResponseIsEmpty()
$this->integrationManagerMock->expects($this->once())
->method('activateIntegration')
->willReturn(true);
- $httpResponse = new \Zend_Http_Response(0, []);
+ $response = new Response();
+ $response->setCustomStatusCode(Response::STATUS_CODE_CUSTOM);
$this->httpClientMock->expects($this->once())
->method('request')
- ->willReturn($httpResponse);
+ ->willReturn($response);
$this->responseResolverMock
->method('getResult')
->willReturn(false);
@@ -167,7 +172,7 @@ private function getTestData()
'url' => 'http://www.mystore.com',
'access-token' => 'thisisaccesstoken',
'integration-token' => 'thisisintegrationtoken',
- 'method' => ZendClient::POST,
+ 'method' => Request::METHOD_POST,
'body'=> ['token' => 'thisisintegrationtoken','url' => 'http://www.mystore.com'],
];
}
diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/UpdateCommandTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/UpdateCommandTest.php
index d9ba16827aa59..8fe9b0c9923b8 100644
--- a/app/code/Magento/Analytics/Test/Unit/Model/Connector/UpdateCommandTest.php
+++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/UpdateCommandTest.php
@@ -7,6 +7,8 @@
namespace Magento\Analytics\Test\Unit\Model\Connector;
+use Laminas\Http\Request;
+use Laminas\Http\Response;
use Magento\Analytics\Model\AnalyticsToken;
use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler;
use Magento\Analytics\Model\Connector\Http\ClientInterface;
@@ -14,7 +16,6 @@
use Magento\Analytics\Model\Connector\UpdateCommand;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\FlagManager;
-use Magento\Framework\HTTP\ZendClient;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
@@ -102,17 +103,19 @@ public function testExecuteSuccess()
->method('getToken')
->willReturn($token);
+ $response = new Response();
+ $response->setStatusCode(Response::STATUS_CODE_200);
$this->httpClientMock->expects($this->once())
->method('request')
->with(
- ZendClient::PUT,
+ Request::METHOD_PUT,
$configVal,
[
'url' => $url,
'new-url' => $configVal,
'access-token' => $token
]
- )->willReturn(new \Zend_Http_Response(200, []));
+ )->willReturn($response);
$this->responseResolverMock->expects($this->once())
->method('getResult')
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/Model/ResourceModel/Operation/Collection.php b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/Collection.php
index 18c5f09794c77..bbbb05b6ef534 100644
--- a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/Collection.php
+++ b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/Collection.php
@@ -6,8 +6,11 @@
*/
namespace Magento\AsynchronousOperations\Model\ResourceModel\Operation;
+use Magento\AsynchronousOperations\Model\Operation;
+use Magento\AsynchronousOperations\Model\ResourceModel\Operation as OperationResourceModel;
+
/**
- * Class Collection
+ * Class Collection for Magento Operation table
* @codeCoverageIgnore
*/
class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
@@ -20,9 +23,10 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab
protected function _construct()
{
$this->_init(
- \Magento\AsynchronousOperations\Model\Operation::class,
- \Magento\AsynchronousOperations\Model\ResourceModel\Operation::class
+ Operation::class,
+ OperationResourceModel::class
);
$this->setMainTable('magento_operation');
+ $this->_setIdFieldName(OperationResourceModel::TABLE_PRIMARY_KEY);
}
}
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/Model/Acl/AclRetriever.php b/app/code/Magento/Authorization/Model/Acl/AclRetriever.php
index 904c8d0ea7794..66bb950ec377d 100644
--- a/app/code/Magento/Authorization/Model/Acl/AclRetriever.php
+++ b/app/code/Magento/Authorization/Model/Acl/AclRetriever.php
@@ -21,8 +21,8 @@
*/
class AclRetriever
{
- const PERMISSION_ANONYMOUS = 'anonymous';
- const PERMISSION_SELF = 'self';
+ public const PERMISSION_ANONYMOUS = 'anonymous';
+ public const PERMISSION_SELF = 'self';
/**
* @var \Psr\Log\LoggerInterface
@@ -117,7 +117,7 @@ public function getAllowedResourcesByRole($roleId)
/** @var \Magento\Authorization\Model\Rules $ruleItem */
foreach ($rulesCollection->getItems() as $ruleItem) {
$resourceId = $ruleItem->getResourceId();
- if ($acl->has($resourceId) && $acl->isAllowed($roleId, $resourceId)) {
+ if ($acl->hasResource($resourceId) && $acl->isAllowed($roleId, $resourceId)) {
$allowedResources[] = $resourceId;
}
}
diff --git a/app/code/Magento/Authorization/Model/Acl/Loader/Rule.php b/app/code/Magento/Authorization/Model/Acl/Loader/Rule.php
index 653882f7a2810..c457a4654ccef 100644
--- a/app/code/Magento/Authorization/Model/Acl/Loader/Rule.php
+++ b/app/code/Magento/Authorization/Model/Acl/Loader/Rule.php
@@ -104,7 +104,7 @@ private function applyPermissionsAccordingToRules(Acl $acl): array
$resource = $rule['resource_id'];
$privileges = !empty($rule['privileges']) ? explode(',', $rule['privileges']) : null;
- if ($acl->has($resource)) {
+ if ($acl->hasResource($resource)) {
$foundResources[$resource] = $resource;
if ($rule['permission'] == 'allow') {
if ($resource === $this->_rootResource->getId()) {
diff --git a/app/code/Magento/Authorization/Model/Acl/Role/Generic.php b/app/code/Magento/Authorization/Model/Acl/Role/Generic.php
index c4d1f8baa25c6..877b98ff37ac8 100644
--- a/app/code/Magento/Authorization/Model/Acl/Role/Generic.php
+++ b/app/code/Magento/Authorization/Model/Acl/Role/Generic.php
@@ -5,9 +5,11 @@
*/
namespace Magento\Authorization\Model\Acl\Role;
+use Laminas\Permissions\Acl\Role\GenericRole;
+
/**
* Generic acl role
*/
-class Generic extends \Zend_Acl_Role
+class Generic extends GenericRole
{
}
diff --git a/app/code/Magento/Authorization/README.md b/app/code/Magento/Authorization/README.md
index f5fa0fccfea3a..916903ffff36b 100644
--- a/app/code/Magento/Authorization/README.md
+++ b/app/code/Magento/Authorization/README.md
@@ -4,7 +4,7 @@ The Magento_Authorization module enables management of access control list roles
## Installation details
-The Magento_AdminNotification module creates the following tables in the database:
+The Magento_Authorization module creates the following tables in the database using `db_schema.xml`:
- `authorization_role`
- `authorization_rule`
diff --git a/app/code/Magento/Authorization/Test/Fixture/Role.php b/app/code/Magento/Authorization/Test/Fixture/Role.php
index 606d4fc878429..db1031d6f1c81 100644
--- a/app/code/Magento/Authorization/Test/Fixture/Role.php
+++ b/app/code/Magento/Authorization/Test/Fixture/Role.php
@@ -9,14 +9,12 @@
use Magento\Authorization\Model\Acl\Role\Group;
use Magento\Authorization\Model\ResourceModel\Role as RoleResource;
+use Magento\Authorization\Model\RoleFactory;
+use Magento\Authorization\Model\RulesFactory;
use Magento\Authorization\Model\UserContextInterface;
use Magento\Framework\DataObject;
-use Magento\SharedCatalog\Model\SharedCatalogFactory;
use Magento\TestFramework\Fixture\Data\ProcessorInterface;
use Magento\TestFramework\Fixture\RevertibleDataFixtureInterface;
-use Magento\Authorization\Model\RoleFactory;
-use Magento\Authorization\Model\RulesFactory;
-use Magento\User\Model\UserFactory;
/**
* Creating a new admin role
diff --git a/app/code/Magento/Authorization/Test/Unit/Model/Acl/AclRetrieverTest.php b/app/code/Magento/Authorization/Test/Unit/Model/Acl/AclRetrieverTest.php
index 15ca3cfd1ffce..2b609e67b51b6 100644
--- a/app/code/Magento/Authorization/Test/Unit/Model/Acl/AclRetrieverTest.php
+++ b/app/code/Magento/Authorization/Test/Unit/Model/Acl/AclRetrieverTest.php
@@ -165,9 +165,9 @@ protected function createAclRetriever()
/**
* @var Acl|MockObject $aclMock
*/
- $aclMock = $this->createPartialMock(Acl::class, ['has', 'isAllowed']);
- $aclMock->expects($this->any())->method('has')->willReturn(true);
- $aclMock->expects($this->any())->method('isAllowed')->willReturn(true);
+ $aclMock = $this->createPartialMock(Acl::class, ['hasResource', 'isAllowed']);
+ $aclMock->method('hasResource')->willReturn(true);
+ $aclMock->method('isAllowed')->willReturn(true);
/**
* @var Builder|MockObject $aclBuilderMock
diff --git a/app/code/Magento/Authorization/Test/Unit/Model/Acl/Loader/RuleTest.php b/app/code/Magento/Authorization/Test/Unit/Model/Acl/Loader/RuleTest.php
index e56f40ba70769..37699c61f9164 100644
--- a/app/code/Magento/Authorization/Test/Unit/Model/Acl/Loader/RuleTest.php
+++ b/app/code/Magento/Authorization/Test/Unit/Model/Acl/Loader/RuleTest.php
@@ -114,7 +114,7 @@ public function testPopulateAclFromCache(): void
);
$aclMock = $this->createMock(Acl::class);
- $aclMock->method('has')->willReturn(true);
+ $aclMock->method('hasResource')->willReturn(true);
$aclMock
->method('allow')
->withConsecutive(
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/Driver/AwsS3.php b/app/code/Magento/AwsS3/Driver/AwsS3.php
index 02c7e94de448c..def5088e89326 100644
--- a/app/code/Magento/AwsS3/Driver/AwsS3.php
+++ b/app/code/Magento/AwsS3/Driver/AwsS3.php
@@ -861,15 +861,21 @@ public function fileWrite($resource, $data)
*/
public function fileClose($resource): bool
{
+ if (!is_resource($resource)) {
+ return false;
+ }
//phpcs:disable
- $resourcePath = stream_get_meta_data($resource)['uri'];
+ $meta = stream_get_meta_data($resource);
//phpcs:enable
foreach ($this->streams as $path => $stream) {
// phpcs:ignore
- if (stream_get_meta_data($stream)['uri'] === $resourcePath) {
+ if (stream_get_meta_data($stream)['uri'] === $meta['uri']) {
+ if (isset($meta['seekable']) && $meta['seekable']) {
+ // rewind the file pointer to make sure the full content of the file is saved
+ $this->fileSeek($resource, 0);
+ }
$this->adapter->writeStream($path, $resource, new Config(self::CONFIG));
-
// Remove path from streams after
unset($this->streams[$path]);
diff --git a/app/code/Magento/AwsS3/Test/Mftf/Test/AdminExportSimpleProductWithImageAndImageAltForDiffrentScopeTest.xml b/app/code/Magento/AwsS3/Test/Mftf/Test/AdminExportSimpleProductWithImageAndImageAltForDiffrentScopeTest.xml
new file mode 100644
index 0000000000000..d138a117b2d78
--- /dev/null
+++ b/app/code/Magento/AwsS3/Test/Mftf/Test/AdminExportSimpleProductWithImageAndImageAltForDiffrentScopeTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+ {"local":"var/export/{$grabNameFile}","s3":"import_export/export/{$grabNameFile}"}
+
+
+ {"local":"var/export/{$grabNameFile}","s3":"import_export/export/{$grabNameFile}"}
+ $createProductImage.entry[content][name]$,"english image alt text 1"
+
+
+ {"local":"var/export/{$grabNameFile}","s3":"import_export/export/{$grabNameFile}"}
+ $createProductImage.entry[content][name]$,"french image alt text 1"
+
+
+
diff --git a/app/code/Magento/AwsS3/Test/Mftf/Test/AdminImportSimpleProductImageLongNameTest.xml b/app/code/Magento/AwsS3/Test/Mftf/Test/AdminImportSimpleProductImageLongNameTest.xml
new file mode 100644
index 0000000000000..374d98b7d585e
--- /dev/null
+++ b/app/code/Magento/AwsS3/Test/Mftf/Test/AdminImportSimpleProductImageLongNameTest.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+ var/import/images/test_image_long_name
+
+
+ dev/tests/acceptance/tests/_data/{{placeholderBaseImageLongName.file}}
+ var/import/images/test_image_long_name/{{placeholderBaseImageLongName.file}}
+
+
+
+
+
+ var/import/images/test_image_long_name
+
+
+
+
+
diff --git a/app/code/Magento/AwsS3/Test/Unit/Driver/AwsS3Test.php b/app/code/Magento/AwsS3/Test/Unit/Driver/AwsS3Test.php
index 811f3567f86a7..965ecaf6565db 100644
--- a/app/code/Magento/AwsS3/Test/Unit/Driver/AwsS3Test.php
+++ b/app/code/Magento/AwsS3/Test/Unit/Driver/AwsS3Test.php
@@ -513,4 +513,28 @@ public function testRenameSameDestination(): void
self::assertTrue($this->driver->rename('test/path', 'test/path'));
}
+
+ public function testFileShouldBeRewindBeforeSave(): void
+ {
+ $resource = $this->driver->fileOpen('test/path', 'w');
+ $this->driver->fileWrite($resource, 'abc');
+ $this->adapterMock->method('fileExists')->willReturn(false);
+ $this->adapterMock->expects($this->once())
+ ->method('writeStream')
+ ->with(
+ 'test/path',
+ $this->callback(
+ // assert that the file pointer is at the beginning of the file before saving it in aws
+ fn ($stream) => $stream === $resource && is_resource($stream) && ftell($stream) === 0
+ )
+ );
+ $this->driver->fileClose($resource);
+ }
+
+ public function testFileCloseShouldReturnFalseIfTheArgumentIsNotAResource(): void
+ {
+ $this->assertEquals(false, $this->driver->fileClose(''));
+ $this->assertEquals(false, $this->driver->fileClose(null));
+ $this->assertEquals(false, $this->driver->fileClose(false));
+ }
}
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/Block/Widget/Grid/Extended.php b/app/code/Magento/Backend/Block/Widget/Grid/Extended.php
index a7bdd13bb00b2..22ca8a49c155b 100644
--- a/app/code/Magento/Backend/Block/Widget/Grid/Extended.php
+++ b/app/code/Magento/Backend/Block/Widget/Grid/Extended.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Backend\Block\Widget\Grid;
use Magento\Framework\App\Filesystem\DirectoryList;
@@ -18,6 +20,7 @@
* @SuppressWarnings(PHPMD.NumberOfChildren)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
+ * @see \Magento\Backend\Block\Widget\Grid
*/
class Extended extends \Magento\Backend\Block\Widget\Grid implements \Magento\Backend\Block\Widget\Grid\ExportInterface
{
@@ -1151,7 +1154,7 @@ public function getExcelFile($sheetName = '')
{
$this->_isExport = true;
$this->_prepareGrid();
-
+ $this->getCollection()->setPageSize(0);
$convert = new \Magento\Framework\Convert\Excel(
$this->getCollection()->getIterator(),
[$this, 'getRowRecord']
diff --git a/app/code/Magento/Backend/Console/Command/AbstractCacheSetCommand.php b/app/code/Magento/Backend/Console/Command/AbstractCacheSetCommand.php
index 02390d03783df..601e712e522e7 100644
--- a/app/code/Magento/Backend/Console/Command/AbstractCacheSetCommand.php
+++ b/app/code/Magento/Backend/Console/Command/AbstractCacheSetCommand.php
@@ -6,10 +6,12 @@
namespace Magento\Backend\Console\Command;
+use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
+ * phpcs:disable Magento2.Classes.AbstractApi
* @api
* @since 100.0.2
*/
@@ -23,7 +25,7 @@ abstract class AbstractCacheSetCommand extends AbstractCacheManageCommand
abstract protected function isEnable();
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -43,5 +45,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$output->writeln('Cleaned cache types:');
$output->writeln(join(PHP_EOL, $changedTypes));
}
+
+ return Cli::RETURN_SUCCESS;
}
}
diff --git a/app/code/Magento/Backend/Console/Command/AbstractCacheTypeManageCommand.php b/app/code/Magento/Backend/Console/Command/AbstractCacheTypeManageCommand.php
index 2facdf165c5df..14905ecf0c1f2 100644
--- a/app/code/Magento/Backend/Console/Command/AbstractCacheTypeManageCommand.php
+++ b/app/code/Magento/Backend/Console/Command/AbstractCacheTypeManageCommand.php
@@ -6,12 +6,14 @@
namespace Magento\Backend\Console\Command;
+use Magento\Framework\Console\Cli;
use Magento\Framework\Event\ManagerInterface as EventManagerInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Magento\Framework\App\Cache\Manager;
/**
+ * phpcs:disable Magento2.Classes.AbstractApi
* @api
* @since 100.0.2
*/
@@ -54,7 +56,7 @@ abstract protected function getDisplayMessage();
*
* @param InputInterface $input
* @param OutputInterface $output
- * @return void
+ * @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -62,5 +64,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->performAction($types);
$output->writeln($this->getDisplayMessage());
$output->writeln(join(PHP_EOL, $types));
+
+ return Cli::RETURN_SUCCESS;
}
}
diff --git a/app/code/Magento/Backend/Console/Command/CacheStatusCommand.php b/app/code/Magento/Backend/Console/Command/CacheStatusCommand.php
index c2d2c6fe49d90..ebb9681b2eeeb 100644
--- a/app/code/Magento/Backend/Console/Command/CacheStatusCommand.php
+++ b/app/code/Magento/Backend/Console/Command/CacheStatusCommand.php
@@ -6,6 +6,7 @@
namespace Magento\Backend\Console\Command;
+use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -18,7 +19,7 @@
class CacheStatusCommand extends AbstractCacheCommand
{
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function configure()
{
@@ -28,7 +29,7 @@ protected function configure()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -36,5 +37,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
foreach ($this->cacheManager->getStatus() as $cache => $status) {
$output->writeln(sprintf('%30s: %d', $cache, $status));
}
+
+ return Cli::RETURN_SUCCESS;
}
}
diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanImages.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanImages.php
index 79bc19256d270..849dc7854b57b 100644
--- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanImages.php
+++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanImages.php
@@ -17,10 +17,10 @@ class CleanImages extends \Magento\Backend\Controller\Adminhtml\Cache implements
*
* @see _isAllowed()
*/
- const ADMIN_RESOURCE = 'Magento_Backend::flush_catalog_images';
+ public const ADMIN_RESOURCE = 'Magento_Backend::flush_catalog_images';
/**
- * Clean JS/css files cache
+ * Clean image cache
*
* @return \Magento\Backend\Model\View\Result\Redirect
*/
diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Tunnel.php b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Tunnel.php
index 2e0ed47a97bbf..cbdd643c3a3f9 100644
--- a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Tunnel.php
+++ b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Tunnel.php
@@ -5,26 +5,35 @@
*/
namespace Magento\Backend\Controller\Adminhtml\Dashboard;
+use Exception;
+use Laminas\Http\Request;
use Magento\Backend\App\Action;
+use Magento\Backend\Block\Dashboard\Graph;
+use Magento\Backend\Controller\Adminhtml\Dashboard;
+use Magento\Backend\Helper\Dashboard\Data;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\Controller\Result;
+use Magento\Framework\Controller\Result\Raw;
+use Magento\Framework\Controller\Result\RawFactory;
use Magento\Framework\Encryption\Helper\Security;
+use Magento\Framework\HTTP\LaminasClient;
+use Psr\Log\LoggerInterface;
/**
* Dashboard graph image tunnel
* @deprecated dashboard graphs were migrated to dynamic chart.js solution
* @see dashboard.chart.amounts and dashboard.chart.orders in adminhtml_dashboard_index.xml
*/
-class Tunnel extends \Magento\Backend\Controller\Adminhtml\Dashboard implements HttpGetActionInterface
+class Tunnel extends Dashboard implements HttpGetActionInterface
{
/**
- * @var \Magento\Framework\Controller\Result\RawFactory
+ * @var RawFactory
*/
protected $resultRawFactory;
/**
* @param Action\Context $context
- * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory
+ * @param RawFactory $resultRawFactory
*/
public function __construct(
Action\Context $context,
@@ -39,7 +48,7 @@ public function __construct(
*
* This is done in order to include the image to a HTTPS-page regardless of web-service settings
*
- * @return \Magento\Framework\Controller\Result\Raw
+ * @return Raw
*/
public function execute()
{
@@ -47,11 +56,11 @@ public function execute()
$httpCode = 400;
$gaData = $this->_request->getParam('ga');
$gaHash = $this->_request->getParam('h');
- /** @var \Magento\Framework\Controller\Result\Raw $resultRaw */
+ /** @var Raw $resultRaw */
$resultRaw = $this->resultRawFactory->create();
if ($gaData && $gaHash) {
- /** @var $helper \Magento\Backend\Helper\Dashboard\Data */
- $helper = $this->_objectManager->get(\Magento\Backend\Helper\Dashboard\Data::class);
+ /** @var $helper Data */
+ $helper = $this->_objectManager->get(Data::class);
$newHash = $helper->getChartDataHash($gaData);
if (Security::compareStrings($newHash, $gaHash)) {
$params = null;
@@ -62,25 +71,19 @@ public function execute()
}
if ($params) {
try {
- /** @var $httpClient \Magento\Framework\HTTP\ZendClient */
- $httpClient = $this->_objectManager->create(\Magento\Framework\HTTP\ZendClient::class);
- $response = $httpClient->setUri(
- \Magento\Backend\Block\Dashboard\Graph::API_URL
- )->setParameterGet(
- $params
- )->setConfig(
- ['timeout' => 5]
- )->request(
- 'GET'
- );
-
- $headers = $response->getHeaders();
+ $httpClient = $this->_objectManager->create(LaminasClient::class);
+ $httpClient->setUri(Graph::API_URL);
+ $httpClient->setParameterGet($params);
+ $httpClient->setOptions(['timeout' => 5]);
+ $httpClient->setMethod(Request::METHOD_GET);
+ $response = $httpClient->send();
+ $headers = $response->getHeaders()->toArray();
$resultRaw->setHeader('Content-type', $headers['Content-type'])
->setContents($response->getBody());
return $resultRaw;
- } catch (\Exception $e) {
- $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e);
+ } catch (Exception $e) {
+ $this->_objectManager->get(LoggerInterface::class)->critical($e);
$error = __('see error log for details');
$httpCode = 503;
}
diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Save.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Save.php
index 25cfb61d658c3..8cec9e320025d 100644
--- a/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Save.php
+++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Save.php
@@ -7,6 +7,7 @@
namespace Magento\Backend\Controller\Adminhtml\System\Design;
use Magento\Framework\App\Action\HttpPostActionInterface;
+use Magento\Framework\Filter\FilterInput;
/**
* Save design action.
@@ -21,13 +22,13 @@ class Save extends \Magento\Backend\Controller\Adminhtml\System\Design implement
*/
protected function _filterPostData($data)
{
- $inputFilter = new \Zend_Filter_Input(
+ $inputFilter = new FilterInput(
['date_from' => $this->dateFilter, 'date_to' => $this->dateFilter],
[],
$data
);
- $data = $inputFilter->getUnescaped();
- return $data;
+
+ return $inputFilter->getUnescaped();
}
/**
diff --git a/app/code/Magento/Backend/Model/Auth/Session.php b/app/code/Magento/Backend/Model/Auth/Session.php
index 22360671ef2da..e65caf13f2ea3 100644
--- a/app/code/Magento/Backend/Model/Auth/Session.php
+++ b/app/code/Magento/Backend/Model/Auth/Session.php
@@ -18,8 +18,6 @@
* @api
* @method \Magento\User\Model\User|null getUser()
* @method \Magento\Backend\Model\Auth\Session setUser(\Magento\User\Model\User $value)
- * @method \Magento\Framework\Acl|null getAcl()
- * @method \Magento\Backend\Model\Auth\Session setAcl(\Magento\Framework\Acl $value)
* @method int getUpdatedAt()
* @method \Magento\Backend\Model\Auth\Session setUpdatedAt(int $value)
*
@@ -62,6 +60,11 @@ class Session extends \Magento\Framework\Session\SessionManager implements \Mage
*/
private $messageManager;
+ /**
+ * @var \Magento\Framework\Acl|null
+ */
+ private $acl = null;
+
/**
* @param \Magento\Framework\App\Request\Http $request
* @param \Magento\Framework\Session\SidResolverInterface $sidResolver
@@ -130,7 +133,7 @@ public function refreshAcl($user = null)
}
if ($user->getReloadAclFlag()) {
$user->unsetData('password');
- $user->setReloadAclFlag('0')->save();
+ $user->setReloadAclFlag(0)->save();
}
return $this;
}
@@ -152,7 +155,7 @@ public function isAllowed($resource, $privilege = null)
return $acl->isAllowed($user->getAclRole(), $resource, $privilege);
} catch (\Exception $e) {
try {
- if (!$acl->has($resource)) {
+ if (!$acl->hasResource($resource)) {
return $acl->isAllowed($user->getAclRole(), null, $privilege);
}
} catch (\Exception $e) {
@@ -284,4 +287,33 @@ public function isValidForPath($path)
{
return true;
}
+
+ /**
+ * Set Acl model
+ *
+ * @return \Magento\Framework\Acl
+ */
+ public function getAcl()
+ {
+ return $this->acl;
+ }
+
+ /**
+ * Retrieve Acl
+ *
+ * @param \Magento\Framework\Acl $acl
+ * @return void
+ */
+ public function setAcl(\Magento\Framework\Acl $acl)
+ {
+ $this->acl = $acl;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getData($key = '', $clear = false)
+ {
+ return $key === 'acl' ? $this->getAcl() : parent::getData($key, $clear);
+ }
}
diff --git a/app/code/Magento/Backend/Model/Dashboard/Chart/Date.php b/app/code/Magento/Backend/Model/Dashboard/Chart/Date.php
index a9dcef0ae07cb..2d1e5e977eaf0 100644
--- a/app/code/Magento/Backend/Model/Dashboard/Chart/Date.php
+++ b/app/code/Magento/Backend/Model/Dashboard/Chart/Date.php
@@ -7,7 +7,6 @@
namespace Magento\Backend\Model\Dashboard\Chart;
-use DateTimeZone;
use Magento\Backend\Model\Dashboard\Period;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\Reports\Model\ResourceModel\Order\CollectionFactory;
@@ -57,36 +56,38 @@ public function getByPeriod(string $period): array
);
$timezoneLocal = $this->localeDate->getConfigTimezone();
-
- $dateStart->setTimezone(new DateTimeZone($timezoneLocal));
- $dateEnd->setTimezone(new DateTimeZone($timezoneLocal));
+ $localStartDate = new \DateTime($dateStart->format('Y-m-d H:i:s'), new \DateTimeZone($timezoneLocal));
+ $localEndDate = new \DateTime($dateEnd->format('Y-m-d H:i:s'), new \DateTimeZone($timezoneLocal));
if ($period === Period::PERIOD_24_HOURS) {
- $dateEnd->modify('-1 hour');
+ $localEndDate = new \DateTime('now', new \DateTimeZone($timezoneLocal));
+ $localStartDate = clone $localEndDate;
+ $localStartDate->modify('-1 day');
+ $localStartDate->modify('+1 hour');
} elseif ($period === Period::PERIOD_TODAY) {
- $dateEnd->modify('now');
+ $localEndDate->modify('now');
} else {
- $dateEnd->setTime(23, 59, 59);
- $dateStart->setTime(0, 0, 0);
+ $localEndDate->setTime(23, 59, 59);
+ $localStartDate->setTime(0, 0, 0);
}
$dates = [];
- while ($dateStart <= $dateEnd) {
+ while ($localStartDate <= $localEndDate) {
switch ($period) {
case Period::PERIOD_7_DAYS:
case Period::PERIOD_1_MONTH:
- $d = $dateStart->format('Y-m-d');
- $dateStart->modify('+1 day');
+ $d = $localStartDate->format('Y-m-d');
+ $localStartDate->modify('+1 day');
break;
case Period::PERIOD_1_YEAR:
case Period::PERIOD_2_YEARS:
- $d = $dateStart->format('Y-m');
- $dateStart->modify('first day of next month');
+ $d = $localStartDate->format('Y-m');
+ $localStartDate->modify('first day of next month');
break;
default:
- $d = $dateStart->format('Y-m-d H:00');
- $dateStart->modify('+1 hour');
+ $d = $localStartDate->format('Y-m-d H:00');
+ $localStartDate->modify('+1 hour');
}
$dates[] = $d;
diff --git a/app/code/Magento/Backend/Model/Menu/Item/Validator.php b/app/code/Magento/Backend/Model/Menu/Item/Validator.php
index 62225c5707c0d..4b1ec400067fc 100644
--- a/app/code/Magento/Backend/Model/Menu/Item/Validator.php
+++ b/app/code/Magento/Backend/Model/Menu/Item/Validator.php
@@ -5,10 +5,11 @@
*/
namespace Magento\Backend\Model\Menu\Item;
+use Laminas\Validator\Regex;
+use Magento\Framework\Validator\StringLength;
+use Laminas\Validator\ValidatorChain;
+
/**
- * Class Validator
- *
- * @package Magento\Backend\Model\Menu\Item
* @api
* @since 100.0.2
*/
@@ -31,7 +32,7 @@ class Validator
/**
* The list of primitive validators
*
- * @var \Zend_Validate[]
+ * @var ValidatorChain[]
*/
protected $_validators = [];
@@ -40,21 +41,21 @@ class Validator
*/
public function __construct()
{
- $idValidator = new \Zend_Validate();
- $idValidator->addValidator(new \Zend_Validate_StringLength(['min' => 3]));
- $idValidator->addValidator(new \Zend_Validate_Regex('/^[A-Za-z0-9\/:_]+$/'));
+ $idValidator = new ValidatorChain();
+ $idValidator->addValidator(new StringLength(['min' => 3]));
+ $idValidator->addValidator(new Regex('/^[A-Za-z0-9\/:_]+$/'));
- $resourceValidator = new \Zend_Validate();
- $resourceValidator->addValidator(new \Zend_Validate_StringLength(['min' => 8]));
+ $resourceValidator = new ValidatorChain();
+ $resourceValidator->addValidator(new StringLength(['min' => 8]));
$resourceValidator->addValidator(
- new \Zend_Validate_Regex('/^[A-Z][A-Za-z0-9]+_[A-Z][A-Za-z0-9]+::[A-Za-z_0-9]+$/')
+ new Regex('/^[A-Z][A-Za-z0-9]+_[A-Z][A-Za-z0-9]+::[A-Za-z_0-9]+$/')
);
- $attributeValidator = new \Zend_Validate();
- $attributeValidator->addValidator(new \Zend_Validate_StringLength(['min' => 3]));
- $attributeValidator->addValidator(new \Zend_Validate_Regex('/^[A-Za-z0-9\/_\-]+$/'));
+ $attributeValidator = new ValidatorChain();
+ $attributeValidator->addValidator(new StringLength(['min' => 3]));
+ $attributeValidator->addValidator(new Regex('/^[A-Za-z0-9\/_\-]+$/'));
- $textValidator = new \Zend_Validate_StringLength(['min' => 3, 'max' => 50]);
+ $textValidator = new StringLength(['min' => 3, 'max' => 50]);
$titleValidator = $tooltipValidator = $textValidator;
$actionValidator = $moduleDepValidator = $configDepValidator = $attributeValidator;
diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminCheckDashboardWithChartsTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminCheckDashboardWithChartsTest.xml
index 0a08a409d2f2e..e0cbed316cf0b 100644
--- a/app/code/Magento/Backend/Test/Mftf/Test/AdminCheckDashboardWithChartsTest.xml
+++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminCheckDashboardWithChartsTest.xml
@@ -21,6 +21,7 @@
+
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/Test/Mftf/Test/AdminVerifyStateProvinceRequiredOnAddingNewAddressPageTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminVerifyStateProvinceRequiredOnAddingNewAddressPageTest.xml
new file mode 100644
index 0000000000000..c539f1db5411d
--- /dev/null
+++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminVerifyStateProvinceRequiredOnAddingNewAddressPageTest.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Renderer/CurrencyTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Renderer/CurrencyTest.php
index e0177fb9e6bee..a99cac2964f2b 100644
--- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Renderer/CurrencyTest.php
+++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/Column/Renderer/CurrencyTest.php
@@ -12,6 +12,7 @@
use Magento\Directory\Model\Currency\DefaultLocator;
use Magento\Directory\Model\CurrencyFactory;
use Magento\Framework\App\RequestInterface;
+use Magento\Framework\Currency\Data\Currency as CurrencyData;
use Magento\Framework\DataObject;
use Magento\Framework\Locale\CurrencyInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
@@ -123,7 +124,7 @@ public function testRenderWithDefaultCurrency()
->method('getDefaultCurrency')
->with($this->_requestMock)
->willReturn('defaultCurrency');
- $currLocaleMock = $this->createMock(\Zend_Currency::class);
+ $currLocaleMock = $this->createMock(CurrencyData::class);
$currLocaleMock->expects($this->once())
->method('toCurrency')
->with(15.0000)
diff --git a/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php b/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php
index 0b97f847099a5..38647d0a515ff 100644
--- a/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php
+++ b/app/code/Magento/Backend/Test/Unit/Model/Auth/SessionTest.php
@@ -79,7 +79,7 @@ protected function setUp(): void
['getCookie', 'setPublicCookie']
);
$this->storage = $this->getMockBuilder(Storage::class)
- ->addMethods(['getUser', 'getAcl', 'setAcl'])
+ ->addMethods(['getUser'])
->disableOriginalConstructor()
->getMock();
$this->sessionConfig = $this->createPartialMock(
@@ -133,8 +133,6 @@ public function testRefreshAcl($isUserPassedViaParams)
$userMock->expects($this->any())->method('getReloadAclFlag')->willReturn(true);
$userMock->expects($this->once())->method('setReloadAclFlag')->with('0')->willReturnSelf();
$userMock->expects($this->once())->method('save');
- $this->storage->expects($this->once())->method('setAcl')->with($aclMock);
- $this->storage->expects($this->any())->method('getAcl')->willReturn($aclMock);
if ($isUserPassedViaParams) {
$this->session->refreshAcl($userMock);
} else {
@@ -250,7 +248,7 @@ public function testIsAllowed($isUserDefined, $isAclDefined, $isAllowed, $expect
$aclMock = $this->getMockBuilder(Acl::class)
->disableOriginalConstructor()
->getMock();
- $this->storage->expects($this->any())->method('getAcl')->willReturn($aclMock);
+ $this->session->setAcl($aclMock);
}
if ($isUserDefined) {
$userMock = $this->getMockBuilder(User::class)
diff --git a/app/code/Magento/Backend/Test/Unit/Model/Dashboard/Chart/DateTest.php b/app/code/Magento/Backend/Test/Unit/Model/Dashboard/Chart/DateTest.php
new file mode 100644
index 0000000000000..2729e22fbc4a9
--- /dev/null
+++ b/app/code/Magento/Backend/Test/Unit/Model/Dashboard/Chart/DateTest.php
@@ -0,0 +1,270 @@
+collection = $this->getCollectionObject();
+ $this->collectionFactoryMock = $this->getMockBuilder(CollectionFactory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->collectionFactoryMock
+ ->expects($this->any())
+ ->method('create')
+ ->willReturn($this->collection);
+ $this->model = new Date($this->collectionFactoryMock, $this->timezoneMock);
+ }
+
+ /**
+ * @param string $period
+ * @param string $config
+ * @param int $expectedYear
+ *
+ * @return void
+ * @dataProvider getByPeriodDataProvider
+ */
+ public function testGetByPeriod($period, $config, $expectedYear): void
+ {
+ $this->scopeConfigMock
+ ->expects($this->once())
+ ->method('getValue')
+ ->with(
+ $config,
+ ScopeInterface::SCOPE_STORE
+ )
+ ->willReturn(1);
+ $dates = $this->model->getByPeriod($period);
+ $this->assertEquals($expectedYear, substr($dates[0], 0, 4));
+ }
+
+ /**
+ * @return array
+ */
+ public function getByPeriodDataProvider(): array
+ {
+ $dateStart = new \DateTime();
+ $expectedYear = $dateStart->format('Y');
+ $expected2YTDYear = $expectedYear - 1;
+
+ return [
+ [Period::PERIOD_1_YEAR, 'reports/dashboard/ytd_start', $expectedYear],
+ [Period::PERIOD_2_YEARS, 'reports/dashboard/ytd_start', $expected2YTDYear]
+ ];
+ }
+
+ /**
+ * @return Collection
+ */
+ private function getCollectionObject()
+ {
+ $this->entityFactoryMock = $this->getMockBuilder(EntityFactory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->loggerMock = $this->getMockBuilder(LoggerInterface::class)
+ ->getMock();
+ $this->fetchStrategyMock = $this->getMockBuilder(
+ FetchStrategyInterface::class
+ )->getMock();
+ $this->managerMock = $this->getMockBuilder(ManagerInterface::class)
+ ->getMock();
+ $snapshotClassName = Snapshot::class;
+ $this->entitySnapshotMock = $this->getMockBuilder($snapshotClassName)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->helperMock = $this->getMockBuilder(Helper::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class)
+ ->getMock();
+ $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class)
+ ->getMock();
+ $this->timezoneMock = $this->getMockBuilder(TimezoneInterface::class)
+ ->getMock();
+ $this->timezoneMock
+ ->expects($this->any())
+ ->method('getConfigTimezone')
+ ->willReturn('America/Chicago');
+ $this->configMock = $this->getMockBuilder(Config::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->orderFactoryMock = $this->getMockBuilder(OrderFactory::class)
+ ->onlyMethods(['create'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->selectMock = $this->getMockBuilder(Select::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->selectMock
+ ->expects($this->any())
+ ->method('columns')
+ ->willReturnSelf();
+ $this->selectMock
+ ->expects($this->any())
+ ->method('where')
+ ->willReturnSelf();
+ $this->selectMock
+ ->expects($this->any())
+ ->method('order')
+ ->willReturnSelf();
+ $this->selectMock
+ ->expects($this->any())
+ ->method('group')
+ ->willReturnSelf();
+ $this->selectMock
+ ->expects($this->any())
+ ->method('getPart')
+ ->willReturn([]);
+ $this->connectionMock = $this->getMockBuilder(Mysql::class)
+ ->onlyMethods(['select', 'getIfNullSql', 'getDateFormatSql', 'prepareSqlCondition', 'getCheckSql'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->connectionMock
+ ->expects($this->any())
+ ->method('select')
+ ->willReturn($this->selectMock);
+ $this->resourceMock = $this->getMockBuilder(AbstractDb::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->resourceMock
+ ->expects($this->once())
+ ->method('getConnection')
+ ->willReturn($this->connectionMock);
+ return new Collection(
+ $this->entityFactoryMock,
+ $this->loggerMock,
+ $this->fetchStrategyMock,
+ $this->managerMock,
+ $this->entitySnapshotMock,
+ $this->helperMock,
+ $this->scopeConfigMock,
+ $this->storeManagerMock,
+ $this->timezoneMock,
+ $this->configMock,
+ $this->orderFactoryMock,
+ null,
+ $this->resourceMock
+ );
+ }
+}
diff --git a/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/XsdTest.php b/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/XsdTest.php
index 8fb8929a553ed..6f93e640c5c54 100644
--- a/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/XsdTest.php
+++ b/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/XsdTest.php
@@ -42,7 +42,10 @@ protected function setUp(): void
public function testSchemaCorrectlyIdentifiesInvalidXml($xmlString, $expectedError)
{
$actualError = $this->_xsdValidator->validate($this->_xsdSchema, $xmlString);
- $this->assertEquals($expectedError, $actualError);
+ $this->assertEquals(false, empty($actualError));
+ foreach ($expectedError as $error) {
+ $this->assertContains($error, $actualError);
+ }
}
public function testSchemaCorrectlyIdentifiesValidXml()
diff --git a/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/_files/invalidMenuXmlArray.php b/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/_files/invalidMenuXmlArray.php
index b979fb451edbf..049284072ae81 100644
--- a/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/_files/invalidMenuXmlArray.php
+++ b/app/code/Magento/Backend/Test/Unit/Model/Menu/Config/_files/invalidMenuXmlArray.php
@@ -12,8 +12,7 @@
' resource="Test_Value::value"/>',
[
"Element 'add', attribute 'action': [facet 'pattern'] The value '' is not accepted by the " .
- "pattern '[a-zA-Z0-9/_\-]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'action': '' is not a valid value of the atomic type 'typeAction'.\nLine: 1\n"
+ "pattern '[a-zA-Z0-9/_\-]{3,}'.\nLine: 1\n"
],
],
'add_action_attribute_less_minLenght_value' => [
@@ -22,8 +21,7 @@
'resource="Test_Value::value"/>',
[
"Element 'add', attribute 'action': [facet 'pattern'] The value 'ad' is not accepted by the " .
- "pattern '[a-zA-Z0-9/_\-]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'action': 'ad' is not a valid value of the atomic type 'typeAction'.\nLine: 1\n"
+ "pattern '[a-zA-Z0-9/_\-]{3,}'.\nLine: 1\n"
],
],
'add_action_attribute_notallowed_symbols_value' => [
@@ -33,9 +31,7 @@
'',
[
"Element 'add', attribute 'action': [facet 'pattern'] The value 'adm$#@inhtml/notification' is not " .
- "accepted by the pattern '[a-zA-Z0-9/_\-]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'action': 'adm$#@inhtml/notification' is not a valid value of the atomic " .
- "type 'typeAction'.\nLine: 1\n"
+ "accepted by the pattern '[a-zA-Z0-9/_\-]{3,}'.\nLine: 1\n"
],
],
'add_dependsOnConfig_attribute_empty_value' => [
@@ -45,9 +41,7 @@
'',
[
"Element 'add', attribute 'dependsOnConfig': [facet 'pattern'] The value '' is not accepted by the " .
- "pattern '[A-Za-z0-9_/]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'dependsOnConfig': '' " .
- "is not a valid value of the atomic type 'typeDependsConfig'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9_/]{3,}'.\nLine: 1\n"
],
],
'add_dependsOnConfig_attribute_less_minLenght_value' => [
@@ -57,9 +51,7 @@
'',
[
"Element 'add', attribute 'dependsOnConfig': [facet 'pattern'] The value 'v' is not accepted by the " .
- "pattern '[A-Za-z0-9_/]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'dependsOnConfig': 'v' is not a valid value of the atomic " .
- "type 'typeDependsConfig'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9_/]{3,}'.\nLine: 1\n"
],
],
'add_dependsOnConfig_attribute_notallowed_symbols_value' => [
@@ -69,9 +61,7 @@
'',
[
"Element 'add', attribute 'dependsOnConfig': [facet 'pattern'] The value 'name#1' is not accepted by " .
- "the pattern '[A-Za-z0-9_/]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'dependsOnConfig': 'name#1' is not a valid value of the atomic " .
- "type 'typeDependsConfig'.\nLine: 1\n"
+ "the pattern '[A-Za-z0-9_/]{3,}'.\nLine: 1\n"
],
],
'add_dependsOnModule_attribute_empty_value' => [
@@ -81,9 +71,7 @@
'',
[
"Element 'add', attribute 'dependsOnModule': [facet 'pattern'] The value '' is not accepted by the " .
- "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'dependsOnModule': '' is not a valid value of the atomic type" .
- " 'typeModule'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n"
],
],
'add_dependsOnModule_attribute_less_minLenght_value' => [
@@ -93,9 +81,7 @@
'',
[
"Element 'add', attribute 'dependsOnModule': [facet 'pattern'] The value 'w' is not accepted by the " .
- "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'dependsOnModule': 'w' is not a valid value of the atomic type"
- . " 'typeModule'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n"
],
],
'add_dependsOnModule_attribute_notallowed_symbols_value' => [
@@ -105,9 +91,7 @@
'',
[
"Element 'add', attribute 'dependsOnModule': [facet 'pattern'] The value '@#erw' is not " .
- "accepted by the pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'dependsOnModule': '@#erw' is not a valid value of the atomic " .
- "type 'typeModule'.\nLine: 1\n"
+ "accepted by the pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n"
],
],
'add_id_attribute_empty_value' => [
@@ -115,10 +99,7 @@
'resource="Test_Value::value"/>',
[
"Element 'add', attribute 'id': [facet 'pattern'] The value '' is not accepted by the " .
- "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'id': '' is not a valid value of the atomic type 'typeId'.\nLine: 1\n",
- "Element 'add', attribute 'id': Warning: No precomputed value available, the value was either invalid or " .
- "something strange happend.\nLine: 1\n"
+ "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'add_id_attribute_less_minLenght_value' => [
@@ -126,10 +107,7 @@
'resource="Test_Value::value"/>',
[
"Element 'add', attribute 'id': [facet 'pattern'] The value 'ma' is not accepted by the " .
- "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'id': 'ma' is not a valid value of the atomic type 'typeId'.\nLine: 1\n",
- "Element 'add', attribute 'id': Warning: No precomputed value available, the value was either invalid or " .
- "something strange happend.\nLine: 1\n"
+ "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'add_id_attribute_notallowed_symbols_value' => [
@@ -138,11 +116,7 @@
'resource="Test_Value::value"/>',
[
"Element 'add', attribute 'id': [facet 'pattern'] The value 'Magento)value::some_value' is not " .
- "accepted by the pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'id': 'Magento)value::some_value' " .
- "is not a valid value of the atomic type 'typeId'.\nLine: 1\n",
- "Element 'add', attribute 'id': Warning: No precomputed value available, the value was either invalid or " .
- "something strange happend.\nLine: 1\n"
+ "accepted by the pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'add_module_attribute_empty_value' => [
@@ -150,8 +124,7 @@
'title="Notifications" resource="Test_Value::value"/>',
[
"Element 'add', attribute 'module': [facet 'pattern'] The value '' is not accepted by the " .
- "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'module': '' is not a valid value of the atomic type 'typeModule'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n"
],
],
'add_module_attribute_less_minLenght_value' => [
@@ -160,8 +133,7 @@
'resource="Test_Value::value"/>',
[
"Element 'add', attribute 'module': [facet 'pattern'] The value 'we' is not accepted by the " .
- "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'module': 'we' is not a valid value of the atomic type 'typeModule'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n"
],
],
'add_module_attribute_notallowed_symbols_value' => [
@@ -170,9 +142,7 @@
'resource="Test_Value::value"/>',
[
"Element 'add', attribute 'module': [facet 'pattern'] The value 'Test_Va%lue' is not accepted by the " .
- "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'module': 'Test_Va%lue' is not a valid value of the atomic type"
- . " 'typeModule'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n"
],
],
'add_parent_attribute_empty_value' => [
@@ -181,8 +151,7 @@
'resource="Test_Value::value"/>',
[
"Element 'add', attribute 'parent': [facet 'pattern'] The value '' is not accepted by the " .
- "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'parent': '' is not a valid value of the atomic type 'typeId'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'add_parent_attribute_less_minLenght_value' => [
@@ -191,8 +160,7 @@
'resource="Test_Value::value"/>',
[
"Element 'add', attribute 'parent': [facet 'pattern'] The value 'Ma' is not accepted by the " .
- "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'parent': 'Ma' is not a valid value of the atomic type 'typeId'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'add_parent_attribute_notallowed_symbols_value' => [
@@ -202,10 +170,7 @@
'',
[
"Element 'add', attribute 'parent': [facet 'pattern'] The value 'Some#Name::system_other_settings' " .
- "is not accepted by the pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'add', attribute 'parent': 'Some#Name::system_other_settings' " .
- "is not a valid value of the atomic " .
- "type 'typeId'.\nLine: 1\n"
+ "is not accepted by the pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'add_resource_attribute_notvalid_regexp_value1' => [
@@ -214,9 +179,7 @@
'resource="test_Value::value"/>',
[
"Element 'add', attribute 'resource': [facet 'pattern'] The value 'test_Value::value' is not " .
- "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'add', attribute 'resource': 'test_Value::value' is not a valid value of the atomic " .
- "type 'typeResource'.\nLine: 1\n"
+ "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'add_resource_attribute_notvalid_regexp_value2' => [
@@ -225,9 +188,7 @@
'resource="Test_value::value"/>',
[
"Element 'add', attribute 'resource': [facet 'pattern'] The value 'Test_value::value' is not " .
- "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'add', attribute 'resource': 'Test_value::value' is not a valid value of the atomic " .
- "type 'typeResource'.\nLine: 1\n"
+ "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'add_resource_attribute_notvalid_regexp_value3' => [
@@ -236,9 +197,7 @@
'resource="M#$%23_value::value"/>',
[
"Element 'add', attribute 'resource': [facet 'pattern'] The value 'M#$%23_value::value' is not " .
- "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'add', attribute 'resource': 'M#$%23_value::value' is not a valid value of the atomic " .
- "type 'typeResource'.\nLine: 1\n"
+ "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'add_resource_attribute_notvalid_regexp_value4' => [
@@ -247,9 +206,7 @@
'resource="_value::value"/>',
[
"Element 'add', attribute 'resource': [facet 'pattern'] The value '_value::value' is not accepted by " .
- "the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'add', attribute 'resource': '_value::value' is not a valid value of the atomic " .
- "type 'typeResource'.\nLine: 1\n"
+ "the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'add_resource_attribute_notvalid_regexp_value5' => [
@@ -258,9 +215,7 @@
'',
[
"Element 'add', attribute 'resource': [facet 'pattern'] The value 'Magento_::value' is not " .
- "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'add', attribute 'resource': 'Magento_::value' is not a valid value of the atomic " .
- "type 'typeResource'.\nLine: 1\n"
+ "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'add_resource_attribute_notvalid_regexp_value6' => [
@@ -269,9 +224,7 @@
'resource="Test_Value:value"/>',
[
"Element 'add', attribute 'resource': [facet 'pattern'] The value 'Test_Value:value' is not " .
- "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'add', attribute 'resource': 'Test_Value:value' is not a valid value of the atomic " .
- "type 'typeResource'.\nLine: 1\n"
+ "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'add_resource_attribute_notvalid_regexp_value7' => [
@@ -280,9 +233,7 @@
'resource="Test_Value::"/>',
[
"Element 'add', attribute 'resource': [facet 'pattern'] The value 'Test_Value::' is not " .
- "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'add', attribute 'resource': 'Test_Value::' " .
- "is not a valid value of the atomic type 'typeResource'.\nLine: 1\n"
+ "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'add_sortOrder_attribute_empty_value' => [
@@ -307,8 +258,7 @@
'',
[
"Element 'add', attribute 'title': [facet 'minLength'] The value '' has a length of '0'; this " .
- "underruns the allowed minimum length of '3'.\nLine: 1\n",
- "Element 'add', attribute 'title': '' is not a valid value of the atomic type 'typeTitle'.\nLine: 1\n"
+ "underruns the allowed minimum length of '3'.\nLine: 1\n"
],
],
'add_title_attribute_less_minLenght_value' => [
@@ -317,8 +267,7 @@
'',
[
"Element 'add', attribute 'title': [facet 'minLength'] The value 'No' has a length of '2'; this " .
- "underruns the allowed minimum length of '3'.\nLine: 1\n",
- "Element 'add', attribute 'title': 'No' is not a valid value of the atomic type 'typeTitle'.\nLine: 1\n"
+ "underruns the allowed minimum length of '3'.\nLine: 1\n"
],
],
'add_title_attribute_more_maxLenght_value' => [
@@ -328,9 +277,7 @@
[
"Element 'add', attribute 'title': [facet 'maxLength'] The value 'Lorem ipsum dolor sit amet, " .
"consectetur adipisicing' has a length of '51'; this exceeds the allowed maximum length" .
- " of '50'.\nLine: 1\n",
- "Element 'add', attribute 'title': 'Lorem ipsum dolor sit amet, consectetur adipisicing' is not a " .
- "valid value of the atomic type 'typeTitle'.\nLine: 1\n"
+ " of '50'.\nLine: 1\n"
],
],
'add_toolTip_attribute_empty_value' => [
@@ -339,8 +286,7 @@
'resource="Test_Value::value"/>',
[
"Element 'add', attribute 'toolTip': [facet 'minLength'] The value '' has a length of '0'; this " .
- "underruns the allowed minimum length of '3'.\nLine: 1\n",
- "Element 'add', attribute 'toolTip': '' is not a valid value of the atomic type 'typeTitle'.\nLine: 1\n"
+ "underruns the allowed minimum length of '3'.\nLine: 1\n"
],
],
'add_toolTip_attribute_less_minLenght_value' => [
@@ -349,8 +295,7 @@
'resource="Test_Value::value"/>',
[
"Element 'add', attribute 'toolTip': [facet 'minLength'] The value 'st' has a length of '2'; this " .
- "underruns the allowed minimum length of '3'.\nLine: 1\n",
- "Element 'add', attribute 'toolTip': 'st' is not a valid value of the atomic type 'typeTitle'.\nLine: 1\n"
+ "underruns the allowed minimum length of '3'.\nLine: 1\n"
],
],
'add_toolTip_attribute_more_maxLenght_value' => [
@@ -361,9 +306,7 @@
[
"Element 'add', attribute 'toolTip': [facet 'maxLength'] The value 'Lorem ipsum dolor sit amet, " .
"consectetur adipisicing' has a length of '51'; this exceeds the allowed maximum length" .
- " of '50'.\nLine: 1\n",
- "Element 'add', attribute 'toolTip': 'Lorem ipsum dolor sit amet, consectetur adipisicing' is not a " .
- "valid value of the atomic type 'typeTitle'.\nLine: 1\n"
+ " of '50'.\nLine: 1\n"
],
],
'add_with_notallowed_atrribute' => [
@@ -419,26 +362,21 @@
' ',
[
"Element 'remove', attribute 'id': [facet 'pattern'] The value '' is not accepted by the " .
- "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'remove', attribute 'id': '' is not a valid value of the atomic type 'typeId'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'remove_id_attribute_less_minLenght_value' => [
' ',
[
"Element 'remove', attribute 'id': [facet 'pattern'] The value 'Test_Value::system_%currency' is not " .
- "accepted by the pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'remove', attribute 'id': 'Test_Value::system_%currency' is not a valid value of the " .
- "atomic type 'typeId'.\nLine: 1\n"
+ "accepted by the pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'remove_id_attribute_notallowed_symbols_value' => [
' ',
[
"Element 'remove', attribute 'id': [facet 'pattern'] The value 'Test_Value::system#currency' is not " .
- "accepted by the pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'remove', attribute 'id': 'Test_Value::system#currency' is not a valid value of the " .
- "atomic type 'typeId'.\nLine: 1\n"
+ "accepted by the pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'remove_with_notallowed_atrribute' => [
@@ -454,8 +392,7 @@
' ',
[
"Element 'update', attribute 'action': [facet 'pattern'] The value '' is not accepted by the " .
- "pattern '[a-zA-Z0-9/_\-]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'action': '' is not a valid value of the atomic type 'typeAction'.\nLine: 1\n"
+ "pattern '[a-zA-Z0-9/_\-]{3,}'.\nLine: 1\n"
],
],
'update_action_attribute_less_minLenght_value' => [
@@ -464,8 +401,7 @@
'resource="Test_Value::value"/>',
[
"Element 'update', attribute 'action': [facet 'pattern'] The value 'v' is not accepted by the " .
- "pattern '[a-zA-Z0-9/_\-]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'action': 'v' is not a valid value of the atomic type 'typeAction'.\nLine: 1\n"
+ "pattern '[a-zA-Z0-9/_\-]{3,}'.\nLine: 1\n"
],
],
'update_action_attribute_notallowed_symbols_value' => [
@@ -473,9 +409,7 @@
'id="Test_Value::some_value"/>',
[
"Element 'update', attribute 'action': [facet 'pattern'] The value '/@##gt;' is not " .
- "accepted by the pattern '[a-zA-Z0-9/_\-]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'action': '/@##gt;' is not a valid value of the atomic" .
- " type 'typeAction'.\nLine: 1\n"
+ "accepted by the pattern '[a-zA-Z0-9/_\-]{3,}'.\nLine: 1\n"
],
],
'update_dependsOnConfig_attribute_empty_value' => [
@@ -483,9 +417,7 @@
'',
[
"Element 'update', attribute 'dependsOnConfig': [facet 'pattern'] The value '' is not accepted by the " .
- "pattern '[A-Za-z0-9_/]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'dependsOnConfig': '' is not a valid value of the atomic " .
- "type 'typeDependsConfig'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9_/]{3,}'.\nLine: 1\n"
],
],
'update_dependsOnConfig_attribute_less_minLenght_value' => [
@@ -493,9 +425,7 @@
'dependsOnConfig="we"/>',
[
"Element 'update', attribute 'dependsOnConfig': [facet 'pattern'] The value 'we' is not accepted by " .
- "the pattern '[A-Za-z0-9_/]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'dependsOnConfig': 'we' is not a valid value of the atomic " .
- "type 'typeDependsConfig'.\nLine: 1\n"
+ "the pattern '[A-Za-z0-9_/]{3,}'.\nLine: 1\n"
],
],
'update_dependsOnConfig_attribute_notallowed_symbols_value' => [
@@ -503,9 +433,7 @@
'',
[
"Element 'update', attribute 'dependsOnConfig': [facet 'pattern'] The value 'someconf%' is not " .
- "accepted by the pattern '[A-Za-z0-9_/]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'dependsOnConfig': 'someconf%' is not a valid value of the atomic " .
- "type 'typeDependsConfig'.\nLine: 1\n"
+ "accepted by the pattern '[A-Za-z0-9_/]{3,}'.\nLine: 1\n"
],
],
'update_dependsOnModule_attribute_empty_value' => [
@@ -513,9 +441,7 @@
'dependsOnModule=""/>',
[
"Element 'update', attribute 'dependsOnModule': [facet 'pattern'] The value '' is not accepted by " .
- "the pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'dependsOnModule': '' is not a valid value of the atomic" .
- " type 'typeModule'.\nLine: 1\n"
+ "the pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n"
],
],
'update_dependsOnModule_attribute_less_minLenght_value' => [
@@ -523,9 +449,7 @@
'dependsOnModule="qw"/>',
[
"Element 'update', attribute 'dependsOnModule': [facet 'pattern'] The value 'qw' is not accepted " .
- "by the pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'dependsOnModule': 'qw' is not a valid value of the atomic" .
- " type 'typeModule'.\nLine: 1\n"
+ "by the pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n"
],
],
'update_dependsOnModule_attribute_notallowed_symbols_value' => [
@@ -533,77 +457,63 @@
'dependsOnModule="someModule#1"/>',
[
"Element 'update', attribute 'dependsOnModule': [facet 'pattern'] The value 'someModule#1' is not " .
- "accepted by the pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'dependsOnModule': 'someModule#1' is not a valid value of the atomic " .
- "type 'typeModule'.\nLine: 1\n"
+ "accepted by the pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n"
],
],
'update_id_attribute_empty_value' => [
' ',
[
"Element 'update', attribute 'id': [facet 'pattern'] The value '' is not accepted by the " .
- "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'id': '' is not a valid value of the atomic type 'typeId'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'update_id_attribute_less_minLenght_value' => [
' ',
[
"Element 'update', attribute 'id': [facet 'pattern'] The value 'g' is not accepted by the " .
- "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'id': 'g' is not a valid value of the atomic type 'typeId'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'update_id_attribute_notallowed_symbols_value' => [
' ' . ' ',
[
"Element 'update', attribute 'id': [facet 'pattern'] The value 'Magento+value::some_value' is not " .
- "accepted by the pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'id': 'Magento+value::some_value' is not a valid value of the atomic " .
- "type 'typeId'.\nLine: 1\n"
+ "accepted by the pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'update_module_attribute_empty_value' => [
' ',
[
"Element 'update', attribute 'module': [facet 'pattern'] The value '' is not accepted by the " .
- "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'module': '' is not a valid value of the atomic type 'typeModule'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n"
],
],
'update_module_attribute_less_minLenght_value' => [
' ',
[
"Element 'update', attribute 'module': [facet 'pattern'] The value 'we' is not accepted by the " .
- "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'module': 'we' is not a valid value of the atomic" .
- " type 'typeModule'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n"
],
],
'update_module_attribute_notallowed_symbols_value' => [
' ',
[
"Element 'update', attribute 'module': [facet 'pattern'] The value '@#$' is not accepted by the " .
- "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'module': '@#$' is not a valid value of the atomic" .
- " type 'typeModule'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9_]{3,}'.\nLine: 1\n"
],
],
'update_parent_attribute_empty_value' => [
' ',
[
"Element 'update', attribute 'parent': [facet 'pattern'] The value '' is not accepted by the " .
- "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'parent': '' is not a valid value of the atomic" .
- " type 'typeId'.\nLine: 1\n"
+ "pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'update_parent_attribute_less_minLenght_value' => [
' ',
[
"Element 'update', attribute 'parent': [facet 'pattern'] The value 'fg' is not accepted by " .
- "the pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'parent': 'fg' is not a valid value of the atomic type 'typeId'.\nLine: 1\n"
+ "the pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'update_parent_attribute_notallowed_symbols_value' => [
@@ -611,9 +521,7 @@
'id="Test_Value::some_value"/>',
[
"Element 'update', attribute 'parent': [facet 'pattern'] The value " .
- "'Test_Value::system_other%settings' is not accepted by the pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n",
- "Element 'update', attribute 'parent': 'Test_Value::system_other%settings' is not a valid value of the " .
- "atomic type 'typeId'.\nLine: 1\n"
+ "'Test_Value::system_other%settings' is not accepted by the pattern '[A-Za-z0-9/_:]{3,}'.\nLine: 1\n"
],
],
'update_resource_attribute_notvalid_regexp_value1' => [
@@ -621,9 +529,7 @@
'resource="test_Value::value"/>',
[
"Element 'update', attribute 'resource': [facet 'pattern'] The value 'test_Value::value' is not " .
- "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'update', attribute 'resource': 'test_Value::value' is not a valid value of the atomic " .
- "type 'typeResource'.\nLine: 1\n"
+ "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'update_resource_attribute_notvalid_regexp_value2' => [
@@ -631,9 +537,7 @@
'resource="Test_value::value"/>',
[
"Element 'update', attribute 'resource': [facet 'pattern'] The value 'Test_value::value' is not " .
- "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'update', attribute 'resource': 'Test_value::value' is not a valid value of the atomic " .
- "type 'typeResource'.\nLine: 1\n"
+ "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'update_resource_attribute_notvalid_regexp_value3' => [
@@ -641,9 +545,7 @@
'resource="M#$%23_value::value"/>',
[
"Element 'update', attribute 'resource': [facet 'pattern'] The value 'M#$%23_value::value' is not " .
- "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'update', attribute 'resource': 'M#$%23_value::value' is not a valid value of the atomic " .
- "type 'typeResource'.\nLine: 1\n"
+ "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'update_resource_attribute_notvalid_regexp_value4' => [
@@ -651,9 +553,7 @@
'resource="_value::value"/>',
[
"Element 'update', attribute 'resource': [facet 'pattern'] The value '_value::value' is not " .
- "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'update', attribute 'resource': '_value::value' is not a valid value of the atomic " .
- "type 'typeResource'.\nLine: 1\n"
+ "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'update_resource_attribute_notvalid_regexp_value5' => [
@@ -661,9 +561,7 @@
'resource="Magento_::value"/>',
[
"Element 'update', attribute 'resource': [facet 'pattern'] The value 'Magento_::value' is not " .
- "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'update', attribute 'resource': 'Magento_::value' is not a valid value of the atomic " .
- "type 'typeResource'.\nLine: 1\n"
+ "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'update_resource_attribute_notvalid_regexp_value6' => [
@@ -671,9 +569,7 @@
'resource="Test_Value:value"/>',
[
"Element 'update', attribute 'resource': [facet 'pattern'] The value 'Test_Value:value' is not " .
- "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'update', attribute 'resource': 'Test_Value:value' is not a valid value of the atomic " .
- "type 'typeResource'.\nLine: 1\n"
+ "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'update_resource_attribute_notvalid_regexp_value7' => [
@@ -681,9 +577,7 @@
'resource="Test_Value::"/>',
[
"Element 'update', attribute 'resource': [facet 'pattern'] The value 'Test_Value::' is not " .
- "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'update', attribute 'resource': 'Test_Value::' is not a valid value of the atomic " .
- "type 'typeResource'.\nLine: 1\n"
+ "accepted by the pattern '[A-Z]+[A-Za-z0-9]{1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'update_sortOrder_attribute_empty_value' => [
@@ -701,16 +595,14 @@
' ',
[
"Element 'update', attribute 'title': [facet 'minLength'] The value '' has a length of '0'; this " .
- "underruns the allowed minimum length of '3'.\nLine: 1\n",
- "Element 'update', attribute 'title': '' is not a valid value of the atomic type 'typeTitle'.\nLine: 1\n"
+ "underruns the allowed minimum length of '3'.\nLine: 1\n"
],
],
'update_title_attribute_less_minLenght_value' => [
' ',
[
"Element 'update', attribute 'title': [facet 'minLength'] The value 'am' has a length of '2'; this " .
- "underruns the allowed minimum length of '3'.\nLine: 1\n",
- "Element 'update', attribute 'title': 'am' is not a valid value of the atomic type 'typeTitle'.\nLine: 1\n"
+ "underruns the allowed minimum length of '3'.\nLine: 1\n"
],
],
'update_title_attribute_more_maxLenght_value' => [
@@ -719,27 +611,21 @@
[
"Element 'update', attribute 'title': [facet 'maxLength'] The value 'Lorem ipsum dolor sit amet, " .
"consectetur adipisicing' has a length of '51'; this exceeds the allowed maximum" .
- " length of '50'.\nLine: 1\n",
- "Element 'update', attribute 'title': 'Lorem ipsum dolor sit amet, " .
- "consectetur adipisicing' is not a valid " .
- "value of the atomic type 'typeTitle'.\nLine: 1\n"
+ " length of '50'.\nLine: 1\n"
],
],
'update_toolTip_attribute_empty_value ' => [
' ',
[
"Element 'update', attribute 'toolTip': [facet 'minLength'] The value '' has a length of '0'; this " .
- "underruns the allowed minimum length of '3'.\nLine: 1\n",
- "Element 'update', attribute 'toolTip': '' is not a valid value of the atomic type 'typeTitle'.\nLine: 1\n"
+ "underruns the allowed minimum length of '3'.\nLine: 1\n"
],
],
'update_toolTip_attribute_less_minLenght_value' => [
' ',
[
"Element 'update', attribute 'toolTip': [facet 'minLength'] The value 'we' has a length of '2'; this " .
- "underruns the allowed minimum length of '3'.\nLine: 1\n",
- "Element 'update', attribute 'toolTip': 'we' is not a valid value of the atomic" .
- " type 'typeTitle'.\nLine: 1\n"
+ "underruns the allowed minimum length of '3'.\nLine: 1\n"
],
],
'update_toolTip_attribute_more_maxLenght_value' => [
@@ -748,9 +634,7 @@
[
"Element 'update', attribute 'toolTip': [facet 'maxLength'] The value 'Lorem ipsum dolor sit " .
"amet, consectetur adipisicing' has a length of '51'; this exceeds the allowed maximum" .
- " length of '50'.\nLine: 1\n",
- "Element 'update', attribute 'toolTip': 'Lorem ipsum dolor sit amet, consectetur adipisicing' " .
- "is not a valid value of the atomic type 'typeTitle'.\nLine: 1\n"
+ " length of '50'.\nLine: 1\n"
],
],
'update_with_notallowed_atrribute' => [
diff --git a/app/code/Magento/Backend/ViewModel/LimitTotalNumberOfProductsInGrid.php b/app/code/Magento/Backend/ViewModel/LimitTotalNumberOfProductsInGrid.php
new file mode 100644
index 0000000000000..4213910e2002e
--- /dev/null
+++ b/app/code/Magento/Backend/ViewModel/LimitTotalNumberOfProductsInGrid.php
@@ -0,0 +1,51 @@
+scopeConfig = $scopeConfig;
+ }
+
+ /**
+ * Check if configuration setting to limit total number of products in grid is enabled.
+ *
+ * @return bool
+ */
+ public function limitTotalNumberOfProducts(): bool
+ {
+ return (bool)$this->scopeConfig->getValue('admin/grid/limit_total_number_of_products');
+ }
+
+ /**
+ * Get records threshold for limit total number of products in collection.
+ *
+ * @return int
+ */
+ public function getRecordsLimit(): int
+ {
+ return (int)$this->scopeConfig->getValue('admin/grid/records_limit');
+ }
+}
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/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml
index 57cc36da95cfe..463976b58212f 100644
--- a/app/code/Magento/Backend/etc/adminhtml/system.xml
+++ b/app/code/Magento/Backend/etc/adminhtml/system.xml
@@ -317,14 +317,18 @@
Disable Email Communications
Magento\Config\Model\Config\Source\Yesno
-
+
+ Transport
+ Magento\Email\Model\Config\Source\SmtpTransportType
+
+
Host
- For Windows server only.
+ For SMTP and Windows server only.
-
+
Port (25)
validate-digits validate-digits-range digits-range-0-65535
- Please enter at least 0 and at most 65535 (For Windows server only).
+ Please enter at least 0 and at most 65535 (For SMTP and Windows server only).
Set Return-Path
@@ -338,6 +342,34 @@
2
+
+ Username
+ Username
+
+ smtp
+
+
+
+ Password
+ Username
+
+ smtp
+
+
+
+ Auth
+ Magento\Email\Model\Config\Source\SmtpAuthType
+
+ smtp
+
+
+
+ SSL
+ Magento\Email\Model\Config\Source\SmtpSslType
+
+ smtp
+
+
Images Upload Configuration
@@ -448,6 +480,23 @@
Magento\Config\Model\Config\Source\Yesno
+
+ Admin Grids
+
+ Limit Number of Products in Grid
+ Magento\Config\Model\Config\Source\Yesno
+ Limit total number of products in grid collection.
+
+
+ Records Limit
+ validate-digits validate-digits-range digits-range-20000-
+ Limit total number of products in grid collection if their number is greater than this value. Minimum value: 20000.
+
+ 1
+
+
+
Web
diff --git a/app/code/Magento/Backend/etc/config.xml b/app/code/Magento/Backend/etc/config.xml
index 1a4a2d2f748b7..f683b1dc81c90 100644
--- a/app/code/Magento/Backend/etc/config.xml
+++ b/app/code/Magento/Backend/etc/config.xml
@@ -31,6 +31,12 @@
1200
+
+
+ 0
+ 20000
+
+
diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml
index 9af7285e7da50..1c395aeaae345 100644
--- a/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml
+++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/grid/extended.phtml
@@ -3,6 +3,9 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+use Magento\Catalog\Ui\DataProvider\Product\ProductCollection;
+
?>
getColumns());
+$viewModel = $block->getViewModel();
?>
getCollection()): ?>
@@ -68,11 +73,15 @@ $numColumns = count($block->getColumns());
getCollection()->getSize(); ?>
+ getCollection() instanceof ProductCollection
+ || !$viewModel->limitTotalNumberOfProducts()
+ || $countRecords < $viewModel->getRecordsLimit()): ?>
getUiId('total-count') ?>>
= /* @noEscape */ $countRecords ?>
- = $block->escapeHtml(__('records found')) ?>
+ = $block->escapeHtml(__('records found')) ?>
+
0
@@ -124,23 +133,27 @@ $numColumns = count($block->getColumns());
= $block->escapeHtml(__('Previous page')) ?>
- getUiId('current-page') ?> />
- = /* @noEscape */ $secureRenderer->renderEventListenerAsTag(
- 'onkeypress',
- /* @noEscape */ $block->getJsObjectName() . '.inputPage(event, \'' .
- /* @noEscape */ $_lastPage . '\')',
- '#' . $block->escapeHtml($block->getHtmlId()) . '_page-current'
- ) ?>
-
- = /* @noEscape */ __('of %1', '' .
- $block->getCollection()->getLastPageNumber() . ' ') ?>
-
+ getCollection() instanceof ProductCollection
+ || !$viewModel->limitTotalNumberOfProducts()
+ || $countRecords < $viewModel->getRecordsLimit()): ?>
+ getUiId('current-page') ?> />
+ = /* @noEscape */ $secureRenderer->renderEventListenerAsTag(
+ 'onkeypress',
+ /* @noEscape */ $block->getJsObjectName() . '.inputPage(event, \'' .
+ /* @noEscape */ $_lastPage . '\')',
+ '#' . $block->escapeHtml($block->getHtmlId()) . '_page-current'
+ ) ?>
+
+ = /* @noEscape */ __('of %1', '' .
+ $block->getCollection()->getLastPageNumber() . ' ') ?>
+
+
isShipmentSeparately($item)) {
$attributes = $this->getSelectionAttributes($item);
if ($attributes) {
- $result = sprintf('%d', $attributes['qty']) . ' x ' . $result;
+ $result = (float) $attributes['qty'] . ' x ' . $result;
}
}
if (!$this->isChildCalculated($item)) {
diff --git a/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/View/Items/Renderer.php b/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/View/Items/Renderer.php
index 480395767326b..c194e81a44229 100644
--- a/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/View/Items/Renderer.php
+++ b/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/View/Items/Renderer.php
@@ -5,10 +5,10 @@
*/
namespace Magento\Bundle\Block\Adminhtml\Sales\Order\View\Items;
+use Magento\Catalog\Helper\Data as CatalogHelper;
use Magento\Catalog\Model\Product\Type\AbstractType;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Serialize\Serializer\Json;
-use Magento\Catalog\Helper\Data as CatalogHelper;
/**
* Adminhtml sales order item renderer
@@ -203,7 +203,7 @@ public function getValueHtml($item)
if (!$this->isShipmentSeparately($item)) {
$attributes = $this->getSelectionAttributes($item);
if ($attributes) {
- $result = sprintf('%d', $attributes['qty']) . ' x ' . $result;
+ $result = (float) $attributes['qty'] . ' x ' . $result;
}
}
if (!$this->isChildCalculated($item)) {
diff --git a/app/code/Magento/Bundle/Block/Sales/Order/Items/Renderer.php b/app/code/Magento/Bundle/Block/Sales/Order/Items/Renderer.php
index 82d978c40b45c..61728bf6cb91f 100644
--- a/app/code/Magento/Bundle/Block/Sales/Order/Items/Renderer.php
+++ b/app/code/Magento/Bundle/Block/Sales/Order/Items/Renderer.php
@@ -150,7 +150,7 @@ public function getSelectionAttributes($item)
public function getValueHtml($item)
{
if ($attributes = $this->getSelectionAttributes($item)) {
- return sprintf('%d', $attributes['qty']) . ' x ' . $this->escapeHtml($item->getName()) . " "
+ return (float) $attributes['qty'] . ' x ' . $this->escapeHtml($item->getName()) . " "
. $this->getOrder()->formatPrice($attributes['price']);
}
return $this->escapeHtml($item->getName());
diff --git a/app/code/Magento/Bundle/Model/Option/SaveAction.php b/app/code/Magento/Bundle/Model/Option/SaveAction.php
index 42349f2f2cc2e..0fe0f7d97ea07 100644
--- a/app/code/Magento/Bundle/Model/Option/SaveAction.php
+++ b/app/code/Magento/Bundle/Model/Option/SaveAction.php
@@ -50,6 +50,7 @@ class SaveAction
* @param Type $type
* @param ProductLinkManagementInterface $linkManagement
* @param StoreManagerInterface|null $storeManager
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct(
Option $optionResource,
@@ -84,11 +85,10 @@ public function save(ProductInterface $bundleProduct, OptionInterface $option)
$optionId = $option->getOptionId();
$linksToAdd = [];
$optionCollection = $this->type->getOptionsCollection($bundleProduct);
- $optionCollection->setIdFilter($option->getOptionId());
- $optionCollection->setProductLinkFilter($parentId);
/** @var \Magento\Bundle\Model\Option $existingOption */
- $existingOption = $optionCollection->getFirstItem();
+ $existingOption = $optionCollection->getItemById($option->getOptionId())
+ ?? $optionCollection->getNewEmptyItem();
if (!$optionId || $existingOption->getParentId() != $parentId) {
//If option ID is empty or existing option's parent ID is different
//we'd need a new ID for the option.
diff --git a/app/code/Magento/Bundle/Model/Option/Validator.php b/app/code/Magento/Bundle/Model/Option/Validator.php
index a67ce7dc3c9ed..22c9fc05d7592 100644
--- a/app/code/Magento/Bundle/Model/Option/Validator.php
+++ b/app/code/Magento/Bundle/Model/Option/Validator.php
@@ -8,7 +8,7 @@
use Magento\Framework\Validator\NotEmpty;
use Magento\Framework\Validator\NotEmptyFactory;
-use Zend_Validate_Exception;
+use Magento\Framework\Validator\ValidateException;
class Validator extends \Magento\Framework\Validator\AbstractValidator
{
@@ -31,7 +31,7 @@ public function __construct(NotEmptyFactory $notEmptyFactory)
* @param \Magento\Bundle\Model\Option $value
*
* @return boolean
- * @throws Zend_Validate_Exception If validation of $value is impossible
+ * @throws ValidateException
*/
public function isValid($value)
{
@@ -46,7 +46,7 @@ public function isValid($value)
* @param \Magento\Bundle\Model\Option $value
*
* @return void
- * @throws \Exception|Zend_Validate_Exception
+ * @throws \Exception|ValidateException
*/
protected function validateRequiredFields($value)
{
diff --git a/app/code/Magento/Bundle/Model/Product/Type.php b/app/code/Magento/Bundle/Model/Product/Type.php
index 3890be0e9a8a6..d542458c365a7 100644
--- a/app/code/Magento/Bundle/Model/Product/Type.php
+++ b/app/code/Magento/Bundle/Model/Product/Type.php
@@ -31,7 +31,7 @@ class Type extends \Magento\Catalog\Model\Product\Type\AbstractType
/**
* Product type
*/
- const TYPE_CODE = 'bundle';
+ public const TYPE_CODE = 'bundle';
/**
* Product is composite
@@ -52,6 +52,7 @@ class Type extends \Magento\Catalog\Model\Product\Type\AbstractType
*
* @var string
* @deprecated 100.2.0
+ * @see MAGETWO-71174
*/
protected $_keySelectionsCollection = '_cache_instance_selections_collection';
@@ -91,14 +92,14 @@ class Type extends \Magento\Catalog\Model\Product\Type\AbstractType
protected $_canConfigure = true;
/**
- * Catalog data
+ * Catalog data helper
*
* @var \Magento\Catalog\Helper\Data
*/
protected $_catalogData = null;
/**
- * Catalog product
+ * Catalog product helper
*
* @var \Magento\Catalog\Helper\Product
*/
@@ -1286,7 +1287,7 @@ protected function checkIsAllRequiredOptions($product, $isStrictProcessMode, $op
{
if (!$product->getSkipCheckRequiredOption() && $isStrictProcessMode) {
foreach ($optionsCollection->getItems() as $option) {
- if ($option->getRequired() && !isset($options[$option->getId()])) {
+ if ($option->getRequired() && empty($options[$option->getId()])) {
throw new \Magento\Framework\Exception\LocalizedException(
__('Please select all required options.')
);
@@ -1421,6 +1422,7 @@ protected function mergeSelectionsWithOptions($options, $selections)
/**
* Get prepared options with selection ids
*
+ * @SuppressWarnings(PHPMD.UnusedLocalVariable)
* @param array $options
* @return array
*/
diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price/DisabledProductOptionPriceModifier.php b/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price/DisabledProductOptionPriceModifier.php
index 0da07eaff4aaf..b3c3e74e1fa60 100644
--- a/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price/DisabledProductOptionPriceModifier.php
+++ b/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price/DisabledProductOptionPriceModifier.php
@@ -85,8 +85,9 @@ public function modifyPrice(IndexTableStructure $priceTable, array $entityIds =
foreach ($this->getBundleIds($entityIds) as $entityId) {
$entityId = (int) $entityId;
foreach ($this->getWebsiteIdsOfProduct($entityId) as $websiteId) {
+ $websiteId = (int) $websiteId;
$productIdsDisabledRequired = $this->selectionProductsDisabledRequired
- ->getChildProductIds($entityId, (int)$websiteId);
+ ->getChildProductIds($entityId, $websiteId);
if ($productIdsDisabledRequired) {
$connection = $this->resourceConnection->getConnection('indexer');
$select = $connection->select();
@@ -118,9 +119,8 @@ private function getWebsiteIdsOfProduct(int $entityId): array
['product_in_websites' => $this->resourceConnection->getTableName('catalog_product_website')],
['website_id']
)->where('product_in_websites.product_id = ?', $entityId);
- foreach ($connection->fetchCol($select) as $websiteId) {
- $this->websiteIdsOfProduct[$entityId][] = (int)$websiteId;
- }
+ $this->websiteIdsOfProduct[$entityId] = $connection->fetchCol($select);
+
return $this->websiteIdsOfProduct[$entityId];
}
diff --git a/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/AbstractItems.php b/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/AbstractItems.php
index 441bc0dd9de89..b3f1e5851f3f5 100644
--- a/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/AbstractItems.php
+++ b/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/AbstractItems.php
@@ -23,7 +23,7 @@
abstract class AbstractItems extends \Magento\Sales\Model\Order\Pdf\Items\AbstractItems
{
/**
- * Serializer
+ * Serializer interface instance.
*
* @var Json
*/
@@ -263,7 +263,8 @@ public function getValueHtml($item)
if (!$this->isShipmentSeparately($item)) {
$attributes = $this->getSelectionAttributes($item);
if ($attributes) {
- $result = $this->filterManager->sprintf($attributes['qty'], ['format' => '%d']) . ' x ' . $result;
+ $qty = $this->filterManager->sprintf($attributes['qty'], ['format' => '%f']);
+ $result = (float) $qty . ' x ' . $result;
}
}
if (!$this->isChildCalculated($item)) {
diff --git a/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/Creditmemo.php b/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/Creditmemo.php
index 1ed3fc76ddee0..bde9633212084 100644
--- a/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/Creditmemo.php
+++ b/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/Creditmemo.php
@@ -137,10 +137,10 @@ public function draw()
foreach ($this->string->split($item->getSku(), 17) as $part) {
$text[] = $part;
}
- $line[] = ['text' => $text, 'feed' => $x];
+ $line[] = ['text' => $text, 'feed' => $x, 'align' => 'right'];
}
- $x += 100;
+ $x += 30;
// draw prices
if ($this->canShowPriceInfo($childItem)) {
@@ -152,7 +152,7 @@ public function draw()
// draw Discount
$text = $order->formatPriceTxt(-$childItem->getDiscountAmount());
$line[] = ['text' => $text, 'feed' => $x, 'font' => 'bold', 'align' => 'right', 'width' => 50];
- $x += 50;
+ $x += 85;
// draw QTY
$text = $childItem->getQty() * 1;
@@ -160,10 +160,10 @@ public function draw()
'text' => $text,
'feed' => $x,
'font' => 'bold',
- 'align' => 'center',
+ 'align' => 'right',
'width' => 30,
];
- $x += 30;
+ $x += 35;
// draw Tax
$text = $order->formatPriceTxt($childItem->getTaxAmount());
diff --git a/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/Invoice.php b/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/Invoice.php
index a7f0a70b45219..c4cdb0aaf92c7 100644
--- a/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/Invoice.php
+++ b/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/Invoice.php
@@ -153,7 +153,11 @@ private function drawSkus(DataObject $childItem, array $lines): array
foreach ($this->string->split($this->getItem()->getSku(), 17) as $part) {
$text[] = $part;
}
- $lines[$index][] = ['text' => $text, 'feed' => 255];
+ $lines[$index][] = [
+ 'text' => $text,
+ 'feed' => 290,
+ 'align' => 'right'
+ ];
}
return $lines;
@@ -177,8 +181,8 @@ private function drawPrices(DataObject $childItem, array $lines): array
$item = $this->getItem();
$this->_item = $childItem;
- $feedPrice = 380;
- $feedSubtotal = $feedPrice + 185;
+ $feedPrice = 395;
+ $feedSubtotal = $feedPrice + 170;
foreach ($this->getItemPricesForDisplay() as $priceData) {
if (isset($priceData['label'])) {
// draw Price label
diff --git a/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/Shipment.php b/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/Shipment.php
index 8a293b63541a2..232fdf6a4fda1 100644
--- a/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/Shipment.php
+++ b/app/code/Magento/Bundle/Model/Sales/Order/Pdf/Items/Shipment.php
@@ -106,7 +106,7 @@ public function draw()
$line[0] = [
'font' => 'italic',
'text' => $this->string->split($attributes['option_label'], 60, true, true),
- 'feed' => 60,
+ 'feed' => 100,
];
$drawItems[$optionId] = ['lines' => [$line], 'height' => 15];
@@ -134,10 +134,10 @@ public function draw()
// draw Name
if ($childItem->getParentItem()) {
- $feed = 65;
+ $feed = 110;
$name = $this->getValueHtml($childItem);
} else {
- $feed = 60;
+ $feed = 100;
$name = $childItem->getName();
}
$text = [];
@@ -151,7 +151,7 @@ public function draw()
foreach ($this->string->split($childItem->getSku(), 25) as $part) {
$text[] = $part;
}
- $line[] = ['text' => $text, 'feed' => 440];
+ $line[] = ['text' => $text, 'feed' => 565, 'align' => 'right'];
$drawItems[$optionId]['lines'][] = $line;
}
diff --git a/app/code/Magento/Bundle/Pricing/Adjustment/DefaultSelectionPriceListProvider.php b/app/code/Magento/Bundle/Pricing/Adjustment/DefaultSelectionPriceListProvider.php
index 297c4659cb877..d09215bff7b00 100644
--- a/app/code/Magento/Bundle/Pricing/Adjustment/DefaultSelectionPriceListProvider.php
+++ b/app/code/Magento/Bundle/Pricing/Adjustment/DefaultSelectionPriceListProvider.php
@@ -10,6 +10,9 @@
use Magento\Bundle\Pricing\Price\BundleSelectionFactory;
use Magento\Catalog\Model\Product;
use Magento\Bundle\Model\Product\Price;
+use Magento\Catalog\Helper\Data as CatalogData;
+use Magento\Store\Model\StoreManagerInterface;
+use Magento\Store\Api\WebsiteRepositoryInterface;
/**
* Provide lightweight implementation which uses price index
@@ -26,16 +29,41 @@ class DefaultSelectionPriceListProvider implements SelectionPriceListProviderInt
*/
private $priceList;
+ /**
+ * @var CatalogData
+ */
+ private $catalogData;
+
+ /**
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
+
+ /**
+ * @var WebsiteRepositoryInterface
+ */
+ private $websiteRepository;
+
/**
* @param BundleSelectionFactory $bundleSelectionFactory
+ * @param CatalogData $catalogData
+ * @param StoreManagerInterface $storeManager
+ * @param WebsiteRepositoryInterface $websiteRepository
*/
- public function __construct(BundleSelectionFactory $bundleSelectionFactory)
- {
+ public function __construct(
+ BundleSelectionFactory $bundleSelectionFactory,
+ CatalogData $catalogData,
+ StoreManagerInterface $storeManager,
+ WebsiteRepositoryInterface $websiteRepository
+ ) {
$this->selectionFactory = $bundleSelectionFactory;
+ $this->catalogData = $catalogData;
+ $this->storeManager = $storeManager;
+ $this->websiteRepository = $websiteRepository;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getPriceList(Product $bundleProduct, $searchMin, $useRegularPrice)
{
@@ -138,7 +166,14 @@ private function addMiniMaxPriceList(Product $bundleProduct, $selectionsCollecti
*/
private function addMaximumMultiSelectionPriceList(Product $bundleProduct, $selectionsCollection, $useRegularPrice)
{
- $selectionsCollection->addPriceData();
+ $websiteId = null;
+ if (!$this->catalogData->isPriceGlobal()) {
+ $websiteId = (int)$this->storeManager->getStore()->getWebsiteId();
+ if ($websiteId === 0) {
+ $websiteId = $this->websiteRepository->getDefault()->getId();
+ }
+ }
+ $selectionsCollection->addPriceData(null, $websiteId);
foreach ($selectionsCollection as $selection) {
$this->priceList[] = $this->selectionFactory->create(
@@ -153,6 +188,8 @@ private function addMaximumMultiSelectionPriceList(Product $bundleProduct, $sele
}
/**
+ * Adjust min price for non required options
+ *
* @return void
*/
private function processMinPriceForNonRequiredOptions()
diff --git a/app/code/Magento/Bundle/Test/Fixture/AddProductToCart.php b/app/code/Magento/Bundle/Test/Fixture/AddProductToCart.php
new file mode 100644
index 0000000000000..1c39722b1c9bb
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Fixture/AddProductToCart.php
@@ -0,0 +1,102 @@
+productRepository = $productRepository;
+ $this->productType = $productType;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @param array $data Parameters
+ * $data['selections'] can be supplied in following formats:
+ * - [["$product1.id$"], ["$product2.id$"]]
+ * - [[{"product_id":"$product1.id$","qty":1}], [{"product_id":"$product2.id$","qty":1}]]
+ * - To skip an option, pass empty array [["$product1.id$"], [], ["$product2.id$"]]
+ *
+ * $data = [
+ * 'cart_id' => (int) Cart ID. Required.
+ * 'product_id' => (int) Product ID. Required.
+ * 'selections' => (array) array of options selections. Required.
+ * 'qty' => (int) Quantity. Optional. Default: 1.
+ * ]
+ *
+ */
+ public function apply(array $data = []): ?DataObject
+ {
+ $bundleProduct = $this->productRepository->getById($data['product_id']);
+ $buyRequest = [
+ 'bundle_option' => [],
+ 'bundle_option_qty' => [],
+ 'qty' => $data['qty'] ?? 1,
+ ];
+ $options = $this->productType->getOptionsCollection($bundleProduct);
+ $selections = $this->productType->getSelectionsCollection([], $bundleProduct);
+ $options->appendSelections($selections, true);
+ $optionsList = array_values($options->getItems());
+ foreach ($data['selections'] as $index => $selections) {
+ if (!empty($selections)) {
+ $option = $optionsList[$index];
+ foreach ($selections as $item) {
+ if (is_array($item)) {
+ $productId = (int)$item['product_id'];
+ $qty = $item['qty'] ?? 1;
+ } else {
+ $productId = (int)$item;
+ $qty = 1;
+ }
+ foreach ($option->getSelections() as $selection) {
+ if (((int)$selection->getProductId()) === $productId) {
+ $buyRequest['bundle_option'][$option->getId()][] = $selection->getSelectionId();
+ $buyRequest['bundle_option_qty'][$option->getId()][$selection->getSelectionId()] = $qty;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return parent::apply(
+ [
+ 'cart_id' => $data['cart_id'],
+ 'product_id' => $data['product_id'],
+ 'buy_request' => $buyRequest
+ ]
+ );
+ }
+}
diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminCustomizableOptionWithOneOptionActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminCustomizableOptionWithOneOptionActionGroup.xml
new file mode 100644
index 0000000000000..bf5de21ace5ca
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminCustomizableOptionWithOneOptionActionGroup.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+ Requires Navigation to the Product Creation page. Adds custom Option with one option.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 b662a4a27dcbe..8b78ac7b5fe6e 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml
@@ -112,5 +112,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml
index 8ae885ee8e38d..ad93ac802a92e 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml
@@ -50,5 +50,6 @@
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml
index eb92fd3756497..9ac8f41ac7515 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml
@@ -13,5 +13,6 @@
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
index 23b541273a861..90d47fd105029 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
@@ -16,5 +16,7 @@
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBundleProductPriceCalculationOnProductPageTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBundleProductPriceCalculationOnProductPageTest.xml
new file mode 100644
index 0000000000000..a41e1f369b707
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBundleProductPriceCalculationOnProductPageTest.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 560
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBundleProductPriceSymbolValidationInGridTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBundleProductPriceSymbolValidationInGridTest.xml
index 307e394913269..47e1cc35fb2f0 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBundleProductPriceSymbolValidationInGridTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBundleProductPriceSymbolValidationInGridTest.xml
@@ -80,7 +80,7 @@
-
+
-
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateBundleProductTest.xml
new file mode 100644
index 0000000000000..9d24d4f8d38b6
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateBundleProductTest.xml
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 50
+ $grabbedFirstBundleOptionQuantity
+
+
+
+ 50
+ $grabbedSecondBundleOptionQuantity
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProductInDutchUserLanguageTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProductInDutchUserLanguageTest.xml
new file mode 100644
index 0000000000000..77a43721d4e67
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProductInDutchUserLanguageTest.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100.00
+
+
+ 100.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByDescriptionTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByDescriptionTest.xml
index 00fb9186314d9..42b3f16ded350 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByDescriptionTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByDescriptionTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByPriceTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByPriceTest.xml
index a37ee49ee1951..cab4b09bbd642 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByPriceTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByPriceTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByShortDescriptionTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByShortDescriptionTest.xml
index f534b52aa2c34..b8bda30faa445 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByShortDescriptionTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByShortDescriptionTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml
index 465a171b9f96f..2e85f8305bba0 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdvanceCatalogSearchBundleBySkuWithHyphenTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdvanceCatalogSearchBundleBySkuWithHyphenTest.xml
index f75fe2fe1f306..63ed6c669d25f 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdvanceCatalogSearchBundleBySkuWithHyphenTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdvanceCatalogSearchBundleBySkuWithHyphenTest.xml
@@ -17,7 +17,7 @@
-
+
@@ -44,4 +44,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSelectAndSetBundleOptionsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSelectAndSetBundleOptionsTest.xml
index 14cfae77060fd..61545268ef63e 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSelectAndSetBundleOptionsTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSelectAndSetBundleOptionsTest.xml
@@ -105,23 +105,23 @@
-
+
-
+
-
+
-
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontValidateQuantityBundleProductsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontValidateQuantityBundleProductsTest.xml
index bfa620a46b8e9..b486d95ac3e4b 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontValidateQuantityBundleProductsTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontValidateQuantityBundleProductsTest.xml
@@ -80,6 +80,7 @@
+
@@ -88,6 +89,7 @@
+
diff --git a/app/code/Magento/Bundle/Test/Unit/Block/Adminhtml/Sales/Order/Items/RendererTest.php b/app/code/Magento/Bundle/Test/Unit/Block/Adminhtml/Sales/Order/Items/RendererTest.php
index daf90f9a07af3..210c69160cf93 100644
--- a/app/code/Magento/Bundle/Test/Unit/Block/Adminhtml/Sales/Order/Items/RendererTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Block/Adminhtml/Sales/Order/Items/RendererTest.php
@@ -310,4 +310,31 @@ public function canShowPriceInfoDataProvider()
[false, ['product_calculations' => 0], false],
];
}
+
+ /**
+ * @dataProvider getValueHtmlWithoutShipmentSeparatelyDataProvider
+ */
+ public function testGetValueHtmlWithoutShipmentSeparately($qty)
+ {
+ $model = $this->getMockBuilder(Renderer::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['escapeHtml', 'isShipmentSeparately', 'getSelectionAttributes', 'isChildCalculated'])
+ ->getMock();
+ $model->expects($this->any())->method('escapeHtml')->willReturn('Test');
+ $model->expects($this->any())->method('isShipmentSeparately')->willReturn(false);
+ $model->expects($this->any())->method('isChildCalculated')->willReturn(true);
+ $model->expects($this->any())->method('getSelectionAttributes')->willReturn(['qty' => $qty]);
+ $this->assertSame($qty . ' x Test', $model->getValueHtml($this->orderItem));
+ }
+
+ /**
+ * @return array
+ */
+ public function getValueHtmlWithoutShipmentSeparatelyDataProvider()
+ {
+ return [
+ [1],
+ [1.5],
+ ];
+ }
}
diff --git a/app/code/Magento/Bundle/Test/Unit/Block/Adminhtml/Sales/Order/View/Items/RendererTest.php b/app/code/Magento/Bundle/Test/Unit/Block/Adminhtml/Sales/Order/View/Items/RendererTest.php
index 8f5498222003f..43d8d7859708c 100644
--- a/app/code/Magento/Bundle/Test/Unit/Block/Adminhtml/Sales/Order/View/Items/RendererTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Block/Adminhtml/Sales/Order/View/Items/RendererTest.php
@@ -224,4 +224,31 @@ public function canShowPriceInfoDataProvider()
[false, ['product_calculations' => 0], false],
];
}
+
+ /**
+ * @dataProvider getValueHtmlWithoutShipmentSeparatelyDataProvider
+ */
+ public function testGetValueHtmlWithoutShipmentSeparately($qty)
+ {
+ $model = $this->getMockBuilder(Renderer::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['escapeHtml', 'isShipmentSeparately', 'getSelectionAttributes', 'isChildCalculated'])
+ ->getMock();
+ $model->expects($this->any())->method('escapeHtml')->willReturn('Test');
+ $model->expects($this->any())->method('isShipmentSeparately')->willReturn(false);
+ $model->expects($this->any())->method('isChildCalculated')->willReturn(true);
+ $model->expects($this->any())->method('getSelectionAttributes')->willReturn(['qty' => $qty]);
+ $this->assertSame($qty . ' x Test', $model->getValueHtml($this->orderItem));
+ }
+
+ /**
+ * @return array
+ */
+ public function getValueHtmlWithoutShipmentSeparatelyDataProvider()
+ {
+ return [
+ [1],
+ [1.5],
+ ];
+ }
}
diff --git a/app/code/Magento/Bundle/Test/Unit/Block/Sales/Order/Items/RendererTest.php b/app/code/Magento/Bundle/Test/Unit/Block/Sales/Order/Items/RendererTest.php
index 0a7a8789d9b8a..67e5cf24ac477 100644
--- a/app/code/Magento/Bundle/Test/Unit/Block/Sales/Order/Items/RendererTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Block/Sales/Order/Items/RendererTest.php
@@ -286,4 +286,41 @@ public function canShowPriceInfoDataProvider()
[false, ['product_calculations' => 0], false],
];
}
+
+ /**
+ * @dataProvider getValueHtmlWithAttributesDataProvider
+ */
+ public function testGetValueHtmlWithAttributes($qty)
+ {
+ $price = 100;
+ $orderModel = $this->getMockBuilder(\Magento\Sales\Model\Order::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['formatPrice'])
+ ->getMock();
+ $orderModel->expects($this->any())->method('formatPrice')->willReturn($price);
+
+ $model = $this->getMockBuilder(Renderer::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getOrder', 'getSelectionAttributes', 'escapeHtml'])
+ ->getMock();
+ $model->expects($this->any())->method('escapeHtml')->willReturn('Test');
+ $model->expects($this->any())->method('getOrder')->willReturn($orderModel);
+ $model->expects($this->any())->method('getSelectionAttributes')
+ ->willReturn([
+ 'qty' => $qty ,
+ 'price' => $price,
+ ]);
+ $this->assertSame($qty . ' x Test ' . $price, $model->getValueHtml($this->orderItem));
+ }
+
+ /**
+ * @return array
+ */
+ public function getValueHtmlWithAttributesDataProvider()
+ {
+ return [
+ [1],
+ [1.5],
+ ];
+ }
}
diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Sales/Order/Pdf/Items/AbstractItemsTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Sales/Order/Pdf/Items/AbstractItemsTest.php
index 6eb542cee4e73..75a43776e98b6 100644
--- a/app/code/Magento/Bundle/Test/Unit/Model/Sales/Order/Pdf/Items/AbstractItemsTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Model/Sales/Order/Pdf/Items/AbstractItemsTest.php
@@ -8,6 +8,7 @@
namespace Magento\Bundle\Test\Unit\Model\Sales\Order\Pdf\Items;
use Magento\Bundle\Model\Sales\Order\Pdf\Items\Shipment;
+use Magento\Framework\Filter\FilterManager;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Sales\Model\Order\Creditmemo;
@@ -33,6 +34,11 @@ class AbstractItemsTest extends TestCase
*/
private $orderItemMock;
+ /**
+ * @var FilterManager|MockObject
+ */
+ private $filterManagerMock;
+
protected function setUp(): void
{
$this->orderItemMock = $this->getMockBuilder(Item::class)
@@ -40,13 +46,18 @@ protected function setUp(): void
->onlyMethods(['getProductOptions', 'getParentItem', 'getId'])
->disableOriginalConstructor()
->getMock();
+ $this->filterManagerMock = $this->getMockBuilder(FilterManager::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['stripTags', 'sprintf'])
+ ->getMock();
$objectManager = new ObjectManager($this);
$this->serializerMock = $this->createMock(Json::class);
$this->model = $objectManager->getObject(
Shipment::class,
[
- 'serializer' => $this->serializerMock
+ 'serializer' => $this->serializerMock,
+ 'filterManager' => $this->filterManagerMock,
]
);
}
@@ -358,4 +369,32 @@ public function canShowPriceInfoDataProvider()
[false, ['product_calculations' => 0], false],
];
}
+
+ /**
+ * @dataProvider getValueHtmlWithoutShipmentSeparatelyDataProvider
+ */
+ public function testGetValueHtmlWithoutShipmentSeparately($qty)
+ {
+ $this->filterManagerMock->expects($this->any())->method('stripTags')->willReturn('Test');
+ $this->filterManagerMock->expects($this->any())->method('sprintf')->willReturn($qty);
+ $this->orderItemMock->expects($this->any())->method('getProductOptions')
+ ->willReturn([
+ 'shipment_type' => 1,
+ 'bundle_selection_attributes' => [],
+ ]);
+ $this->serializerMock->expects($this->any())->method('unserialize')
+ ->willReturn(['qty' => $qty]);
+ $this->assertSame($qty . ' x Test', $this->model->getValueHtml($this->orderItemMock));
+ }
+
+ /**
+ * @return array
+ */
+ public function getValueHtmlWithoutShipmentSeparatelyDataProvider()
+ {
+ return [
+ [1],
+ [1.5],
+ ];
+ }
}
diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Sales/Order/Pdf/Items/InvoiceTestProvider.php b/app/code/Magento/Bundle/Test/Unit/Model/Sales/Order/Pdf/Items/InvoiceTestProvider.php
index 7de3d383d006e..24aeffc1e33c7 100644
--- a/app/code/Magento/Bundle/Test/Unit/Model/Sales/Order/Pdf/Items/InvoiceTestProvider.php
+++ b/app/code/Magento/Bundle/Test/Unit/Model/Sales/Order/Pdf/Items/InvoiceTestProvider.php
@@ -52,7 +52,7 @@ public function getData(): array
],
[
'text' => 'Excl. Tax:',
- 'feed' => 380,
+ 'feed' => 395,
'align' => 'right',
],
[
@@ -64,7 +64,7 @@ public function getData(): array
[
[
'text' => '10.00',
- 'feed' => 380,
+ 'feed' => 395,
'font' => 'bold',
'align' => 'right',
],
@@ -78,7 +78,7 @@ public function getData(): array
[
[
'text' => 'Incl. Tax:',
- 'feed' => 380,
+ 'feed' => 395,
'align' => 'right',
],
[
@@ -90,7 +90,7 @@ public function getData(): array
[
[
'text' => '10.83',
- 'feed' => 380,
+ 'feed' => 395,
'font' => 'bold',
'align' => 'right',
],
@@ -119,7 +119,7 @@ public function getData(): array
],
[
'text' => 'Excl. Tax:',
- 'feed' => 380,
+ 'feed' => 395,
'align' => 'right',
],
[
@@ -131,7 +131,7 @@ public function getData(): array
[
[
'text' => '5.00',
- 'feed' => 380,
+ 'feed' => 395,
'font' => 'bold',
'align' => 'right',
],
@@ -145,7 +145,7 @@ public function getData(): array
[
[
'text' => 'Incl. Tax:',
- 'feed' => 380,
+ 'feed' => 395,
'align' => 'right',
],
[
@@ -157,7 +157,7 @@ public function getData(): array
[
[
'text' => '5.41',
- 'feed' => 380,
+ 'feed' => 395,
'font' => 'bold',
'align' => 'right',
],
@@ -203,7 +203,7 @@ public function getData(): array
],
[
'text' => '10.83',
- 'feed' => 380,
+ 'feed' => 395,
'font' => 'bold',
'align' => 'right',
],
@@ -232,7 +232,7 @@ public function getData(): array
],
[
'text' => '5.41',
- 'feed' => 380,
+ 'feed' => 395,
'font' => 'bold',
'align' => 'right',
],
@@ -279,7 +279,7 @@ public function getData(): array
],
[
'text' => '10.00',
- 'feed' => 380,
+ 'feed' => 395,
'font' => 'bold',
'align' => 'right',
],
@@ -308,7 +308,7 @@ public function getData(): array
],
[
'text' => '5.00',
- 'feed' => 380,
+ 'feed' => 395,
'font' => 'bold',
'align' => 'right',
],
diff --git a/app/code/Magento/Bundle/Test/Unit/Pricing/Adjustment/DefaultSelectionPriceListProviderTest.php b/app/code/Magento/Bundle/Test/Unit/Pricing/Adjustment/DefaultSelectionPriceListProviderTest.php
new file mode 100644
index 0000000000000..0adb1f5b9730f
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Unit/Pricing/Adjustment/DefaultSelectionPriceListProviderTest.php
@@ -0,0 +1,183 @@
+selectionFactory = $this->getMockBuilder(BundleSelectionFactory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->catalogData = $this->getMockBuilder(CatalogData::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class)
+ ->getMockForAbstractClass();
+ $this->websiteRepository = $this->getMockBuilder(WebsiteRepositoryInterface::class)
+ ->getMockForAbstractClass();
+
+ $this->product = $this->getMockBuilder(Product::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->optionsCollection = $this->getMockBuilder(Collection::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->typeInstance = $this->getMockBuilder(Type::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->option = $this->getMockBuilder(Option::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->selectionCollection = $this->getMockBuilder(SelectionCollection::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->selection = $this->getMockBuilder(DataObject::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->store = $this->getMockBuilder(StoreInterface::class)
+ ->getMockForAbstractClass();
+ $this->website = $this->getMockBuilder(WebsiteInterface::class)
+ ->getMockForAbstractClass();
+
+ $this->model = new DefaultSelectionPriceListProvider(
+ $this->selectionFactory,
+ $this->catalogData,
+ $this->storeManager,
+ $this->websiteRepository
+ );
+ }
+
+ public function testGetPriceList(): void
+ {
+ $optionId = 1;
+
+ $this->typeInstance->expects($this->any())
+ ->method('getOptionsCollection')
+ ->with($this->product)
+ ->willReturn($this->optionsCollection);
+ $this->product->expects($this->any())
+ ->method('getTypeInstance')
+ ->willReturn($this->typeInstance);
+ $this->optionsCollection->expects($this->once())
+ ->method('getIterator')
+ ->willReturn(new \ArrayIterator([$this->option]));
+ $this->option->expects($this->once())
+ ->method('getOptionId')
+ ->willReturn($optionId);
+ $this->typeInstance->expects($this->once())
+ ->method('getSelectionsCollection')
+ ->with([$optionId], $this->product)
+ ->willReturn($this->selectionCollection);
+ $this->option->expects($this->once())
+ ->method('isMultiSelection')
+ ->willReturn(true);
+ $this->storeManager->expects($this->once())
+ ->method('getStore')
+ ->willReturn($this->store);
+ $this->store->expects($this->once())
+ ->method('getWebsiteId')
+ ->willReturn(0);
+ $this->websiteRepository->expects($this->once())
+ ->method('getDefault')
+ ->willReturn($this->website);
+ $this->website->expects($this->once())
+ ->method('getId')
+ ->willReturn(1);
+ $this->selectionCollection->expects($this->once())
+ ->method('getIterator')
+ ->willReturn(new \ArrayIterator([]));
+
+ $this->model->getPriceList($this->product, false, false);
+ }
+}
diff --git a/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/Modifier/SpecialPriceAttributesTest.php b/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/Modifier/SpecialPriceAttributesTest.php
new file mode 100644
index 0000000000000..0146fdf48a426
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Unit/Ui/DataProvider/Product/Modifier/SpecialPriceAttributesTest.php
@@ -0,0 +1,164 @@
+localResolver = $this->createMock(ResolverInterface::class);
+ $this->numberFormatterFactory = $this->createMock(NumberFormatterFactory::class);
+ $this->model = new SpecialPriceAttributes(
+ $this->createMock(DirectoryCurrency::class),
+ $this->localResolver,
+ ['attr1'],
+ $this->numberFormatterFactory
+ );
+ }
+
+ /**
+ * @param string $locale
+ * @param array $input
+ * @param array $output
+ * @return void
+ * @dataProvider modifyDataProvider
+ */
+ public function testModifyData(string $locale, array $input, array $output): void
+ {
+ $this->localResolver->method('getLocale')
+ ->willReturn($locale);
+ $this->numberFormatterFactory->method('create')
+ ->willReturnCallback(
+ function (array $args) {
+ return new NumberFormatter(...array_values($args));
+ }
+ );
+ $this->assertEquals($output, $this->model->modifyData($input));
+ }
+
+ /**
+ * @return array
+ */
+ public function modifyDataProvider(): array
+ {
+ return [
+ [
+ 'en_US',
+ [
+ 'items' => [
+ [
+ 'type_id' => 'simple',
+ 'attr1' => '99',
+ ]
+ ]
+ ],
+ [
+ 'items' => [
+ [
+ 'type_id' => 'simple',
+ 'attr1' => '99',
+ ]
+ ]
+ ],
+ ],
+ [
+ 'en_US',
+ [
+ 'items' => [
+ [
+ 'type_id' => 'simple',
+ 'attr1' => '99',
+ ],
+ [
+ 'type_id' => Type::TYPE_CODE,
+ 'attr1' => '99',
+ ]
+ ]
+ ],
+ [
+ 'items' => [
+ [
+ 'type_id' => 'simple',
+ 'attr1' => '99',
+ ],
+ [
+ 'type_id' => Type::TYPE_CODE,
+ 'attr1' => '99.000000%',
+ ]
+ ]
+ ],
+ ],
+ [
+ 'en_US',
+ [
+ 'items' => [
+ [
+ 'type_id' => Type::TYPE_CODE,
+ 'attr1' => '9999',
+ ]
+ ]
+ ],
+ [
+ 'items' => [
+ [
+ 'type_id' => Type::TYPE_CODE,
+ 'attr1' => '9,999.000000%',
+ ]
+ ]
+ ],
+ ],
+ [
+ 'de_DE',
+ [
+ 'items' => [
+ [
+ 'type_id' => Type::TYPE_CODE,
+ 'attr1' => '9999',
+ ]
+ ]
+ ],
+ [
+ 'items' => [
+ [
+ 'type_id' => Type::TYPE_CODE,
+ 'attr1' => '9.999,000000 %',
+ ]
+ ]
+ ],
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Modifier/SpecialPriceAttributes.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Modifier/SpecialPriceAttributes.php
index cbe4dfa748340..4857d37774b8f 100644
--- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Modifier/SpecialPriceAttributes.php
+++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Modifier/SpecialPriceAttributes.php
@@ -8,13 +8,13 @@
namespace Magento\Bundle\Ui\DataProvider\Product\Modifier;
use Magento\Bundle\Model\Product\Type;
+use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Directory\Model\Currency as DirectoryCurrency;
-use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Locale\ResolverInterface;
+use Magento\Framework\NumberFormatterFactory;
use Magento\Ui\DataProvider\Modifier\ModifierInterface;
use NumberFormatter;
-use Zend_Currency;
-use Zend_Currency_Exception;
/**
* Modify product listing special price attributes
@@ -32,9 +32,9 @@ class SpecialPriceAttributes implements ModifierInterface
private $priceAttributeList;
/**
- * @var DirectoryCurrency
+ * @var NumberFormatterFactory
*/
- private $directoryCurrency;
+ private $numberFormatterFactory;
/**
* PriceAttributes constructor.
@@ -42,42 +42,38 @@ class SpecialPriceAttributes implements ModifierInterface
* @param DirectoryCurrency $directoryCurrency
* @param ResolverInterface $localeResolver
* @param array $priceAttributeList
+ * @param NumberFormatterFactory|null $numberFormatterFactory
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct(
DirectoryCurrency $directoryCurrency,
ResolverInterface $localeResolver,
- array $priceAttributeList = []
+ array $priceAttributeList = [],
+ ?NumberFormatterFactory $numberFormatterFactory = null
) {
- $this->directoryCurrency = $directoryCurrency;
$this->localeResolver = $localeResolver;
$this->priceAttributeList = $priceAttributeList;
+ $this->numberFormatterFactory = $numberFormatterFactory
+ ?? ObjectManager::getInstance()->get(NumberFormatterFactory::class);
}
/**
* @inheritdoc
- * @throws NoSuchEntityException
- * @throws Zend_Currency_Exception
*/
public function modifyData(array $data): array
{
if (empty($data) || empty($this->priceAttributeList)) {
return $data;
}
- $numberFormatter = new NumberFormatter(
- $this->localeResolver->getLocale(),
- NumberFormatter::PERCENT
- );
- $numberFormatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, 2);
+ $numberFormatter = $this->numberFormatterFactory->create([
+ 'locale' => $this->localeResolver->getLocale(),
+ 'style' => NumberFormatter::PERCENT
+ ]);
+ $numberFormatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, 6);
foreach ($data['items'] as &$item) {
foreach ($this->priceAttributeList as $priceAttribute) {
- if (isset($item[$priceAttribute]) && $item['type_id'] == Type::TYPE_CODE) {
- $item[$priceAttribute] =
- $this->directoryCurrency->format(
- $item[$priceAttribute],
- ['display' => Zend_Currency::NO_SYMBOL],
- false
- );
- $item[$priceAttribute] = $numberFormatter->format($item[$priceAttribute] / 100);
+ if (isset($item[$priceAttribute]) && $item[ProductInterface::TYPE_ID] === Type::TYPE_CODE) {
+ $item[$priceAttribute] = $numberFormatter->format((float) $item[$priceAttribute] / 100);
}
}
}
diff --git a/app/code/Magento/Bundle/ViewModel/ValidateQuantity.php b/app/code/Magento/Bundle/ViewModel/ValidateQuantity.php
index 52309dd068670..ee7e2064e906f 100644
--- a/app/code/Magento/Bundle/ViewModel/ValidateQuantity.php
+++ b/app/code/Magento/Bundle/ViewModel/ValidateQuantity.php
@@ -9,7 +9,6 @@
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Framework\View\Element\Block\ArgumentInterface;
-use Magento\Catalog\Block\Product\View as ProductView;
/**
* ViewModel for Bundle Option Block
@@ -21,27 +20,24 @@ class ValidateQuantity implements ArgumentInterface
*/
private $serializer;
- /**
- * @var ProductView
- */
- private $productView;
-
/**
* @param Json $serializer
- * @param ProductView $productView
*/
public function __construct(
- Json $serializer,
- ProductView $productView
+ Json $serializer
) {
$this->serializer = $serializer;
- $this->productView = $productView;
}
+ /**
+ * Returns quantity validator.
+ *
+ * @return string
+ */
public function getQuantityValidators(): string
{
- return $this->serializer->serialize(
- $this->productView->getQuantityValidators()
- );
+ $validators['validate-item-quantity'] = [];
+
+ return $this->serializer->serialize($validators);
}
}
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/Bundle/etc/adminhtml/di.xml b/app/code/Magento/Bundle/etc/adminhtml/di.xml
index c13e249818051..f173bb26fcc3d 100644
--- a/app/code/Magento/Bundle/etc/adminhtml/di.xml
+++ b/app/code/Magento/Bundle/etc/adminhtml/di.xml
@@ -69,4 +69,11 @@
+
+
+
+ - Magento\Bundle\Model\Product\Type::TYPE_CODE
+
+
+
diff --git a/app/code/Magento/Bundle/view/base/web/js/price-bundle.js b/app/code/Magento/Bundle/view/base/web/js/price-bundle.js
index 367899c81dd72..fe01f23bb4510 100644
--- a/app/code/Magento/Bundle/view/base/web/js/price-bundle.js
+++ b/app/code/Magento/Bundle/view/base/web/js/price-bundle.js
@@ -387,15 +387,9 @@ define([
function isValidQty(bundleOption) {
var isValid = true,
qtyElem = bundleOption.data('qtyField'),
- bundleOptionType = bundleOption.prop('type'),
- qtyValidator = qtyElem.data('validate') &&
- typeof qtyElem.data('validate')['validate-item-quantity'] === 'object' ?
- qtyElem.data('validate')['validate-item-quantity'] : null;
-
- if (['radio', 'select-one'].includes(bundleOptionType) &&
- qtyValidator &&
- (qtyElem.val() < qtyValidator.minAllowed || qtyElem.val() > qtyValidator.maxAllowed)
- ) {
+ bundleOptionType = bundleOption.prop('type');
+
+ if (['radio', 'select-one'].includes(bundleOptionType) && qtyElem.val() < 0) {
isValid = false;
}
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Collection.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Collection.php
index fe1b47bc635b6..2fa0ce6def9d3 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Collection.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Collection.php
@@ -112,13 +112,15 @@ private function fetch() : array
$productTable = $optionsCollection->getTable('catalog_product_entity');
$linkField = $optionsCollection->getConnection()->getAutoIncrementField($productTable);
+ $entityIds = array_column($this->skuMap, 'entity_id');
+
$optionsCollection->getSelect()->join(
['cpe' => $productTable],
'cpe.' . $linkField . ' = main_table.parent_id',
[]
)->where(
"cpe.entity_id IN (?)",
- $this->skuMap
+ $entityIds
);
$optionsCollection->setPositionOrder();
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Label.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Label.php
index de72b18982c12..dfdf4e904a475 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Label.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Label.php
@@ -15,7 +15,7 @@
use Magento\Framework\GraphQl\Query\ResolverInterface;
/**
- * Class Label
+ * Bundle product option label resolver
*/
class Label implements ResolverInterface
{
@@ -56,8 +56,8 @@ public function resolve(
$this->product->addProductSku($value['sku']);
$this->product->addEavAttributes(['name']);
- $result = function () use ($value) {
- $productData = $this->product->getProductBySku($value['sku']);
+ $result = function () use ($value, $context) {
+ $productData = $this->product->getProductBySku($value['sku'], $context);
/** @var \Magento\Catalog\Model\Product $productModel */
$productModel = isset($productData['model']) ? $productData['model'] : null;
return $productModel ? $productModel->getName() : null;
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/PriceRange.php b/app/code/Magento/BundleGraphQl/Model/Resolver/PriceRange.php
index 0129c7ae65138..6cc546fd33d48 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/PriceRange.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/PriceRange.php
@@ -73,7 +73,7 @@ public function resolve(
array $args = null
) {
$this->productDataProvider->addProductSku($value['sku']);
- $productData = $this->productDataProvider->getProductBySku($value['sku']);
+ $productData = $this->productDataProvider->getProductBySku($value['sku'], $context);
$value['model'] = $productData['model'];
return $this->priceRangeDataProvider->prepare($context, $info, $value);
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/BundleGraphQl/etc/graphql/di.xml b/app/code/Magento/BundleGraphQl/etc/graphql/di.xml
index caca08d3d4a9b..2f4cd2db8cea5 100644
--- a/app/code/Magento/BundleGraphQl/etc/graphql/di.xml
+++ b/app/code/Magento/BundleGraphQl/etc/graphql/di.xml
@@ -114,4 +114,22 @@
+
+
+ Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Deferred\ChildProduct
+
+
+
+
+ Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Deferred\ChildProduct
+
+
+
+
+
+ Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Deferred\ChildProduct
+
+
+
diff --git a/app/code/Magento/BundleGraphQl/etc/schema.graphqls b/app/code/Magento/BundleGraphQl/etc/schema.graphqls
index 9820bf108f325..6ebdddfbedc53 100644
--- a/app/code/Magento/BundleGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/BundleGraphQl/etc/schema.graphqls
@@ -69,7 +69,7 @@ type BundleItemOption @doc(description: "Defines the characteristics that compri
price: Float @doc(description: "The price of the selected option.")
price_type: PriceTypeEnum @doc(description: "One of FIXED, PERCENT, or DYNAMIC.")
can_change_quantity: Boolean @doc(description: "Indicates whether the customer can change the number of items for this option.")
- product: ProductInterface @doc(description: "Contains details about this product option.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product")
+ product: ProductInterface @doc(description: "Contains details about this product option.") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Options\\Product")
uid: ID! @doc(description: "The unique ID for a `BundleItemOption` object.") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Options\\BundleItemOptionUid")
}
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/Block/Product/View/Options/AbstractOptions.php b/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php
index 8655897fa5cad..0d6bca2d37a10 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php
@@ -195,17 +195,6 @@ protected function _formatPrice($value, $flag = true)
$customOptionPrice = $this->getProduct()->getPriceInfo()->getPrice('custom_option_price');
$isPercent = (bool) $value['is_percent'];
- if (!$isPercent) {
- $catalogPriceValue = $this->calculateCustomOptionCatalogRule->execute(
- $this->getProduct(),
- (float)$value['pricing_value'],
- $isPercent
- );
- if ($catalogPriceValue !== null) {
- $value['pricing_value'] = $catalogPriceValue;
- }
- }
-
$context = [CustomOptionPriceInterface::CONFIGURATION_OPTION_FLAG => true];
$optionAmount = $isPercent
? $this->calculator->getAmount(
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category.php
index de8c402c7e761..e931c44396be7 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Category.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category.php
@@ -8,6 +8,7 @@
namespace Magento\Catalog\Controller\Adminhtml;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Filter\FilterInput;
use Magento\Store\Model\Store;
use Magento\Framework\Controller\ResultFactory;
@@ -22,7 +23,7 @@ abstract class Category extends \Magento\Backend\App\Action
*
* @see _isAllowed()
*/
- const ADMIN_RESOURCE = 'Magento_Catalog::categories';
+ public const ADMIN_RESOURCE = 'Magento_Catalog::categories';
/**
* @var \Magento\Framework\Stdlib\DateTime\Filter\Date
@@ -154,6 +155,7 @@ private function resolveStoreId() : int
* @return \Magento\Framework\Controller\Result\Json
*
* @deprecated 101.0.0
+ * @see we don't recommend this approach anymore
*/
protected function ajaxRequestResponse($category, $resultPage)
{
@@ -213,7 +215,7 @@ protected function dateTimePreprocessing($category, $postData)
}
}
}
- $inputFilter = new \Zend_Filter_Input($dateFieldFilters, [], $postData);
+ $inputFilter = new FilterInput($dateFieldFilters, [], $postData);
return $inputFilter->getUnescaped();
}
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute.php
index 7a6b71db9dde3..71649e4fa5612 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute.php
@@ -9,6 +9,7 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
+use Laminas\Validator\Regex;
use Magento\Framework\Controller\Result;
use Magento\Framework\View\Result\PageFactory;
@@ -19,7 +20,7 @@ abstract class Attribute extends \Magento\Backend\App\Action
*
* @see _isAllowed()
*/
- const ADMIN_RESOURCE = 'Magento_Catalog::attributes_attributes';
+ public const ADMIN_RESOURCE = 'Magento_Catalog::attributes_attributes';
/**
* @var \Magento\Framework\Cache\FrontendInterface
@@ -32,8 +33,6 @@ abstract class Attribute extends \Magento\Backend\App\Action
protected $_entityTypeId;
/**
- * Core registry
- *
* @var \Magento\Framework\Registry
*/
protected $_coreRegistry = null;
@@ -80,6 +79,8 @@ public function dispatch(\Magento\Framework\App\RequestInterface $request)
}
/**
+ * Method to create action page.
+ *
* @param \Magento\Framework\Phrase|null $title
* @return \Magento\Backend\Model\View\Result\Page
*/
@@ -124,7 +125,7 @@ protected function generateCode($label)
0,
30
);
- $validatorAttrCode = new \Zend_Validate_Regex(['pattern' => '/^[a-z][a-z_0-9]{0,29}[a-z0-9]$/']);
+ $validatorAttrCode = new Regex(['pattern' => '/^[a-z][a-z_0-9]{0,29}[a-z0-9]$/']);
if (!$validatorAttrCode->isValid($code)) {
// md5() here is not for cryptographic use.
// phpcs:ignore Magento2.Security.InsecureFunction
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
index 440716e456910..d443f399360a9 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
@@ -135,7 +135,6 @@ public function __construct(
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- * @throws \Zend_Validate_Exception
*/
public function execute()
{
@@ -327,6 +326,9 @@ public function execute()
return $this->returnResult('catalog/*/', [], ['error' => false]);
} catch (\Exception $e) {
$this->messageManager->addErrorMessage($e->getMessage());
+ if ($attributeId === null) {
+ unset($data['frontend_input']);
+ }
$this->_session->setAttributeData($data);
return $this->returnResult(
'catalog/*/edit',
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
index 115e26df31bbf..4491ad1ee99b5 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
@@ -25,7 +25,7 @@
use Magento\Framework\Locale\FormatInterface;
use Magento\Framework\Stdlib\DateTime\Filter\Date;
use Magento\Store\Model\StoreManagerInterface;
-use Zend_Filter_Input;
+use Magento\Framework\Filter\FilterInput;
/**
* Product helper
@@ -60,6 +60,7 @@ class Helper
/**
* @var Date
* @deprecated 101.0.0
+ * @see we don't recommend this approach anymore
*/
protected $dateFilter;
@@ -250,7 +251,7 @@ public function initializeFromData(Product $product, array $productData)
}
}
- $inputFilter = new Zend_Filter_Input($dateFieldFilters, [], $productData);
+ $inputFilter = new FilterInput($dateFieldFilters, [], $productData);
$productData = $inputFilter->getUnescaped();
if (isset($productData['options'])) {
@@ -435,6 +436,7 @@ private function overwriteValue($optionId, $option, $overwriteOptions)
*
* @return LinkResolver
* @deprecated 102.0.0
+ * @see we don't recommend this approach anymore
*/
private function getLinkResolver()
{
diff --git a/app/code/Magento/Catalog/Cron/RefreshSpecialPrices.php b/app/code/Magento/Catalog/Cron/RefreshSpecialPrices.php
index 25668e57e40ed..61d731dc4ad07 100644
--- a/app/code/Magento/Catalog/Cron/RefreshSpecialPrices.php
+++ b/app/code/Magento/Catalog/Cron/RefreshSpecialPrices.php
@@ -6,7 +6,6 @@
namespace Magento\Catalog\Cron;
use Magento\Catalog\Api\Data\CategoryInterface;
-use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\EntityManager\MetadataPool;
@@ -59,6 +58,7 @@ class RefreshSpecialPrices
* @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
* @param \Magento\Eav\Model\Config $eavConfig
* @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $processor
+ * @param MetadataPool $metadataPool
*/
public function __construct(
\Magento\Store\Model\StoreManagerInterface $storeManager,
@@ -66,7 +66,8 @@ public function __construct(
\Magento\Framework\Stdlib\DateTime $dateTime,
\Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
\Magento\Eav\Model\Config $eavConfig,
- \Magento\Catalog\Model\Indexer\Product\Price\Processor $processor
+ \Magento\Catalog\Model\Indexer\Product\Price\Processor $processor,
+ MetadataPool $metadataPool
) {
$this->_storeManager = $storeManager;
$this->_resource = $resource;
@@ -74,6 +75,7 @@ public function __construct(
$this->_localeDate = $localeDate;
$this->_eavConfig = $eavConfig;
$this->_processor = $processor;
+ $this->metadataPool = $metadataPool;
}
/**
@@ -139,8 +141,8 @@ protected function _refreshSpecialPriceByStore($storeId, $attrCode, $attrConditi
$attribute = $this->_eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $attrCode);
$attributeId = $attribute->getAttributeId();
- $linkField = $this->getMetadataPool()->getMetadata(CategoryInterface::class)->getLinkField();
- $identifierField = $this->getMetadataPool()->getMetadata(CategoryInterface::class)->getIdentifierField();
+ $linkField = $this->metadataPool->getMetadata(CategoryInterface::class)->getLinkField();
+ $identifierField = $this->metadataPool->getMetadata(CategoryInterface::class)->getIdentifierField();
$connection = $this->_getConnection();
@@ -164,24 +166,10 @@ protected function _refreshSpecialPriceByStore($storeId, $attrCode, $attrConditi
$attrConditionValue
);
- $selectData = $connection->fetchCol($select, $identifierField);
+ $selectData = $connection->fetchCol($select);
if (!empty($selectData)) {
$this->_processor->getIndexer()->reindexList($selectData);
}
}
-
- /**
- * Get MetadataPool instance
- * @return MetadataPool
- *
- * @deprecated 101.0.0
- */
- private function getMetadataPool()
- {
- if (null === $this->metadataPool) {
- $this->metadataPool = ObjectManager::getInstance()->get(MetadataPool::class);
- }
- return $this->metadataPool;
- }
}
diff --git a/app/code/Magento/Catalog/CustomerData/CompareProducts.php b/app/code/Magento/Catalog/CustomerData/CompareProducts.php
index bdac4dfde64d1..0039e48ac7ed3 100644
--- a/app/code/Magento/Catalog/CustomerData/CompareProducts.php
+++ b/app/code/Magento/Catalog/CustomerData/CompareProducts.php
@@ -9,6 +9,7 @@
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Catalog Product Compare Widget
@@ -37,22 +38,30 @@ class CompareProducts implements SectionSourceInterface
*/
private $scopeConfig;
+ /**
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
+
/**
* @param \Magento\Catalog\Helper\Product\Compare $helper
* @param \Magento\Catalog\Model\Product\Url $productUrl
* @param \Magento\Catalog\Helper\Output $outputHelper
* @param ScopeConfigInterface|null $scopeConfig
+ * @param StoreManagerInterface|null $storeManager
*/
public function __construct(
\Magento\Catalog\Helper\Product\Compare $helper,
\Magento\Catalog\Model\Product\Url $productUrl,
\Magento\Catalog\Helper\Output $outputHelper,
- ?ScopeConfigInterface $scopeConfig = null
+ ?ScopeConfigInterface $scopeConfig = null,
+ ?StoreManagerInterface $storeManager = null
) {
$this->helper = $helper;
$this->productUrl = $productUrl;
$this->outputHelper = $outputHelper;
$this->scopeConfig = $scopeConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class);
+ $this->storeManager = $storeManager ?? ObjectManager::getInstance()->get(StoreManagerInterface::class);
}
/**
@@ -66,6 +75,7 @@ public function getSectionData()
'countCaption' => $count == 1 ? __('1 item') : __('%1 items', $count),
'listUrl' => $this->helper->getListUrl(),
'items' => $count ? $this->getItems() : [],
+ 'websiteId' => $this->storeManager->getWebsite()->getId()
];
}
diff --git a/app/code/Magento/Catalog/Helper/Product/Compare.php b/app/code/Magento/Catalog/Helper/Product/Compare.php
index 4e476fe8d1568..1bc7388f4e9e6 100644
--- a/app/code/Magento/Catalog/Helper/Product/Compare.php
+++ b/app/code/Magento/Catalog/Helper/Product/Compare.php
@@ -41,35 +41,35 @@ class Compare extends \Magento\Framework\Url\Helper\Data
protected $_allowUsedFlat = true;
/**
- * Customer id
+ * Customer id for Compare Helper
*
* @var null|int
*/
protected $_customerId = null;
/**
- * Catalog session
+ * Catalog session for Compare Helper
*
* @var \Magento\Catalog\Model\Session
*/
protected $_catalogSession;
/**
- * Customer session
+ * Customer session for Compare Helper
*
* @var \Magento\Customer\Model\Session
*/
protected $_customerSession;
/**
- * Customer visitor
+ * Customer visitor for Compare Helper
*
* @var \Magento\Customer\Model\Visitor
*/
protected $_customerVisitor;
/**
- * Catalog product visibility
+ * Catalog product visibility for Compare Helper
*
* @var \Magento\Catalog\Model\Product\Visibility
*/
@@ -297,7 +297,11 @@ public function getItemCollection()
$this->_itemCollection->addAttributeToSelect('name')->addUrlRewrite()->load();
/* update compare items count */
- $this->_catalogSession->setCatalogCompareItemsCount(count($this->_itemCollection));
+ $count = count($this->_itemCollection);
+ $counts = $this->_catalogSession->getCatalogCompareItemsCountPerWebsite() ?: [];
+ $counts[$this->_storeManager->getWebsite()->getId()] = $count;
+ $this->_catalogSession->setCatalogCompareItemsCountPerWebsite($counts);
+ $this->_catalogSession->setCatalogCompareItemsCount($count); //deprecated
}
return $this->_itemCollection;
@@ -327,7 +331,10 @@ public function calculate($logout = false)
->setVisibility($this->_catalogProductVisibility->getVisibleInSiteIds());
$count = $collection->getSize();
- $this->_catalogSession->setCatalogCompareItemsCount($count);
+ $counts = $this->_catalogSession->getCatalogCompareItemsCountPerWebsite() ?: [];
+ $counts[$this->_storeManager->getWebsite()->getId()] = $count;
+ $this->_catalogSession->setCatalogCompareItemsCountPerWebsite($counts);
+ $this->_catalogSession->setCatalogCompareItemsCount($count); //deprecated
return $this;
}
@@ -339,11 +346,12 @@ public function calculate($logout = false)
*/
public function getItemCount()
{
- if (!$this->_catalogSession->hasCatalogCompareItemsCount()) {
+ $counts = $this->_catalogSession->getCatalogCompareItemsCountPerWebsite() ?: [];
+ if (!isset($counts[$this->_storeManager->getWebsite()->getId()])) {
$this->calculate();
}
- return $this->_catalogSession->getCatalogCompareItemsCount();
+ return $counts[$this->_storeManager->getWebsite()->getId()] ?? 0;
}
/**
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/Model/CategoryManagement.php b/app/code/Magento/Catalog/Model/CategoryManagement.php
index 1b6c08b86577c..c1a0a2668d24a 100644
--- a/app/code/Magento/Catalog/Model/CategoryManagement.php
+++ b/app/code/Magento/Catalog/Model/CategoryManagement.php
@@ -128,7 +128,7 @@ public function move($categoryId, $parentId, $afterId = null)
$afterId = ($afterId === null || $afterId > $lastId) ? $lastId : $afterId;
}
$parentPath = $parentCategory->getPath() ?? '';
- $path = $model->getPath();
+ $path = $model->getPath() . '/';
if ($path && strpos($parentPath, $path) === 0) {
throw new \Magento\Framework\Exception\LocalizedException(
__('Operation do not allow to move a parent category to any of children category')
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php
index 8e4eda70b7083..e69ab504880ef 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php
@@ -164,13 +164,15 @@ public function __construct(
*/
abstract public function execute($ids);
+ // phpcs:disable
/**
* Synchronize data between index storage and original storage
*
* @param array $processIds
* @return AbstractAction
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
- * @deprecated 102.0.6 Used only for backward compatibility for indexer, which not support indexation by dimensions
+ * @deprecated 102.0.6
+ * @see Used only for backward compatibility for indexer, which not support indexation by dimensions
*/
protected function _syncData(array $processIds = [])
{
@@ -195,6 +197,7 @@ protected function _syncData(array $processIds = [])
return $this;
}
+ // phpcs:enable
/**
* Prepare website current dates table
*
@@ -386,31 +389,33 @@ protected function _reindexRows($changedIds = [])
$changedIds = array_unique(array_merge($changedIds, ...array_values($parentProductsTypes)));
$productsTypes = array_merge_recursive($productsTypes, $parentProductsTypes);
- if ($changedIds) {
+ $typeIndexers = array_filter(
+ $this->getTypeIndexers(),
+ function ($type) use ($productsTypes) {
+ return isset($productsTypes[$type]) && !empty($productsTypes[$type]);
+ },
+ ARRAY_FILTER_USE_KEY
+ );
+ if (empty($typeIndexers)) {
$this->deleteIndexData($changedIds);
+ return $changedIds;
}
- $typeIndexers = $this->getTypeIndexers();
foreach ($typeIndexers as $productType => $indexer) {
- $entityIds = $productsTypes[$productType] ?? [];
- if (empty($entityIds)) {
- continue;
- }
-
+ $entityIds = $productsTypes[$productType];
if ($indexer instanceof DimensionalIndexerInterface) {
foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
$this->tableMaintainer->createMainTmpTable($dimensions);
$temporaryTable = $this->tableMaintainer->getMainTmpTable($dimensions);
$this->_emptyTable($temporaryTable);
$indexer->executeByDimensions($dimensions, \SplFixedArray::fromArray($entityIds, false));
- // copy to index
- $this->_insertFromTable(
- $temporaryTable,
- $this->tableMaintainer->getMainTableByDimensions($dimensions)
- );
+ $mainTable = $this->tableMaintainer->getMainTableByDimensions($dimensions);
+ $this->_insertFromTable($temporaryTable, $mainTable);
+ $this->deleteOutdatedData($entityIds, $temporaryTable, $mainTable);
}
} else {
// handle 3d-party indexers for backward compatibility
+ $this->deleteIndexData($changedIds);
$this->_emptyTable($this->_defaultIndexerResource->getIdxTable());
$this->_copyRelationIndexData($entityIds);
$indexer->reindexEntity($entityIds);
@@ -421,6 +426,30 @@ protected function _reindexRows($changedIds = [])
return $changedIds;
}
+ /**
+ * Delete records that do not exist anymore
+ *
+ * @param array $entityIds
+ * @param string $temporaryTable
+ * @param string $mainTable
+ * @return void
+ */
+ private function deleteOutdatedData(array $entityIds, string $temporaryTable, string $mainTable): void
+ {
+ $joinCondition = [
+ 'tmp_table.entity_id = main_table.entity_id',
+ 'tmp_table.customer_group_id = main_table.customer_group_id',
+ 'tmp_table.website_id = main_table.website_id',
+ ];
+ $select = $this->getConnection()->select()
+ ->from(['main_table' => $mainTable], null)
+ ->joinLeft(['tmp_table' => $temporaryTable], implode(' AND ', $joinCondition), null)
+ ->where('tmp_table.entity_id IS NULL')
+ ->where('main_table.entity_id IN (?)', $entityIds, \Zend_Db::INT_TYPE);
+ $query = $select->deleteFromSelect('main_table');
+ $this->getConnection()->query($query);
+ }
+
/**
* Delete Index data index for list of entities
*
@@ -429,6 +458,7 @@ protected function _reindexRows($changedIds = [])
*/
private function deleteIndexData(array $entityIds)
{
+ $entityIds = array_unique(array_map('intval', $entityIds));
foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
$select = $this->getConnection()->select()->from(
['index_price' => $this->tableMaintainer->getMainTableByDimensions($dimensions)],
@@ -439,13 +469,15 @@ private function deleteIndexData(array $entityIds)
}
}
+ // phpcs:disable
/**
* Copy relations product index from primary index to temporary index table by parent entity
*
* @param null|array $parentIds
* @param array $excludeIds
* @return \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction
- * @deprecated 102.0.6 Used only for backward compatibility for do not broke custom indexer implementation
+ * @deprecated 102.0.6
+ * @see Used only for backward compatibility for do not broke custom indexer implementation
* which do not work by dimensions.
* For indexers, which support dimensions all composite products read data directly from main price indexer table
* or replica table for partial or full reindex correspondingly.
@@ -488,6 +520,7 @@ protected function _copyRelationIndexData($parentIds, $excludeIds = null)
return $this;
}
+ // phpcs:enable
/**
* Retrieve index table by dimension that will be used for write operations.
*
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
index a562b3203490c..2ff5b14fcb9e0 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
@@ -6,6 +6,7 @@
*/
namespace Magento\Catalog\Model\Product\Attribute;
+use Laminas\Validator\Regex;
use Magento\Eav\Api\Data\AttributeInterface;
use Magento\Eav\Model\Entity\Attribute;
use Magento\Framework\Exception\InputException;
@@ -225,7 +226,7 @@ protected function generateCode($label)
0,
Attribute::ATTRIBUTE_CODE_MAX_LENGTH
);
- $validatorAttrCode = new \Zend_Validate_Regex(['pattern' => '/^[a-z][a-z_0-9]{0,29}[a-z0-9]$/']);
+ $validatorAttrCode = new Regex(['pattern' => '/^[a-z][a-z_0-9]{0,29}[a-z0-9]$/']);
if (!$validatorAttrCode->isValid($code)) {
$code = 'attr_' . ($code ?: substr(hash('sha256', time()), 0, 8));
}
@@ -238,11 +239,11 @@ protected function generateCode($label)
*
* @param string $code
* @return void
- * @throws \Magento\Framework\Exception\InputException
+ * @throws InputException
*/
protected function validateCode($code)
{
- $validatorAttrCode = new \Zend_Validate_Regex(
+ $validatorAttrCode = new Regex(
['pattern' => '/^[a-z][a-z_0-9]{0,' . Attribute::ATTRIBUTE_CODE_MAX_LENGTH . '}$/']
);
if (!$validatorAttrCode->isValid($code)) {
@@ -255,7 +256,7 @@ protected function validateCode($code)
*
* @param string $frontendInput
* @return void
- * @throws \Magento\Framework\Exception\InputException
+ * @throws InputException
*/
protected function validateFrontendInput($frontendInput)
{
diff --git a/app/code/Magento/Catalog/Model/Product/Filter/DateTime.php b/app/code/Magento/Catalog/Model/Product/Filter/DateTime.php
index 93b7d55458a9f..7c216319f9ec8 100644
--- a/app/code/Magento/Catalog/Model/Product/Filter/DateTime.php
+++ b/app/code/Magento/Catalog/Model/Product/Filter/DateTime.php
@@ -7,13 +7,14 @@
namespace Magento\Catalog\Model\Product\Filter;
+use Laminas\Filter\FilterInterface;
use Magento\Framework\Stdlib\DateTime as StdlibDateTime;
use Magento\Framework\Stdlib\DateTime\Filter\DateTime as StdlibDateTimeFilter;
/**
* Product datetime fields values filter
*/
-class DateTime implements \Zend_Filter_Interface
+class DateTime implements FilterInterface
{
/**
* @var StdlibDateTimeFilter
diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php b/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php
index f08e053720b0a..a5572e787250e 100644
--- a/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php
+++ b/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php
@@ -8,10 +8,18 @@
namespace Magento\Catalog\Model\Product\Gallery;
use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\Media\Config;
+use Magento\Catalog\Model\ResourceModel\Product\Gallery;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\EntityManager\Operation\ExtensionInterface;
+use Magento\Framework\Exception\FileSystemException;
+use Magento\Framework\Filesystem;
+use Magento\Framework\Json\Helper\Data;
+use Magento\MediaStorage\Helper\File\Storage\Database;
use Magento\MediaStorage\Model\File\Uploader as FileUploader;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
@@ -89,6 +97,11 @@ class CreateHandler implements ExtensionInterface
*/
private $storeManager;
+ /**
+ * @var DeleteValidator
+ */
+ private $deleteValidator;
+
/**
* @var string[]
*/
@@ -99,25 +112,27 @@ class CreateHandler implements ExtensionInterface
];
/**
- * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
- * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository
- * @param \Magento\Catalog\Model\ResourceModel\Product\Gallery $resourceModel
- * @param \Magento\Framework\Json\Helper\Data $jsonHelper
- * @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig
- * @param \Magento\Framework\Filesystem $filesystem
- * @param \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb
- * @param \Magento\Store\Model\StoreManagerInterface|null $storeManager
- * @throws \Magento\Framework\Exception\FileSystemException
+ * @param MetadataPool $metadataPool
+ * @param ProductAttributeRepositoryInterface $attributeRepository
+ * @param Gallery $resourceModel
+ * @param Data $jsonHelper
+ * @param Config $mediaConfig
+ * @param Filesystem $filesystem
+ * @param Database $fileStorageDb
+ * @param StoreManagerInterface|null $storeManager
+ * @param DeleteValidator|null $deleteValidator
+ * @throws FileSystemException
*/
public function __construct(
- \Magento\Framework\EntityManager\MetadataPool $metadataPool,
- \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository,
- \Magento\Catalog\Model\ResourceModel\Product\Gallery $resourceModel,
- \Magento\Framework\Json\Helper\Data $jsonHelper,
- \Magento\Catalog\Model\Product\Media\Config $mediaConfig,
- \Magento\Framework\Filesystem $filesystem,
- \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb,
- \Magento\Store\Model\StoreManagerInterface $storeManager = null
+ MetadataPool $metadataPool,
+ ProductAttributeRepositoryInterface $attributeRepository,
+ Gallery $resourceModel,
+ Data $jsonHelper,
+ Config $mediaConfig,
+ Filesystem $filesystem,
+ Database $fileStorageDb,
+ StoreManagerInterface $storeManager = null,
+ ?DeleteValidator $deleteValidator = null
) {
$this->metadata = $metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
$this->attributeRepository = $attributeRepository;
@@ -127,6 +142,7 @@ public function __construct(
$this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
$this->fileStorageDb = $fileStorageDb;
$this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManagerInterface::class);
+ $this->deleteValidator = $deleteValidator ?: ObjectManager::getInstance()->get(DeleteValidator::class);
}
/**
@@ -165,7 +181,7 @@ public function execute($product, $arguments = [])
if ($product->getIsDuplicate() != true) {
foreach ($value['images'] as &$image) {
- if (!empty($image['removed']) && !$this->canRemoveImage($product, $image['file'])) {
+ if (!empty($image['removed']) && $this->deleteValidator->validate($product, $image['file'])) {
$image['removed'] = '';
}
@@ -184,7 +200,7 @@ public function execute($product, $arguments = [])
// For duplicating we need copy original images.
$duplicate = [];
foreach ($value['images'] as &$image) {
- if (!empty($image['removed']) && !$this->canRemoveImage($product, $image['file'])) {
+ if (!empty($image['removed']) && $this->deleteValidator->validate($product, $image['file'])) {
$image['removed'] = '';
}
@@ -608,41 +624,6 @@ private function getImagesForAllStores(ProductInterface $product)
return $this->imagesGallery;
}
- /**
- * Check possibility to remove image
- *
- * @param ProductInterface $product
- * @param string $imageFile
- * @return bool
- */
- private function canRemoveImage(ProductInterface $product, string $imageFile) :bool
- {
- $canRemoveImage = true;
- $gallery = $this->getImagesForAllStores($product);
- $storeId = $product->getStoreId();
- $storeIds = [];
- $storeIds[] = 0;
- $websiteIds = array_map('intval', $product->getWebsiteIds() ?? []);
- foreach ($this->storeManager->getStores() as $store) {
- if (in_array((int) $store->getWebsiteId(), $websiteIds, true)) {
- $storeIds[] = (int) $store->getId();
- }
- }
-
- if (!empty($gallery)) {
- foreach ($gallery as $image) {
- if (in_array((int) $image['store_id'], $storeIds)
- && $image['filepath'] === $imageFile
- && (int) $image['store_id'] !== $storeId
- ) {
- $canRemoveImage = false;
- }
- }
- }
-
- return $canRemoveImage;
- }
-
/**
* Get media attribute value for store view
*
diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/DeleteValidator.php b/app/code/Magento/Catalog/Model/Product/Gallery/DeleteValidator.php
new file mode 100644
index 0000000000000..845a1dc66dc97
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Gallery/DeleteValidator.php
@@ -0,0 +1,84 @@
+resourceModel = $resourceModel;
+ }
+
+ /**
+ * Validates media image for removal
+ *
+ * @param ProductInterface $product
+ * @param string $imageFile
+ * @return Phrase[]
+ */
+ public function validate(ProductInterface $product, string $imageFile): array
+ {
+ $errors = [];
+ if (count($product->getStoreIds()) > 1) {
+ if (in_array($imageFile, $this->getImagesWithRolesInOtherStores($product))) {
+ $errors[] = __('The image cannot be removed as it has been assigned to the other image role');
+ }
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Returns all images that are assigned to a role in store views other than the current store view
+ *
+ * @param ProductInterface $product
+ * @return array
+ */
+ private function getImagesWithRolesInOtherStores(ProductInterface $product): array
+ {
+ if ($this->product !== $product || !$this->imagesWithRolesInOtherStoresCache) {
+ $this->product = $product;
+ $storeIds = array_diff(
+ array_merge($product->getStoreIds(), [Store::DEFAULT_STORE_ID]),
+ [$product->getStoreId()]
+ );
+ $this->imagesWithRolesInOtherStoresCache = array_column(
+ $this->resourceModel->getProductImages($product, $storeIds),
+ 'filepath'
+ );
+ }
+ return $this->imagesWithRolesInOtherStoresCache;
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php
index 6a078a915119c..1105960e36d82 100644
--- a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php
+++ b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php
@@ -7,6 +7,9 @@
namespace Magento\Catalog\Model\Product\Gallery;
use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface;
+use Magento\Catalog\Api\Data\ProductInterfaceFactory;
+use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\StateException;
@@ -32,17 +35,34 @@ class GalleryManagement implements \Magento\Catalog\Api\ProductAttributeMediaGal
protected $contentValidator;
/**
- * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
+ * @var ProductInterfaceFactory
+ */
+ private $productInterfaceFactory;
+
+ /**
+ * @var DeleteValidator
+ */
+ private $deleteValidator;
+
+ /**
+ * @param ProductRepositoryInterface $productRepository
* @param ImageContentValidatorInterface $contentValidator
- *
+ * @param ProductInterfaceFactory|null $productInterfaceFactory
+ * @param DeleteValidator|null $deleteValidator
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
- \Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
- ImageContentValidatorInterface $contentValidator
+ ProductRepositoryInterface $productRepository,
+ ImageContentValidatorInterface $contentValidator,
+ ?ProductInterfaceFactory $productInterfaceFactory = null,
+ ?DeleteValidator $deleteValidator = null
) {
$this->productRepository = $productRepository;
$this->contentValidator = $contentValidator;
+ $this->productInterfaceFactory = $productInterfaceFactory
+ ?? ObjectManager::getInstance()->get(ProductInterfaceFactory::class);
+ $this->deleteValidator = $deleteValidator
+ ?? ObjectManager::getInstance()->get(DeleteValidator::class);
}
/**
@@ -72,6 +92,8 @@ public function create($sku, ProductAttributeMediaGalleryEntryInterface $entry)
}
$existingMediaGalleryEntries[] = $entry;
}
+ $product = $this->productInterfaceFactory->create();
+ $product->setSku($sku);
$product->setMediaGalleryEntries($existingMediaGalleryEntries);
try {
$product = $this->productRepository->save($product);
@@ -119,6 +141,8 @@ public function update($sku, ProductAttributeMediaGalleryEntryInterface $entry)
__('No image with the provided ID was found. Verify the ID and try again.')
);
}
+ $product = $this->productInterfaceFactory->create();
+ $product->setSku($sku);
$product->setMediaGalleryEntries($existingMediaGalleryEntries);
try {
@@ -145,6 +169,10 @@ public function remove($sku, $entryId)
foreach ($existingMediaGalleryEntries as $key => $entry) {
if ($entry->getId() == $entryId) {
unset($existingMediaGalleryEntries[$key]);
+ $errors = $this->deleteValidator->validate($product, $entry->getFile());
+ if (!empty($errors)) {
+ throw new StateException($errors[0]);
+ }
$found = true;
break;
}
@@ -154,6 +182,8 @@ public function remove($sku, $entryId)
__('No image with the provided ID was found. Verify the ID and try again.')
);
}
+ $product = $this->productInterfaceFactory->create();
+ $product->setSku($sku);
$product->setMediaGalleryEntries($existingMediaGalleryEntries);
$this->productRepository->save($product);
return true;
diff --git a/app/code/Magento/Catalog/Model/Product/Option.php b/app/code/Magento/Catalog/Model/Product/Option.php
index 828d634fbd88d..a2d2e82149c34 100644
--- a/app/code/Magento/Catalog/Model/Product/Option.php
+++ b/app/code/Magento/Catalog/Model/Product/Option.php
@@ -16,10 +16,8 @@
use Magento\Catalog\Model\Product\Option\Type\File;
use Magento\Catalog\Model\Product\Option\Type\Select;
use Magento\Catalog\Model\Product\Option\Type\Text;
-use Magento\Catalog\Model\Product\Option\Value;
use Magento\Catalog\Model\ResourceModel\Product\Option\Value\Collection;
use Magento\Catalog\Pricing\Price\BasePrice;
-use Magento\Catalog\Pricing\Price\CalculateCustomOptionCatalogRule;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Exception\LocalizedException;
@@ -130,11 +128,6 @@ class Option extends AbstractExtensibleModel implements ProductCustomOptionInter
*/
private $customOptionValuesFactory;
- /**
- * @var CalculateCustomOptionCatalogRule
- */
- private $calculateCustomOptionCatalogRule;
-
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -150,7 +143,6 @@ class Option extends AbstractExtensibleModel implements ProductCustomOptionInter
* @param ProductCustomOptionValuesInterfaceFactory|null $customOptionValuesFactory
* @param array $optionGroups
* @param array $optionTypesToGroups
- * @param CalculateCustomOptionCatalogRule|null $calculateCustomOptionCatalogRule
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -167,8 +159,7 @@ public function __construct(
array $data = [],
ProductCustomOptionValuesInterfaceFactory $customOptionValuesFactory = null,
array $optionGroups = [],
- array $optionTypesToGroups = [],
- CalculateCustomOptionCatalogRule $calculateCustomOptionCatalogRule = null
+ array $optionTypesToGroups = []
) {
$this->productOptionValue = $productOptionValue;
$this->optionTypeFactory = $optionFactory;
@@ -176,8 +167,6 @@ public function __construct(
$this->validatorPool = $validatorPool;
$this->customOptionValuesFactory = $customOptionValuesFactory ?:
ObjectManager::getInstance()->get(ProductCustomOptionValuesInterfaceFactory::class);
- $this->calculateCustomOptionCatalogRule = $calculateCustomOptionCatalogRule ??
- ObjectManager::getInstance()->get(CalculateCustomOptionCatalogRule::class);
$this->optionGroups = $optionGroups ?: [
self::OPTION_GROUP_DATE => Date::class,
self::OPTION_GROUP_FILE => File::class,
@@ -212,7 +201,8 @@ public function __construct(
* Get resource instance
*
* @return \Magento\Framework\Model\ResourceModel\Db\AbstractDb
- * @deprecated 102.0.0 because resource models should be used directly
+ * @deprecated 102.0.0
+ * @see resource models should be used directly
*/
protected function _getResource()
{
@@ -478,21 +468,10 @@ public function afterSave()
*/
public function getPrice($flag = false)
{
- if ($flag && $this->getPriceType() === self::$typePercent) {
- $price = $this->calculateCustomOptionCatalogRule->execute(
- $this->getProduct(),
- (float)$this->getData(self::KEY_PRICE),
- $this->getPriceType() === Value::TYPE_PERCENT
- );
-
- if ($price === null) {
- $basePrice = $this->getProduct()->getPriceInfo()->getPrice(BasePrice::PRICE_CODE)->getValue();
- $price = $basePrice * ($this->_getData(self::KEY_PRICE) / 100);
- }
-
- return $price;
+ if ($flag && $this->getPriceType() == self::$typePercent) {
+ $basePrice = $this->getProduct()->getPriceInfo()->getPrice(BasePrice::PRICE_CODE)->getValue();
+ return $basePrice * ($this->_getData(self::KEY_PRICE) / 100);
}
-
return $this->_getData(self::KEY_PRICE);
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/DefaultType.php b/app/code/Magento/Catalog/Model/Product/Option/Type/DefaultType.php
index e819f36b5cf7d..225f1bb3d10e3 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/DefaultType.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/DefaultType.php
@@ -13,8 +13,6 @@
use Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface;
use Magento\Catalog\Model\Product\Configuration\Item\ItemInterface;
use Magento\Catalog\Model\Product\Option\Value;
-use Magento\Catalog\Pricing\Price\CalculateCustomOptionCatalogRule;
-use Magento\Framework\App\ObjectManager;
/**
* Catalog product option default type
@@ -49,43 +47,30 @@ class DefaultType extends \Magento\Framework\DataObject
protected $_productOptions = [];
/**
- * Core store config
- *
* @var \Magento\Framework\App\Config\ScopeConfigInterface
*/
protected $_scopeConfig;
/**
- * Checkout session
- *
* @var \Magento\Checkout\Model\Session
*/
protected $_checkoutSession;
- /**
- * @var CalculateCustomOptionCatalogRule
- */
- private $calculateCustomOptionCatalogRule;
-
/**
* Construct
*
* @param \Magento\Checkout\Model\Session $checkoutSession
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
* @param array $data
- * @param CalculateCustomOptionCatalogRule|null $calculateCustomOptionCatalogRule
*/
public function __construct(
\Magento\Checkout\Model\Session $checkoutSession,
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
- array $data = [],
- CalculateCustomOptionCatalogRule $calculateCustomOptionCatalogRule = null
+ array $data = []
) {
$this->_checkoutSession = $checkoutSession;
parent::__construct($data);
$this->_scopeConfig = $scopeConfig;
- $this->calculateCustomOptionCatalogRule = $calculateCustomOptionCatalogRule ?? ObjectManager::getInstance()
- ->get(CalculateCustomOptionCatalogRule::class);
}
/**
@@ -352,20 +337,11 @@ public function getOptionPrice($optionValue, $basePrice)
{
$option = $this->getOption();
- $catalogPriceValue = $this->calculateCustomOptionCatalogRule->execute(
- $option->getProduct(),
- (float)$option->getPrice(),
- $option->getPriceType() === Value::TYPE_PERCENT
+ return $this->_getChargeableOptionPrice(
+ $option->getPrice(),
+ $option->getPriceType() === Value::TYPE_PERCENT,
+ $basePrice
);
- if ($catalogPriceValue !== null) {
- return $catalogPriceValue;
- } else {
- return $this->_getChargeableOptionPrice(
- $option->getPrice(),
- $option->getPriceType() === Value::TYPE_PERCENT,
- $basePrice
- );
- }
}
/**
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ExistingValidate.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ExistingValidate.php
index c9afdf023b307..be85af2f7161c 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ExistingValidate.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ExistingValidate.php
@@ -8,45 +8,67 @@
namespace Magento\Catalog\Model\Product\Option\Type\File;
+use Laminas\Validator\ValidatorChain;
+
/**
* Validator for existing (already saved) files.
*/
-class ExistingValidate extends \Zend_Validate
+class ExistingValidate extends ValidatorChain
{
+ /**
+ * @var array
+ */
+ private $errors = [];
+
/**
* @inheritDoc
*
* @param string $value File's full path.
* @param string|null $originalName Original file's name (when uploaded).
*/
- public function isValid($value, string $originalName = null)
+ public function isValid($value, $originalName = null)
{
- $this->_messages = [];
- $this->_errors = [];
+ $this->messages = [];
if (!is_string($value)) {
- $this->_messages[] = __('Full file path is expected.')->render();
+ $this->messages[] = __('Full file path is expected.')->render();
return false;
}
$result = true;
$fileInfo = null;
if ($originalName) {
- $fileInfo = ['name' => $originalName];
+ $fileInfo = ['name' => $originalName, 'tmp_name'=> $value];
}
- foreach ($this->_validators as $element) {
+ $messagesArray = $errorsArray = [];
+
+ foreach ($this->validators as $element) {
$validator = $element['instance'];
+
if ($validator->isValid($value, $fileInfo)) {
continue;
}
$result = false;
$messages = $validator->getMessages();
- $this->_messages = array_merge($this->_messages, $messages);
- $this->_errors = array_merge($this->_errors, array_keys($messages));
+ $messagesArray[] = $messages;
+ $errorsArray[] = array_keys($messages);
if ($element['breakChainOnFailure']) {
break;
}
}
+ $this->messages = array_merge($this->messages, ...$messagesArray);
+ $this->errors = array_merge($this->errors, ...$errorsArray);
+
return $result;
}
+
+ /**
+ * Returns array of validation failure message codes
+ *
+ * @return array
+ */
+ public function getErrors()
+ {
+ return $this->errors;
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/Validator.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/Validator.php
index 2ff73ff92308d..fea8768d94d76 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/Validator.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/Validator.php
@@ -6,7 +6,13 @@
namespace Magento\Catalog\Model\Product\Option\Type\File;
+use Laminas\Validator\File\ExcludeExtension;
+use Laminas\Validator\File\Extension;
+use Laminas\Validator\File\FilesSize;
+use Laminas\Validator\File\ImageSize;
+use Laminas\Validator\ValidatorChain;
use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\File\Http;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -60,7 +66,7 @@ protected function getConfigData($key)
/**
* Get Error messages for validator Errors
*
- * @param string[] $errors Array of validation failure message codes @see \Zend_Validate::getErrors()
+ * @param string[] $errors Array of validation failure message codes @see ValidatorChain::getErrors()
* @param array $fileInfo File info
* @param \Magento\Catalog\Model\Product\Option $option
* @return string[] Array of error messages
@@ -71,22 +77,22 @@ protected function getValidatorErrors($errors, $fileInfo, $option)
$result = [];
foreach ($errors as $errorCode) {
switch ($errorCode) {
- case \Zend_Validate_File_ExcludeExtension::FALSE_EXTENSION:
+ case ExcludeExtension::FALSE_EXTENSION:
$result[] = __(
"The file '%1' for '%2' has an invalid extension.",
$fileInfo['title'],
$option->getTitle()
);
break;
- case \Zend_Validate_File_Extension::FALSE_EXTENSION:
+ case Extension::FALSE_EXTENSION:
$result[] = __(
"The file '%1' for '%2' has an invalid extension.",
$fileInfo['title'],
$option->getTitle()
);
break;
- case \Zend_Validate_File_ImageSize::WIDTH_TOO_BIG:
- case \Zend_Validate_File_ImageSize::HEIGHT_TOO_BIG:
+ case ImageSize::WIDTH_TOO_BIG:
+ case ImageSize::HEIGHT_TOO_BIG:
$result[] = __(
"The maximum allowed image size for '%1' is %2x%3 px.",
$option->getTitle(),
@@ -94,14 +100,14 @@ protected function getValidatorErrors($errors, $fileInfo, $option)
$option->getImageSizeY()
);
break;
- case \Zend_Validate_File_FilesSize::TOO_BIG:
+ case FilesSize::TOO_BIG:
$result[] = __(
"The file '%1' you uploaded is larger than the %2 megabytes allowed by our server.",
$fileInfo['title'],
$this->fileSize->getMaxFileSizeInMb()
);
break;
- case \Zend_Validate_File_ImageSize::NOT_DETECTED:
+ case ImageSize::NOT_DETECTED:
$result[] = __(
'The file "%1" is empty. Select another file and try again.',
$fileInfo['title']
@@ -137,10 +143,10 @@ protected function parseExtensionsString($extensions)
/**
* Adds required validators to th $object
*
- * @param \Zend_File_Transfer_Adapter_Http|\Zend_Validate $object
+ * @param Http|ValidatorChain $object
* @param \Magento\Catalog\Model\Product\Option $option
* @param array $fileFullPath
- * @return \Zend_File_Transfer_Adapter_Http|\Zend_Validate $object
+ * @return Http|ValidatorChain $object
* @throws \Magento\Framework\Exception\InputException
*/
protected function buildImageValidator($object, $option, $fileFullPath = null)
@@ -159,22 +165,22 @@ protected function buildImageValidator($object, $option, $fileFullPath = null)
__('File \'%1\' is not an image.', $option->getTitle())
);
}
- $object->addValidator(new \Zend_Validate_File_ImageSize($dimensions));
+ $object->addValidator(new ImageSize($dimensions));
}
// File extension
$allowed = $this->parseExtensionsString($option->getFileExtension());
if ($allowed !== null) {
- $object->addValidator(new \Zend_Validate_File_Extension($allowed));
+ $object->addValidator(new Extension($allowed));
} else {
$forbidden = $this->parseExtensionsString($this->getConfigData('forbidden_extensions'));
if ($forbidden !== null) {
- $object->addValidator(new \Zend_Validate_File_ExcludeExtension($forbidden));
+ $object->addValidator(new ExcludeExtension($forbidden));
}
}
$object->addValidator(
- new \Zend_Validate_File_FilesSize(['max' => $this->fileSize->getMaxFileSize()])
+ new FilesSize(['max' => $this->fileSize->getMaxFileSize()])
);
return $object;
}
@@ -182,7 +188,7 @@ protected function buildImageValidator($object, $option, $fileFullPath = null)
/**
* Simple check if file is image
*
- * @param array|string $fileInfo - either file data from \Zend_File_Transfer or file path
+ * @param array|string $fileInfo - either file data from \Laminas\File\Transfer\Transfer or file path
* @return boolean
* @see \Magento\Catalog\Model\Product\Option\Type\File::_isImage
*/
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php
index e02b3ffd2b3c2..420ced5d877f6 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php
@@ -7,6 +7,8 @@
namespace Magento\Catalog\Model\Product\Option\Type\File;
+use Laminas\Filter\File\Rename;
+use Laminas\File\Transfer\Exception\PhpEnvironmentException;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Exception as ProductException;
use Magento\Framework\App\Filesystem\DirectoryList;
@@ -124,7 +126,7 @@ public function setProduct(Product $product)
* @throws \Exception
* @throws \Magento\Framework\Exception\InputException
* @throws \Magento\Framework\Validator\Exception
- * @throws \Zend_File_Transfer_Exception
+ * @throws PhpEnvironmentException
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
@@ -182,7 +184,7 @@ public function validate($processingParams, $option)
$filePath = $dispersion . '/' . $fileName;
$fileFullPath = $this->mediaDirectory->getAbsolutePath($this->quotePath . $filePath);
- $upload->addFilter(new \Zend_Filter_File_Rename(['target' => $fileFullPath, 'overwrite' => true]));
+ $upload->addFilter(new Rename(['target' => $fileFullPath, 'overwrite' => true]));
if ($this->product !== null) {
$this->product->getTypeInstance()->addFileQueue(
@@ -200,8 +202,10 @@ public function validate($processingParams, $option)
$_height = 0;
if ($tmpDirectory->isReadable($tmpDirectory->getRelativePath($fileInfo['tmp_name']))) {
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
if (filesize($fileInfo['tmp_name'])) {
if ($this->isImageValidator->isValid($fileInfo['tmp_name'])) {
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$imageSize = getimagesize($fileInfo['tmp_name']);
}
} else {
@@ -272,6 +276,7 @@ protected function initFilesystem()
*/
protected function validateContentLength()
{
+ // phpcs:disable Magento2.Security.Superglobal
return isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] > $this->fileSize->getMaxFileSize();
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php b/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php
index 286fa39a04ffe..b2c7fc79d84bd 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php
@@ -7,10 +7,7 @@
namespace Magento\Catalog\Model\Product\Option\Type;
use Magento\Catalog\Model\Product\Option\Value;
-use Magento\Catalog\Pricing\Price\CalculateCustomOptionCatalogRule;
-use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\LocalizedException;
-use Magento\Catalog\Model\Product\Option;
/**
* Catalog product option select type
@@ -41,11 +38,6 @@ class Select extends \Magento\Catalog\Model\Product\Option\Type\DefaultType
*/
private $singleSelectionTypes;
- /**
- * @var CalculateCustomOptionCatalogRule
- */
- private $calculateCustomOptionCatalogRule;
-
/**
* @param \Magento\Checkout\Model\Session $checkoutSession
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
@@ -53,7 +45,6 @@ class Select extends \Magento\Catalog\Model\Product\Option\Type\DefaultType
* @param \Magento\Framework\Escaper $escaper
* @param array $data
* @param array $singleSelectionTypes
- * @param CalculateCustomOptionCatalogRule|null $calculateCustomOptionCatalogRule
*/
public function __construct(
\Magento\Checkout\Model\Session $checkoutSession,
@@ -61,8 +52,7 @@ public function __construct(
\Magento\Framework\Stdlib\StringUtils $string,
\Magento\Framework\Escaper $escaper,
array $data = [],
- array $singleSelectionTypes = [],
- CalculateCustomOptionCatalogRule $calculateCustomOptionCatalogRule = null
+ array $singleSelectionTypes = []
) {
$this->string = $string;
$this->_escaper = $escaper;
@@ -72,8 +62,6 @@ public function __construct(
'drop_down' => \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN,
'radio' => \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO,
];
- $this->calculateCustomOptionCatalogRule = $calculateCustomOptionCatalogRule ?? ObjectManager::getInstance()
- ->get(CalculateCustomOptionCatalogRule::class);
}
/**
@@ -90,19 +78,8 @@ public function validateUserValue($values)
$option = $this->getOption();
$value = $this->getUserValue();
- if (empty($value) && $option->getIsRequire() && !$this->getSkipCheckRequiredOption()) {
- $this->setIsValid(false);
- throw new LocalizedException(
- __("The product's required option(s) weren't entered. Make sure the options are entered and try again.")
- );
- }
- if (!$this->_isSingleSelection()) {
- if (is_string($value)) {
- $value = explode(',', $value);
- }
- $valuesCollection = $option->getOptionValuesByOptionId($value, $this->getProduct()->getStoreId())->load();
- $valueCount = is_array($value) ? count($value) : 0;
- if ($valuesCollection->count() != $valueCount) {
+ if (empty($value)) {
+ if ($option->getIsRequire() && !$this->getSkipCheckRequiredOption()) {
$this->setIsValid(false);
throw new LocalizedException(
__(
@@ -111,6 +88,21 @@ public function validateUserValue($values)
)
);
}
+ } else {
+ if (!$this->_isSingleSelection()) {
+ if (is_string($value)) {
+ $value = explode(',', $value);
+ }
+ $valuesCollection = $option->getOptionValuesByOptionId($value, $this->getProduct()->getStoreId());
+ $valueCount = is_array($value) ? count($value) : 0;
+ if ($valuesCollection->count() != $valueCount) {
+ $this->setIsValid(false);
+ throw new LocalizedException($this->_getWrongConfigurationMessage());
+ }
+ } elseif (!$option->getValueById($value)) {
+ $this->setIsValid(false);
+ throw new LocalizedException($this->_getWrongConfigurationMessage());
+ }
}
return $this;
}
@@ -261,7 +253,11 @@ public function getOptionPrice($optionValue, $basePrice)
foreach (explode(',', (string)$optionValue) as $value) {
$_result = $option->getValueById($value);
if ($_result) {
- $result += $this->getCalculatedOptionValue($option, $_result, $basePrice);
+ $result += $this->_getChargeableOptionPrice(
+ $_result->getPrice(),
+ $_result->getPriceType() === Value::TYPE_PERCENT,
+ $basePrice
+ );
} else {
if ($this->getListener()) {
$this->getListener()->setHasError(true)->setMessage($this->_getWrongConfigurationMessage());
@@ -272,20 +268,11 @@ public function getOptionPrice($optionValue, $basePrice)
} elseif ($this->_isSingleSelection()) {
$_result = $option->getValueById($optionValue);
if ($_result) {
- $catalogPriceValue = $this->calculateCustomOptionCatalogRule->execute(
- $option->getProduct(),
- (float)$_result->getPrice(),
- $_result->getPriceType() === Value::TYPE_PERCENT
+ $result = $this->_getChargeableOptionPrice(
+ $_result->getPrice(),
+ $_result->getPriceType() === Value::TYPE_PERCENT,
+ $basePrice
);
- if ($catalogPriceValue !== null) {
- $result = $catalogPriceValue;
- } else {
- $result = $this->_getChargeableOptionPrice(
- $_result->getPrice(),
- $_result->getPriceType() == 'percent',
- $basePrice
- );
- }
} else {
if ($this->getListener()) {
$this->getListener()->setHasError(true)->setMessage($this->_getWrongConfigurationMessage());
@@ -347,31 +334,4 @@ protected function _isSingleSelection()
{
return in_array($this->getOption()->getType(), $this->singleSelectionTypes, true);
}
-
- /**
- * Returns calculated price of option
- *
- * @param Option $option
- * @param Option\Value $result
- * @param float $basePrice
- * @return float
- */
- protected function getCalculatedOptionValue(Option $option, Value $result, float $basePrice) : float
- {
- $catalogPriceValue = $this->calculateCustomOptionCatalogRule->execute(
- $option->getProduct(),
- (float)$result->getPrice(),
- $result->getPriceType() === Value::TYPE_PERCENT
- );
- if ($catalogPriceValue !== null) {
- $optionCalculatedValue = $catalogPriceValue;
- } else {
- $optionCalculatedValue = $this->_getChargeableOptionPrice(
- $result->getPrice(),
- $result->getPriceType() === Value::TYPE_PERCENT,
- $basePrice
- );
- }
- return $optionCalculatedValue;
- }
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php
index 08455430ccac8..e2e23eb1bb6c7 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php
@@ -7,7 +7,7 @@
namespace Magento\Catalog\Model\Product\Option\Validator;
use Magento\Catalog\Model\Product\Option;
-use Zend_Validate_Exception;
+use Magento\Framework\Validator\ValidateException;
/**
* Product option default validator
@@ -15,15 +15,11 @@
class DefaultValidator extends \Magento\Framework\Validator\AbstractValidator
{
/**
- * Product option types
- *
* @var string[]
*/
protected $productOptionTypes;
/**
- * Price types
- *
* @var string[]
*/
protected $priceTypes;
@@ -66,7 +62,7 @@ public function __construct(
*
* @param \Magento\Catalog\Model\Product\Option $value
* @return boolean
- * @throws Zend_Validate_Exception If validation of $value is impossible
+ * @throws ValidateException If validation of $value is impossible
*/
public function isValid($value)
{
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/Pool.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/Pool.php
index 2256f031098f1..b7761b1a9f65a 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Validator/Pool.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/Pool.php
@@ -6,15 +6,17 @@
namespace Magento\Catalog\Model\Product\Option\Validator;
+use Laminas\Validator\ValidatorInterface;
+
class Pool
{
/**
- * @var \Zend_Validate_Interface
+ * @var ValidatorInterface
*/
protected $validators;
/**
- * @param \Zend_Validate_Interface[] $validators
+ * @param ValidatorInterface[] $validators
*/
public function __construct(array $validators)
{
@@ -25,7 +27,7 @@ public function __construct(array $validators)
* Get validator
*
* @param string $type
- * @return \Zend_Validate_Interface
+ * @return ValidatorInterface
*/
public function get($type)
{
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Value.php b/app/code/Magento/Catalog/Model/Product/Option/Value.php
index 12b418c33deec..be919daa13541 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Value.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Value.php
@@ -10,10 +10,9 @@
use Magento\Catalog\Model\Product\Option;
use Magento\Framework\Model\AbstractModel;
use Magento\Catalog\Pricing\Price\BasePrice;
-use Magento\Catalog\Pricing\Price\CalculateCustomOptionCatalogRule;
-use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Pricing\Price\CustomOptionPriceCalculator;
use Magento\Catalog\Pricing\Price\RegularPrice;
+use Magento\Framework\App\ObjectManager;
/**
* Catalog product option select type model
@@ -33,20 +32,22 @@ class Value extends AbstractModel implements \Magento\Catalog\Api\Data\ProductCu
/**
* Option type percent
*/
- const TYPE_PERCENT = 'percent';
+ public const TYPE_PERCENT = 'percent';
/**#@+
* Constants
*/
- const KEY_TITLE = 'title';
- const KEY_SORT_ORDER = 'sort_order';
- const KEY_PRICE = 'price';
- const KEY_PRICE_TYPE = 'price_type';
- const KEY_SKU = 'sku';
- const KEY_OPTION_TYPE_ID = 'option_type_id';
+ public const KEY_TITLE = 'title';
+ public const KEY_SORT_ORDER = 'sort_order';
+ public const KEY_PRICE = 'price';
+ public const KEY_PRICE_TYPE = 'price_type';
+ public const KEY_SKU = 'sku';
+ public const KEY_OPTION_TYPE_ID = 'option_type_id';
/**#@-*/
- /**#@-*/
+ /**
+ * @var array
+ */
protected $_values = [];
/**
@@ -60,8 +61,6 @@ class Value extends AbstractModel implements \Magento\Catalog\Api\Data\ProductCu
protected $_option;
/**
- * Value collection factory
- *
* @var \Magento\Catalog\Model\ResourceModel\Product\Option\Value\CollectionFactory
*/
protected $_valueCollectionFactory;
@@ -71,11 +70,6 @@ class Value extends AbstractModel implements \Magento\Catalog\Api\Data\ProductCu
*/
private $customOptionPriceCalculator;
- /**
- * @var CalculateCustomOptionCatalogRule
- */
- private $calculateCustomOptionCatalogRule;
-
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -84,7 +78,6 @@ class Value extends AbstractModel implements \Magento\Catalog\Api\Data\ProductCu
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
* @param array $data
* @param CustomOptionPriceCalculator|null $customOptionPriceCalculator
- * @param CalculateCustomOptionCatalogRule|null $calculateCustomOptionCatalogRule
*/
public function __construct(
\Magento\Framework\Model\Context $context,
@@ -93,14 +86,11 @@ public function __construct(
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
array $data = [],
- CustomOptionPriceCalculator $customOptionPriceCalculator = null,
- CalculateCustomOptionCatalogRule $calculateCustomOptionCatalogRule = null
+ CustomOptionPriceCalculator $customOptionPriceCalculator = null
) {
$this->_valueCollectionFactory = $valueCollectionFactory;
$this->customOptionPriceCalculator = $customOptionPriceCalculator
?? ObjectManager::getInstance()->get(CustomOptionPriceCalculator::class);
- $this->calculateCustomOptionCatalogRule = $calculateCustomOptionCatalogRule
- ?? ObjectManager::getInstance()->get(CalculateCustomOptionCatalogRule::class);
parent::__construct(
$context,
@@ -264,16 +254,7 @@ public function saveValues()
public function getPrice($flag = false)
{
if ($flag) {
- $catalogPriceValue = $this->calculateCustomOptionCatalogRule->execute(
- $this->getProduct(),
- (float)$this->getData(self::KEY_PRICE),
- $this->getPriceType() === self::TYPE_PERCENT
- );
- if ($catalogPriceValue!==null) {
- return $catalogPriceValue;
- } else {
- return $this->customOptionPriceCalculator->getOptionPriceByPriceCode($this, BasePrice::PRICE_CODE);
- }
+ return $this->customOptionPriceCalculator->getOptionPriceByPriceCode($this);
}
return $this->_getData(self::KEY_PRICE);
}
diff --git a/app/code/Magento/Catalog/Model/Product/Price/BasePriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/BasePriceStorage.php
index 1ff5b828445ec..6690f0fdc906b 100644
--- a/app/code/Magento/Catalog/Model/Product/Price/BasePriceStorage.php
+++ b/app/code/Magento/Catalog/Model/Product/Price/BasePriceStorage.php
@@ -175,6 +175,12 @@ public function update(array $prices)
$formattedPrices = $this->applyWebsitePrices($formattedPrices);
}
+ if ($priceAttribute !== null && $priceAttribute->isScopeGlobal()) {
+ foreach ($formattedPrices as &$price) {
+ $price['store_id'] = Store::DEFAULT_STORE_ID;
+ }
+ }
+
$this->getPricePersistence()->update($formattedPrices);
return $this->validationResult->getFailedItems();
diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceFactory.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceFactory.php
index e1a21b6b99519..f23ce0faf708c 100644
--- a/app/code/Magento/Catalog/Model/Product/Price/TierPriceFactory.php
+++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceFactory.php
@@ -95,7 +95,8 @@ public function create(array $rawPrice, $sku)
$price->setCustomerGroup(
$rawPrice['all_groups'] == $this->allGroupsId
? $this->allGroupsValue
- : $this->customerGroupRepository->getById($rawPrice['customer_group_id'])->getCode()
+ : ($rawPrice['customer_group_code']
+ ?? $this->customerGroupRepository->getById($rawPrice['customer_group_id'])->getCode())
);
$price->setQuantity($rawPrice['qty']);
diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php
index 7d458401c950e..4d4c33acbd5fe 100644
--- a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php
+++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php
@@ -11,6 +11,7 @@
use Magento\Catalog\Model\Indexer\Product\Price\Processor as PriceIndexerProcessor;
use Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator;
use Magento\Catalog\Model\ProductIdLocatorInterface;
+use Magento\Customer\Model\ResourceModel\Group\GetCustomerGroupCodesByIds;
class TierPriceStorage implements TierPriceStorageInterface
{
@@ -22,8 +23,6 @@ class TierPriceStorage implements TierPriceStorageInterface
private $tierPricePersistence;
/**
- * Tier price validator.
- *
* @var TierPriceValidator
*/
private $tierPriceValidator;
@@ -36,38 +35,42 @@ class TierPriceStorage implements TierPriceStorageInterface
private $tierPriceFactory;
/**
- * Price index processor.
- *
* @var PriceIndexerProcessor
*/
private $priceIndexProcessor;
/**
- * Product ID locator.
- *
* @var ProductIdLocatorInterface
*/
private $productIdLocator;
+ /**
+ * @var GetCustomerGroupCodesByIds
+ */
+ private $getCustomerGroupCodesByIds;
+
/**
* @param TierPricePersistence $tierPricePersistence
* @param TierPriceValidator $tierPriceValidator
* @param TierPriceFactory $tierPriceFactory
* @param PriceIndexerProcessor $priceIndexProcessor
* @param ProductIdLocatorInterface $productIdLocator
+ * @param GetCustomerGroupCodesByIds $getCustomerGroupCodesByIds
*/
public function __construct(
TierPricePersistence $tierPricePersistence,
TierPriceValidator $tierPriceValidator,
TierPriceFactory $tierPriceFactory,
PriceIndexerProcessor $priceIndexProcessor,
- ProductIdLocatorInterface $productIdLocator
+ ProductIdLocatorInterface $productIdLocator,
+ GetCustomerGroupCodesByIds $getCustomerGroupCodesByIds
) {
$this->tierPricePersistence = $tierPricePersistence;
$this->tierPriceValidator = $tierPriceValidator;
$this->tierPriceFactory = $tierPriceFactory;
$this->priceIndexProcessor = $priceIndexProcessor;
$this->productIdLocator = $productIdLocator;
+ $this->getCustomerGroupCodesByIds = $getCustomerGroupCodesByIds;
}
/**
@@ -148,8 +151,22 @@ private function getExistingPrices(array $skus, bool $groupBySku = false): array
if ($rawPrices) {
$linkField = $this->tierPricePersistence->getEntityLinkField();
$skuByIdLookup = $this->buildSkuByIdLookup($skus);
+ $customerGroupCodesByIds = $this->getCustomerGroupCodesByIds->execute(
+ array_column(
+ array_filter(
+ $rawPrices,
+ static function (array $row) {
+ return (int) $row['all_groups'] !== 1;
+ }
+ ),
+ 'customer_group_id'
+ ),
+ );
foreach ($rawPrices as $rawPrice) {
$sku = $skuByIdLookup[$rawPrice[$linkField]];
+ if (isset($customerGroupCodesByIds[$rawPrice['customer_group_id']])) {
+ $rawPrice['customer_group_code'] = $customerGroupCodesByIds[$rawPrice['customer_group_id']];
+ }
$price = $this->tierPriceFactory->create($rawPrice, $sku);
if ($groupBySku) {
$prices[$sku][] = $price;
diff --git a/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php b/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php
index d14984d3d3478..be2709f343d10 100644
--- a/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php
+++ b/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php
@@ -14,6 +14,8 @@
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\TemporaryStateExceptionInterface;
+use Magento\Framework\Validator\FloatUtils;
+use Magento\Framework\Validator\ValidatorChain;
use Magento\Store\Api\Data\WebsiteInterface;
use Magento\Store\Model\ScopeInterface;
@@ -91,8 +93,8 @@ public function __construct(
*/
public function add($sku, $customerGroupId, $price, $qty)
{
- if (!is_float($price) && !is_int($price) && !\Zend_Validate::is((string)$price, 'Float')
- || !is_float($qty) && !is_int($qty) && !\Zend_Validate::is((string)$qty, 'Float')
+ if (!is_float($price) && !is_int($price) && !ValidatorChain::is((string)$price, FloatUtils::class)
+ || !is_float($qty) && !is_int($qty) && !ValidatorChain::is((string)$qty, FloatUtils::class)
|| $price <= 0
|| $qty <= 0
) {
diff --git a/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php b/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php
index 276a62062a010..c4c389c02cd9a 100644
--- a/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php
+++ b/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php
@@ -7,8 +7,9 @@
namespace Magento\Catalog\Model\Product\Type;
use Magento\Catalog\Api\ProductRepositoryInterface;
-use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\File\Http;
use Magento\Framework\File\UploaderFactory;
/**
@@ -493,7 +494,7 @@ public function processFileQueue()
case 'receive_uploaded_file':
$src = $queueOptions['src_name'] ?? '';
$dst = $queueOptions['dst_name'] ?? '';
- /** @var $uploader \Zend_File_Transfer_Adapter_Http */
+ /** @var $uploader Http */
$uploader = $queueOptions['uploader'] ?? null;
$isUploaded = false;
if ($uploader && $uploader->isValid($src)) {
@@ -601,7 +602,6 @@ protected function _prepareOptions(\Magento\Framework\DataObject $buyRequest, $p
$transport->options[$option->getId()] = $optionsFromRequest[$option->getId()];
}
}
-
} catch (LocalizedException $e) {
$results[] = $e->getMessage();
continue;
diff --git a/app/code/Magento/Catalog/Model/ProductLink/Repository.php b/app/code/Magento/Catalog/Model/ProductLink/Repository.php
index 7a0b224a6153a..98784f016d543 100644
--- a/app/code/Magento/Catalog/Model/ProductLink/Repository.php
+++ b/app/code/Magento/Catalog/Model/ProductLink/Repository.php
@@ -3,22 +3,20 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\Catalog\Model\ProductLink;
use Magento\Catalog\Api\Data\ProductInterface;
-use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory;
use Magento\Catalog\Api\Data\ProductLinkExtensionFactory;
+use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory;
use Magento\Catalog\Model\Product\Initialization\Helper\ProductLinks as LinksInitializer;
use Magento\Catalog\Model\Product\LinkTypeProvider;
use Magento\Catalog\Model\ProductLink\Data\ListCriteria;
-use Magento\Framework\Api\SimpleDataObjectConverter;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\NoSuchEntityException;
-use Magento\Framework\EntityManager\MetadataPool;
-use Magento\Framework\App\ObjectManager;
/**
* Product link entity repository.
@@ -62,6 +60,7 @@ class Repository implements \Magento\Catalog\Api\ProductLinkRepositoryInterface
/**
* @var LinksInitializer
* @deprecated 103.0.4 Not used.
+ * @see \Magento\Catalog\Model\ResourceModel\Product\Link::saveProductLinks()
*/
protected $linkInitializer;
@@ -134,6 +133,9 @@ public function __construct(
*/
public function save(\Magento\Catalog\Api\Data\ProductLinkInterface $entity)
{
+ if (!$entity->getLinkedProductSku()) {
+ throw new NoSuchEntityException(__('The linked product SKU is invalid. Verify the data and try again.'));
+ }
$linkedProduct = $this->productRepository->get($entity->getLinkedProductSku());
$product = $this->productRepository->get($entity->getSku());
$links = [];
diff --git a/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php
index ecb7322ac10d2..b67cf4acd9f34 100644
--- a/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php
+++ b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php
@@ -8,14 +8,18 @@
namespace Magento\Catalog\Model\ProductRepository;
use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Product\Gallery\DeleteValidator;
use Magento\Catalog\Model\Product\Gallery\Processor;
use Magento\Catalog\Model\Product\Media\Config;
+use Magento\Catalog\Model\ResourceModel\Product\Gallery;
use Magento\Framework\Api\Data\ImageContentInterface;
use Magento\Framework\Api\Data\ImageContentInterfaceFactory;
use Magento\Framework\Api\ImageProcessorInterface;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\StateException;
+use Magento\Store\Model\Store;
/**
* Process Media gallery data for ProductRepository before save product.
@@ -37,25 +41,31 @@ class MediaGalleryProcessor
private $contentFactory;
/**
- * Image processor.
- *
* @var ImageProcessorInterface
*/
private $imageProcessor;
+ /**
+ * @var DeleteValidator
+ */
+ private $deleteValidator;
+
/**
* @param Processor $processor
* @param ImageContentInterfaceFactory $contentFactory
* @param ImageProcessorInterface $imageProcessor
+ * @param DeleteValidator|null $deleteValidator
*/
public function __construct(
Processor $processor,
ImageContentInterfaceFactory $contentFactory,
- ImageProcessorInterface $imageProcessor
+ ImageProcessorInterface $imageProcessor,
+ ?DeleteValidator $deleteValidator = null
) {
$this->processor = $processor;
$this->contentFactory = $contentFactory;
$this->imageProcessor = $imageProcessor;
+ $this->deleteValidator = $deleteValidator ?? ObjectManager::getInstance()->get(DeleteValidator::class);
}
/**
@@ -103,7 +113,7 @@ public function processMediaGallery(ProductInterface $product, array $mediaGalle
// phpcs:ignore Magento2.Performance.ForeachArrayMerge
$existingMediaGallery[$key] = array_merge($existingEntry, $updatedEntry);
}
- } else {
+ } elseif ($this->canRemoveImage($product, $existingEntry)) {
//set the removed flag
$existingEntry['removed'] = true;
}
@@ -254,4 +264,17 @@ private function processMediaAttributes(ProductInterface $product, array $images
}
}
}
+
+ /**
+ * Check whether the image can be removed
+ *
+ * @param ProductInterface $product
+ * @param array $image
+ * @return bool
+ */
+ private function canRemoveImage(ProductInterface $product, array $image): bool
+ {
+ return !isset($image['file'])
+ || !$this->deleteValidator->validate($product, $image['file']);
+ }
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/ChildCollectionFactory.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/ChildCollectionFactory.php
new file mode 100755
index 0000000000000..94b3ee03d5d97
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/ChildCollectionFactory.php
@@ -0,0 +1,27 @@
+setFlag('product_children', true);
+ return $collection;
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/CollectionFactory.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/CollectionFactory.php
new file mode 100755
index 0000000000000..4ae58c98f2979
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/CollectionFactory.php
@@ -0,0 +1,53 @@
+objectManager = $objectManager;
+ $this->instanceName = $instanceName;
+ }
+
+ /**
+ * Create class instance with specified parameters
+ *
+ * @param array $data
+ * @return \Magento\Catalog\Model\ResourceModel\Product\Collection
+ */
+ public function create(array $data = [])
+ {
+ return $this->objectManager->create($this->instanceName, $data);
+ }
+}
diff --git a/app/code/Magento/Catalog/Plugin/Ui/DataProvider/Product/ProductDataProvider.php b/app/code/Magento/Catalog/Plugin/Ui/DataProvider/Product/ProductDataProvider.php
new file mode 100644
index 0000000000000..182b47af75f54
--- /dev/null
+++ b/app/code/Magento/Catalog/Plugin/Ui/DataProvider/Product/ProductDataProvider.php
@@ -0,0 +1,64 @@
+scopeConfig = $scopeConfig;
+ }
+
+ /**
+ * Modify catalog product UI data with show total records flag.
+ *
+ * @param CatalogProductDataProvider $subject
+ * @param array $data
+ * @return array
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterGetData(CatalogProductDataProvider $subject, array $data): array
+ {
+ return $this->addShowTotalRecords($data);
+ }
+
+ /**
+ * Add flag to display/hide total records found and pagination elements in products grid header.
+ *
+ * @param array $data
+ * @return array
+ */
+ private function addShowTotalRecords(array $data): array
+ {
+ if (key_exists('totalRecords', $data)) {
+ if ($this->scopeConfig->getValue('admin/grid/limit_total_number_of_products')
+ && $data['totalRecords'] >= $this->scopeConfig->getValue('admin/grid/records_limit')) {
+ $data['showTotalRecords'] = false;
+ } else {
+ $data['showTotalRecords'] = true;
+ }
+ }
+
+ return $data;
+ }
+}
diff --git a/app/code/Magento/Catalog/Pricing/Price/CalculateCustomOptionCatalogRule.php b/app/code/Magento/Catalog/Pricing/Price/CalculateCustomOptionCatalogRule.php
index 1090867aa51a5..a6a11fb803bd8 100644
--- a/app/code/Magento/Catalog/Pricing/Price/CalculateCustomOptionCatalogRule.php
+++ b/app/code/Magento/Catalog/Pricing/Price/CalculateCustomOptionCatalogRule.php
@@ -13,6 +13,9 @@
/**
* Calculates prices of custom options of the product with catalog rules applied.
+ *
+ * @deprecated
+ * @see Catalog rule should not apply to custom option
*/
class CalculateCustomOptionCatalogRule
{
diff --git a/app/code/Magento/Catalog/Test/Fixture/Product.php b/app/code/Magento/Catalog/Test/Fixture/Product.php
index ff65037b7469a..c6d0905c539ed 100644
--- a/app/code/Magento/Catalog/Test/Fixture/Product.php
+++ b/app/code/Magento/Catalog/Test/Fixture/Product.php
@@ -7,12 +7,17 @@
namespace Magento\Catalog\Test\Fixture;
+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;
+use Magento\Framework\Exception\NoSuchEntityException;
use Magento\TestFramework\Fixture\Api\DataMerger;
use Magento\TestFramework\Fixture\Api\ServiceFactory;
use Magento\TestFramework\Fixture\RevertibleDataFixtureInterface;
@@ -137,6 +142,8 @@ private function prepareData(array $data): array
}
$data['product_links'] = $this->prepareLinksData($data);
+ $data['options'] = $this->prepareOptions($data);
+ $data['media_gallery_entries'] = $this->prepareMediaGallery($data);
return $this->dataProcessor->process($this, $data);
}
@@ -146,6 +153,7 @@ private function prepareData(array $data): array
*
* @param array $data
* @return array
+ * @throws NoSuchEntityException
*/
private function prepareLinksData(array $data): array
{
@@ -181,4 +189,146 @@ private function prepareLinksData(array $data): array
return $links;
}
+
+ /**
+ *
+ * Prepare custom option fixtures
+ *
+ * @param array $data
+ * @return array
+ */
+ private function prepareOptions(array $data): array
+ {
+ $options = [];
+ $default = [
+ 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 + [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;
+ }
+
+ return $options;
+ }
+
+ /**
+ * Prepare media gallery entries fixtures
+ *
+ * @param array $data
+ * @return array
+ */
+ private function prepareMediaGallery(array $data): array
+ {
+ $mimeTypeExtensionMap = [
+ 'image/jpeg' => 'jpeg',
+ 'image/png' => 'png',
+ ];
+ $default = [
+ 'id' => null,
+ 'position' => 1,
+ 'media_type' => 'image',
+ 'disabled' => false,
+ 'label' => 'Image%position%%uniqid%',
+ 'types' => [
+ 'image',
+ 'small_image',
+ 'thumbnail',
+ ],
+ 'content' => [
+ 'type' => 'image/jpeg',
+ 'name' => 'image%position%%uniqid%.%extension%',
+ 'base64_encoded_data' => '',
+ ],
+ ];
+ $mediaGalleryEntries = [];
+ $position = 1;
+ foreach ($data['media_gallery_entries'] as $item) {
+ $mediaGalleryEntry = $item + ['position' => $position++] + $default;
+ //reset types for subsequent images
+ $default['types'] = [];
+ $placeholders = [
+ '%position%' => $mediaGalleryEntry['position'],
+ '%extension%' => $mimeTypeExtensionMap[$mediaGalleryEntry['content']['type']]
+ ];
+ $mediaGalleryEntry['label'] = strtr($mediaGalleryEntry['label'], $placeholders);
+ $mediaGalleryEntry['content']['name'] = strtr($mediaGalleryEntry['content']['name'], $placeholders);
+ if (empty($mediaGalleryEntry['content']['base64_encoded_data'])) {
+ $imageContent = base64_encode($this->generateImage($mediaGalleryEntry['content']['type']));
+ $mediaGalleryEntry['content']['base64_encoded_data'] = $imageContent;
+ }
+ $mediaGalleryEntries[] = $mediaGalleryEntry;
+ }
+ return $mediaGalleryEntries;
+ }
+
+ /**
+ * Generate a dummy image
+ *
+ * @param string $type
+ * @return string
+ */
+ private function generateImage(string $type): string
+ {
+ ob_start();
+ $image = imagecreatetruecolor(1024, 768);
+ switch ($type) {
+ case 'image/jpeg':
+ imagejpeg($image);
+ break;
+ case 'image/png':
+ imagepng($image);
+ break;
+ }
+ $content = ob_get_clean();
+ imagedestroy($image);
+
+ return $content;
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddCustomAttributeToSelectedProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddCustomAttributeToSelectedProductActionGroup.xml
new file mode 100755
index 0000000000000..e47b3f2cf19f3
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddCustomAttributeToSelectedProductActionGroup.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+ Add the created attribute to the selected product
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddMultipleSimpleProductToBundleProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddMultipleSimpleProductToBundleProductActionGroup.xml
new file mode 100644
index 0000000000000..46bc686f94d1a
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddMultipleSimpleProductToBundleProductActionGroup.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ Admin Add multiple Simple Product to bundled products
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddOptionToBundleProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddOptionToBundleProductActionGroup.xml
new file mode 100644
index 0000000000000..8973fda0b877e
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAddOptionToBundleProductActionGroup.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+ Admin Add Option to bundled products
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAllIndexerSetUpdateOnSaveActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAllIndexerSetUpdateOnSaveActionGroup.xml
new file mode 100755
index 0000000000000..24054e5947743
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAllIndexerSetUpdateOnSaveActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+ Goes to the Index Management page. Select all Indexers. Selects 'Update on Save'. Clicks on Submit.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageSmallRoleActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageSmallRoleActionGroup.xml
new file mode 100755
index 0000000000000..5ecade27e2c9f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageSmallRoleActionGroup.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ Requires the navigation to the Product Creation page. Checks the Small Role area for image.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageThumbNailRoleActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageThumbNailRoleActionGroup.xml
new file mode 100755
index 0000000000000..0377f8c7585fa
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageThumbNailRoleActionGroup.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ Requires the navigation to the Product Creation page. Checks Thumb Base Role area for image.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeCategoryAndURLNameActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeCategoryAndURLNameActionGroup.xml
new file mode 100755
index 0000000000000..d50b80ca98110
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeCategoryAndURLNameActionGroup.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ Admin Change/Update category and URL Name in Category page.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCustomGroupInAnAttriubuteSetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCustomGroupInAnAttriubuteSetActionGroup.xml
new file mode 100644
index 0000000000000..30437c92893d4
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateCustomGroupInAnAttriubuteSetActionGroup.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+ Admin Create Custom Group In An Attribute Set
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminDeleteCustomGroupDragAndDropAttributesInAnAttributeSetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminDeleteCustomGroupDragAndDropAttributesInAnAttributeSetActionGroup.xml
new file mode 100644
index 0000000000000..bbc4c3766cce1
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminDeleteCustomGroupDragAndDropAttributesInAnAttributeSetActionGroup.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+ Admin Delete Custom Group In An Attribute Set
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenAndApplyCustomOptionRecordsPerPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenAndApplyCustomOptionRecordsPerPageActionGroup.xml
new file mode 100644
index 0000000000000..1bea645419f8c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenAndApplyCustomOptionRecordsPerPageActionGroup.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ Open the custom option drop down section and assign the user defined custom value
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenCreateNewWidgetsOfCMSStaticBlockActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenCreateNewWidgetsOfCMSStaticBlockActionGroup.xml
new file mode 100755
index 0000000000000..2b46ee58624c3
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenCreateNewWidgetsOfCMSStaticBlockActionGroup.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+ Open create new Widgets.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 7
+ countNestedCategory
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminToggleAnchorSwitchActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminToggleAnchorSwitchActionGroup.xml
new file mode 100644
index 0000000000000..8cc907d160aef
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminToggleAnchorSwitchActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ Admin enable or disable Anchor Switch for any category.
+
+
+
+
+
+
+
+
+
+
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/ActionGroup/AdminVerifiesListAndGridModeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminVerifiesListAndGridModeActionGroup.xml
new file mode 100644
index 0000000000000..70d8268a7a685
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminVerifiesListAndGridModeActionGroup.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ Catalog Verifies List and Grid Modes.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryAndPriceActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryAndPriceActionGroup.xml
new file mode 100644
index 0000000000000..df83a7c7ff9eb
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryAndPriceActionGroup.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+ Layered Navigation Category And Price validation.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CreateCustomAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CreateCustomAttributeActionGroup.xml
new file mode 100755
index 0000000000000..0c5cf95efb49f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CreateCustomAttributeActionGroup.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+ Create a custom attribute to be added to any product from admin portal.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddMultipleSimpleProductToBundleProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddMultipleSimpleProductToBundleProductActionGroup.xml
new file mode 100644
index 0000000000000..360c6599887c0
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddMultipleSimpleProductToBundleProductActionGroup.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+ "Adding multiple simple product under bundle products"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontGoToSubSubCategoryPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontGoToSubSubCategoryPageActionGroup.xml
new file mode 100755
index 0000000000000..88e3d0df7ad57
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontGoToSubSubCategoryPageActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SwitchCategoryWebSiteStoreViewActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SwitchCategoryWebSiteStoreViewActionGroup.xml
new file mode 100644
index 0000000000000..ab08969f89fbb
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SwitchCategoryWebSiteStoreViewActionGroup.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+ Navigates to category page, selects a category and changes store view to specified store when we have multiple website.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/UpdateAllIndexerByScheduleActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/UpdateAllIndexerByScheduleActionGroup.xml
new file mode 100755
index 0000000000000..7d5e5690cdfa6
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/UpdateAllIndexerByScheduleActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ Goes to the Index Management page. Select All Indexers. Selects 'Update by Schedule'. Clicks on Submit.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceData.xml
index 0f7f4da1b68c0..9bdf03819b6a3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceData.xml
@@ -29,4 +29,20 @@
+
+ tax/calculation/price_includes_tax
+ 1
+
+
+ tax/display/type
+ 3
+
+
+ tax/calculation/price_includes_tax
+ 0
+
+
+ tax/display/type
+ 1
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml
index f3ccc00192a43..f7922e9b8440a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml
@@ -77,4 +77,52 @@
catalog/frontend/grid_per_page
1
-
+
+ catalog/frontend/list_mode
+ list-grid
+
+
+ catalog/frontend/grid_per_page_values
+ 1,10,50,100
+
+
+ catalog/frontend/grid_per_page
+ 10
+
+
+ catalog/frontend/list_per_page_values
+ 1,10,30
+
+
+ catalog/frontend/list_per_page
+ 1
+
+
+ catalog/frontend/default_sort_by
+ price
+
+
+ catalog/frontend/list_allow_all
+ 1
+
+
+ catalog/frontend/list_mode
+ grid-list
+
+
+ catalog/frontend/list_per_page_values
+ 5,10,15,20,25
+
+
+ catalog/frontend/list_per_page
+ 10
+
+
+ catalog/frontend/default_sort_by
+ position
+
+
+ catalog/frontend/list_allow_all
+ 0
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
index 0fb4f3ef32050..e5b6efbd6373a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
@@ -18,6 +18,12 @@
ApiCategory
true
+
+ CategoryB
+ CategoryB
+ CategoryB
+ true
+
SimpleSubCategory
simplesubcategory
@@ -101,6 +107,30 @@
true
+
+ FourthLevel
+ fourthlevel
+ fourthlevel
+ true
+ true
+
+
+
+ FifthLevel
+ fifthlevel
+ fifthlevel
+ true
+ true
+
+
+
+ SixthLevel
+ sixthlevel
+ sixthlevel
+ true
+ true
+
+
NotInclMenu
notinclemenu
@@ -279,4 +309,4 @@
-
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
index 55d12b0dc1c16..c178c5ed0fd2d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
@@ -22,6 +22,20 @@
EavStockItem
CustomAttributeCategoryIds
+
+ productA
+ productAfortest
+ simple-product-for-producta
+ simple
+ 4
+ 4
+ 123.00
+ 1
+ 100
+ 1
+ EavStockItem
+ CustomAttributeCategoryIds
+
testProductName
testSku
@@ -1471,4 +1485,4 @@
CustomAttributeCategoryIds
-
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection/AdminCategoryBasicFieldSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection/AdminCategoryBasicFieldSection.xml
index e0a6e594ef7bc..b0792c6d3464d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection/AdminCategoryBasicFieldSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection/AdminCategoryBasicFieldSection.xml
@@ -25,5 +25,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
index 4a54997a145ee..c22677f0e0f5f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
@@ -13,7 +13,7 @@
-
+
@@ -23,5 +23,7 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminChecksListAndGridModeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminChecksListAndGridModeSection.xml
new file mode 100644
index 0000000000000..0fc601af553a6
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminChecksListAndGridModeSection.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml
index bf6912db916a9..2f833888dfbb6 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml
@@ -36,5 +36,16 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection/AttributeManageSwatchSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection/AttributeManageSwatchSection.xml
index 6592f8e20cff5..d13ddeb3fcf3d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection/AttributeManageSwatchSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection/AttributeManageSwatchSection.xml
@@ -10,5 +10,9 @@
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection/AttributePropertiesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection/AttributePropertiesSection.xml
index 904e0f03544b5..aa86573044279 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection/AttributePropertiesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection/AttributePropertiesSection.xml
@@ -17,17 +17,21 @@
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetEditSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetEditSection.xml
index df8915a499843..d456e0f9afca3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetEditSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetEditSection.xml
@@ -24,9 +24,11 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml
index 3fad50adb771a..e69bc0e93dd3f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml
@@ -15,5 +15,11 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomOptionsPaginationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomOptionsPaginationSection.xml
new file mode 100644
index 0000000000000..a024fcac13eef
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomOptionsPaginationSection.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection/AdminProductCustomizableOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection/AdminProductCustomizableOptionsSection.xml
index 9f8669118a8c2..5c6ad7dc1f02a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection/AdminProductCustomizableOptionsSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection/AdminProductCustomizableOptionsSection.xml
@@ -22,6 +22,7 @@
+
@@ -57,6 +58,13 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection/AdminProductFormSection.xml
index 590b9a185e5e4..0a3c67bc00d5b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection/AdminProductFormSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection/AdminProductFormSection.xml
@@ -79,5 +79,9 @@
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml
index 201affacd9adb..447af3f80ce2f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml
@@ -39,5 +39,7 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
index 8f2b789639e7f..05391d9babce5 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
@@ -20,7 +20,7 @@
-
+
@@ -37,5 +37,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridTableHeaderSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridTableHeaderSection.xml
index 7341a6ded7a09..61d1a167a91fe 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridTableHeaderSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridTableHeaderSection.xml
@@ -10,5 +10,6 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
index f3a0919f6c728..526ac700a0b5a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
@@ -40,5 +40,9 @@
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection/StorefrontCategorySidebarSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection/StorefrontCategorySidebarSection.xml
index 42a2c559b88c4..26a5452ee018c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection/StorefrontCategorySidebarSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection/StorefrontCategorySidebarSection.xml
@@ -20,5 +20,12 @@
+
+
+
+
+
+
+
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/Section/StorefrontNavigationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml
index 292b2d7008bc1..247cfa78b3360 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml
@@ -11,6 +11,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProducRelatedProductsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProducRelatedProductsSection.xml
index f4db37b677584..91f3087c4d440 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProducRelatedProductsSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProducRelatedProductsSection.xml
@@ -10,8 +10,10 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductCompareMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductCompareMainSection.xml
index f8000a25efdcf..1e0e531707cca 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductCompareMainSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductCompareMainSection.xml
@@ -16,5 +16,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
index 0b44d9bb0ea6b..6ea8102a035d3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
@@ -35,6 +35,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml
index 0c6d667a5dd75..ceeec34bef90b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml
@@ -18,5 +18,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontWidgetsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontWidgetsSection.xml
index ca0c32f142852..d9ee32cc5114f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontWidgetsSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontWidgetsSection.xml
@@ -9,6 +9,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AddExistingProductAttributeFromProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AddExistingProductAttributeFromProductPageTest.xml
new file mode 100644
index 0000000000000..38d8b572ac62d
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AddExistingProductAttributeFromProductPageTest.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AddNewProductAttributeInProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AddNewProductAttributeInProductPageTest.xml
new file mode 100755
index 0000000000000..f5dec88789bf0
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AddNewProductAttributeInProductPageTest.xml
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddAndUpdateCustomGroupInAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddAndUpdateCustomGroupInAttributeSetTest.xml
new file mode 100644
index 0000000000000..35f17f1dcb32a
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddAndUpdateCustomGroupInAttributeSetTest.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml
index f04d832b0a41b..5786eabf9c840 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml
@@ -51,7 +51,7 @@
-
+
@@ -107,16 +107,20 @@
+
+
+
-
+
+
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAlertDoseNotAppearOnProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAlertDoseNotAppearOnProductPageTest.xml
new file mode 100644
index 0000000000000..471880b5f6ea5
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAlertDoseNotAppearOnProductPageTest.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyCatalogStorefrontConfigurationSettingsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyCatalogStorefrontConfigurationSettingsTest.xml
new file mode 100644
index 0000000000000..ee275ce4514ec
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyCatalogStorefrontConfigurationSettingsTest.xml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyChangePriceForConfigurableProductWithAssignedSimpleProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyChangePriceForConfigurableProductWithAssignedSimpleProductsTest.xml
new file mode 100755
index 0000000000000..cfaf0c4b88ad3
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyChangePriceForConfigurableProductWithAssignedSimpleProductsTest.xml
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/StoreFrontDeleteProductImagesAssignedDifferentRolesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/StoreFrontDeleteProductImagesAssignedDifferentRolesTest.xml
new file mode 100644
index 0000000000000..a3d50a9c361b8
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/StoreFrontDeleteProductImagesAssignedDifferentRolesTest.xml
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeArrangementOfAttributesInAnAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeArrangementOfAttributesInAnAttributeSetTest.xml
new file mode 100644
index 0000000000000..c682c7ab4001e
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeArrangementOfAttributesInAnAttributeSetTest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10
+ 1000
+
+
+
+ 11
+ 1000
+
+
+
+ 12
+ 1000
+
+
+
+ 13
+ 1000
+
+
+
+ 14
+ 1000
+
+
+
+ 15
+ 1000
+
+
+
+ 16
+ 1000
+
+
+
+ 17
+ 1000
+
+
+
+ 18
+ 1000
+
+
+
+ 19
+ 1000
+
+
+
+ 20
+ 1000
+
+
+
+ 21
+ 1000
+
+
+ 22
+ 1000
+
+
+ 23
+ 1000
+
+
+ 24
+ 1000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckProductListPriceAttributesWithDifferentCurrencyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckProductListPriceAttributesWithDifferentCurrencyTest.xml
new file mode 100644
index 0000000000000..3fdd278a6bacd
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckProductListPriceAttributesWithDifferentCurrencyTest.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductNegativePriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductNegativePriceTest.xml
index 97db161e6f137..4b40f04f098e0 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductNegativePriceTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductNegativePriceTest.xml
@@ -32,7 +32,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSwatchAttributeWithSpecialCharactersTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSwatchAttributeWithSpecialCharactersTest.xml
new file mode 100644
index 0000000000000..18fb840202f47
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSwatchAttributeWithSpecialCharactersTest.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $grabSwatchForAdmin
+
+
+ $grabDescriptionForAdmin
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteCustomGroupInAnAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteCustomGroupInAnAttributeSetTest.xml
new file mode 100644
index 0000000000000..d4c7e20223a68
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteCustomGroupInAnAttributeSetTest.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminLimitNumberOfProductsInGridTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminLimitNumberOfProductsInGridTest.xml
new file mode 100644
index 0000000000000..ab1bbc39e393b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminLimitNumberOfProductsInGridTest.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
index aa8ec89d25605..9c55e70c3c661 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductStatusStoreViewScopeTest/AdminMassUpdateProductStatusStoreViewScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductStatusStoreViewScopeTest/AdminMassUpdateProductStatusStoreViewScopeTest.xml
index 22bd0186ba466..014104380bf5c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductStatusStoreViewScopeTest/AdminMassUpdateProductStatusStoreViewScopeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductStatusStoreViewScopeTest/AdminMassUpdateProductStatusStoreViewScopeTest.xml
@@ -17,7 +17,7 @@
-
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveProductAttributeFromAttributeSetUsingDragAndDropTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveProductAttributeFromAttributeSetUsingDragAndDropTest.xml
new file mode 100644
index 0000000000000..de3e2a9b9dada
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveProductAttributeFromAttributeSetUsingDragAndDropTest.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRenameCategoryOnStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRenameCategoryOnStoreViewLevelTest.xml
new file mode 100644
index 0000000000000..f647492775b2f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRenameCategoryOnStoreViewLevelTest.xml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml
index 3d3c7f198d808..92818c846fcf1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml
@@ -97,6 +97,8 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateOfSystemProductAttributeIsNotPossibleTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateOfSystemProductAttributeIsNotPossibleTest.xml
new file mode 100644
index 0000000000000..3c4dd60785614
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateOfSystemProductAttributeIsNotPossibleTest.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminValidateAllNestedCategoryInWidgetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminValidateAllNestedCategoryInWidgetTest.xml
new file mode 100755
index 0000000000000..c79567d7f674f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminValidateAllNestedCategoryInWidgetTest.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminValidateProductPricesOnTheFrontendWithTierPricingSetupTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminValidateProductPricesOnTheFrontendWithTierPricingSetupTest.xml
new file mode 100644
index 0000000000000..21873bc10acb2
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminValidateProductPricesOnTheFrontendWithTierPricingSetupTest.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminValidateSimpleProductWithCustomOptionsDragNDropPerPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminValidateSimpleProductWithCustomOptionsDragNDropPerPageTest.xml
new file mode 100644
index 0000000000000..91d6f9d2dea67
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminValidateSimpleProductWithCustomOptionsDragNDropPerPageTest.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AlterAnchorCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AlterAnchorCategoryTest.xml
new file mode 100644
index 0000000000000..a285846fb1a6a
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AlterAnchorCategoryTest.xml
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Simple1
+ 90
+
+
+
+ Simple2
+ 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This operation can take a long time
+ $grabTextPopUp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/ChangeScopeForProductStatusAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/ChangeScopeForProductStatusAttributeTest.xml
new file mode 100644
index 0000000000000..919f0d806157c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/ChangeScopeForProductStatusAttributeTest.xml
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CreateAnchorCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CreateAnchorCategoryTest.xml
new file mode 100755
index 0000000000000..b983112d91e4b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/CreateAnchorCategoryTest.xml
@@ -0,0 +1,219 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ B
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ C
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ D
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ E
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateBundleProductCustomAttributeEntityTextAreaTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateBundleProductCustomAttributeEntityTextAreaTest.xml
new file mode 100644
index 0000000000000..6134058e22ff7
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest/CreateBundleProductCustomAttributeEntityTextAreaTest.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/DisplayingCustomAttributesInProductGridTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/DisplayingCustomAttributesInProductGridTest.xml
new file mode 100644
index 0000000000000..123a686e421c1
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/DisplayingCustomAttributesInProductGridTest.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CAdminTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CAdminTest.xml
index c5093dabec6f3..3b0fad592fed8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CAdminTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CAdminTest.xml
@@ -16,9 +16,6 @@
-
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest/EndToEndB2CGuestUserTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest/EndToEndB2CGuestUserTest.xml
index 379a3112280d3..397a42a45e004 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest/EndToEndB2CGuestUserTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest/EndToEndB2CGuestUserTest.xml
@@ -16,7 +16,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml
index cc1aa5207b751..f32ba620732fc 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml
@@ -17,9 +17,6 @@
-
- Skipped
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontAddRelatedandUpsellstoCartfromproductpageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontAddRelatedandUpsellstoCartfromproductpageTest.xml
new file mode 100644
index 0000000000000..3e3f504444d3e
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontAddRelatedandUpsellstoCartfromproductpageTest.xml
@@ -0,0 +1,336 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontProductsDisplayUsingElasticSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontProductsDisplayUsingElasticSearchTest.xml
index c6d6ee6f9f44f..29cd64759a59b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontProductsDisplayUsingElasticSearchTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontProductsDisplayUsingElasticSearchTest.xml
@@ -16,7 +16,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchSimpleProductBySkuWithHyphenTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchSimpleProductBySkuWithHyphenTest.xml
index 42feb613c310a..3d895e5699351 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchSimpleProductBySkuWithHyphenTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchSimpleProductBySkuWithHyphenTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchVirtualProductBySkuWithHyphenTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchVirtualProductBySkuWithHyphenTest.xml
index 0809124b118b1..0dcc48f5e7e87 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchVirtualProductBySkuWithHyphenTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchVirtualProductBySkuWithHyphenTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategorySidebarMobileMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategorySidebarMobileMenuTest.xml
new file mode 100644
index 0000000000000..f0058712c41a5
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategorySidebarMobileMenuTest.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyCompareListVisibilityForMultiWebsiteTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyCompareListVisibilityForMultiWebsiteTest.xml
new file mode 100644
index 0000000000000..80b8cafc20d1b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyCompareListVisibilityForMultiWebsiteTest.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 560
+
+
+ 560
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyProductAfterPartialReindexOnSeveralWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyProductAfterPartialReindexOnSeveralWebsitesTest.xml
new file mode 100644
index 0000000000000..e5f464920c3ee
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyProductAfterPartialReindexOnSeveralWebsitesTest.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyThatRecentlyOrderedWidgetShowOnlyFiveProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyThatRecentlyOrderedWidgetShowOnlyFiveProductTest.xml
new file mode 100644
index 0000000000000..64901a541a779
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyThatRecentlyOrderedWidgetShowOnlyFiveProductTest.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 11.00
+
+
+
+ 12.00
+
+
+
+ 13.00
+
+
+
+ 14.00
+
+
+
+
+ 15.00
+
+
+
+
+ 16.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5
+ reorders
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Unit/Cron/RefreshSpecialPricesTest.php b/app/code/Magento/Catalog/Test/Unit/Cron/RefreshSpecialPricesTest.php
index 93968d22fa052..7a73f3a261cb9 100644
--- a/app/code/Magento/Catalog/Test/Unit/Cron/RefreshSpecialPricesTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Cron/RefreshSpecialPricesTest.php
@@ -18,7 +18,6 @@
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Stdlib\DateTime;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
-use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Indexer\Model\Indexer;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
@@ -30,11 +29,6 @@
*/
class RefreshSpecialPricesTest extends TestCase
{
- /**
- * @var ObjectManager
- */
- protected $_objectManager;
-
/**
* @var RefreshSpecialPrices
*/
@@ -82,35 +76,24 @@ class RefreshSpecialPricesTest extends TestCase
protected function setUp(): void
{
- $this->_objectManager = new ObjectManager($this);
-
$this->_storeManagerMock = $this->getMockForAbstractClass(StoreManagerInterface::class);
$this->_resourceMock = $this->createMock(ResourceConnection::class);
$this->_dateTimeMock = $this->createMock(DateTime::class);
$this->_localeDateMock = $this->getMockForAbstractClass(TimezoneInterface::class);
$this->_eavConfigMock = $this->createMock(Config::class);
$this->_priceProcessorMock = $this->createMock(Processor::class);
-
+ $this->metadataPool = $this->createMock(MetadataPool::class);
$this->metadataMock = $this->createMock(EntityMetadata::class);
- $this->_model = $this->_objectManager->getObject(
- RefreshSpecialPrices::class,
- [
- 'storeManager' => $this->_storeManagerMock,
- 'resource' => $this->_resourceMock,
- 'dateTime' => $this->_dateTimeMock,
- 'localeDate' => $this->_localeDateMock,
- 'eavConfig' => $this->_eavConfigMock,
- 'processor' => $this->_priceProcessorMock
- ]
+ $this->_model = new RefreshSpecialPrices(
+ $this->_storeManagerMock,
+ $this->_resourceMock,
+ $this->_dateTimeMock,
+ $this->_localeDateMock,
+ $this->_eavConfigMock,
+ $this->_priceProcessorMock,
+ $this->metadataPool
);
-
- $this->metadataPool = $this->createMock(MetadataPool::class);
-
- $reflection = new \ReflectionClass(get_class($this->_model));
- $reflectionProperty = $reflection->getProperty('metadataPool');
- $reflectionProperty->setAccessible(true);
- $reflectionProperty->setValue($this->_model, $this->metadataPool);
}
public function testRefreshSpecialPrices()
@@ -132,13 +115,10 @@ public function testRefreshSpecialPrices()
$connectionMock = $this->getMockForAbstractClass(AdapterInterface::class);
$connectionMock->expects($this->any())->method('select')->willReturn($selectMock);
- $connectionMock->expects(
- $this->any()
- )->method(
- 'fetchCol'
- )->willReturn(
- $idsToProcess
- );
+ $connectionMock->expects($this->exactly(2))
+ ->method('fetchCol')
+ ->with($selectMock, [])
+ ->willReturn($idsToProcess);
$this->_resourceMock->expects(
$this->once()
diff --git a/app/code/Magento/Catalog/Test/Unit/CustomerData/CompareProductsTest.php b/app/code/Magento/Catalog/Test/Unit/CustomerData/CompareProductsTest.php
index 3a3656fe63b9e..62780d9026e9f 100644
--- a/app/code/Magento/Catalog/Test/Unit/CustomerData/CompareProductsTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/CustomerData/CompareProductsTest.php
@@ -17,8 +17,10 @@
use Magento\Catalog\Model\ResourceModel\Product\Compare\Item\Collection;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
+use Magento\Store\Model\Website;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
+use Magento\Store\Model\StoreManagerInterface;
class CompareProductsTest extends TestCase
{
@@ -52,6 +54,16 @@ class CompareProductsTest extends TestCase
*/
private $scopeConfigMock;
+ /**
+ * @var StoreManagerInterface
+ */
+ private $storeManagerMock;
+
+ /**
+ * @var Website|MockObject
+ */
+ private $websiteMock;
+
/**
* @var array
*/
@@ -77,6 +89,18 @@ protected function setUp(): void
->disableOriginalConstructor()
->getMockForAbstractClass();
+ $this->storeManagerMock = $this->getMockBuilder(
+ StoreManagerInterface::class
+ )->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->websiteMock = $this->getMockBuilder(
+ Website::class
+ )->onlyMethods(
+ ['getId',]
+ )->disableOriginalConstructor()
+ ->getMock();
+
$this->objectManagerHelper = new ObjectManagerHelper($this);
$this->model = $this->objectManagerHelper->getObject(
@@ -85,7 +109,9 @@ protected function setUp(): void
'helper' => $this->helperMock,
'productUrl' => $this->productUrlMock,
'outputHelper' => $this->outputHelperMock,
- 'scopeConfig' => $this->scopeConfigMock
+ 'scopeConfig' => $this->scopeConfigMock,
+ 'storeManager' => $this->storeManagerMock
+
]
);
}
@@ -196,7 +222,8 @@ public function testGetSectionData()
$this->helperMock->expects($this->once())
->method('getListUrl')
->willReturn('http://list.url');
-
+ $this->storeManagerMock->expects($this->any())->method('getWebsite')->willReturn($this->websiteMock);
+ $this->websiteMock->expects($this->any())->method('getId')->willReturn(1);
$this->assertEquals(
[
'count' => $count,
@@ -224,7 +251,8 @@ public function testGetSectionData()
'remove_url' => 'http://remove.url/3',
'productScope' => null
]
- ]
+ ],
+ 'websiteId' => 1
],
$this->model->getSectionData()
);
@@ -245,12 +273,16 @@ public function testGetSectionDataNoItems()
->method('getListUrl')
->willReturn('http://list.url');
+ $this->storeManagerMock->expects($this->any())->method('getWebsite')->willReturn($this->websiteMock);
+ $this->websiteMock->expects($this->any())->method('getId')->willReturn(1);
+
$this->assertEquals(
[
'count' => $count,
'countCaption' => __('%1 items', $count),
'listUrl' => 'http://list.url',
- 'items' => []
+ 'items' => [],
+ 'websiteId' => 1
],
$this->model->getSectionData()
);
@@ -264,6 +296,9 @@ public function testGetSectionDataSingleItem()
->method('getItemCount')
->willReturn($count);
+ $this->storeManagerMock->expects($this->any())->method('getWebsite')->willReturn($this->websiteMock);
+ $this->websiteMock->expects($this->any())->method('getId')->willReturn(1);
+
$items = $this->prepareProductsWithCorrespondingMocks(
[
[
@@ -296,7 +331,8 @@ public function testGetSectionDataSingleItem()
'remove_url' => 'http://remove.url/12345',
'productScope' => null
]
- ]
+ ],
+ 'websiteId' => 1
],
$this->model->getSectionData()
);
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CategoryManagementTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CategoryManagementTest.php
index 16bb46320e37d..f1ab21ad03277 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/CategoryManagementTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/CategoryManagementTest.php
@@ -201,8 +201,8 @@ public function testGetTreeForAllScope()
public function testMove()
{
- $categoryId = 2;
- $parentId = 1;
+ $categoryId = 4;
+ $parentId = 40;
$afterId = null;
$categoryMock = $this->getMockBuilder(Category::class)
->setMockClassName('categoryMock')
@@ -214,18 +214,25 @@ public function testMove()
->getMock();
$this->categoryRepositoryMock
- ->expects($this->exactly(2))
+ ->expects($this->exactly(6))
->method('get')
->willReturnMap([
[$categoryId, null, $categoryMock],
[$parentId, null, $parentCategoryMock],
]);
- $parentCategoryMock->expects($this->once())->method('hasChildren')->willReturn(true);
+ $parentCategoryMock->expects($this->exactly(3))->method('hasChildren')
+ ->willReturn(true, false, false);
$parentCategoryMock->expects($this->once())->method('getChildren')->willReturn('5,6,7');
- $categoryMock->expects($this->once())->method('getPath');
- $parentCategoryMock->expects($this->once())->method('getPath');
- $categoryMock->expects($this->once())->method('move')->with($parentId, '7');
+ $categoryMock->expects($this->exactly(3))->method('getPath')
+ ->willReturnOnConsecutiveCalls('2/4', '2/3/4', '2/3/4');
+ $parentCategoryMock->expects($this->exactly(3))->method('getPath')
+ ->willReturnOnConsecutiveCalls('2/40', '2/3/40', '2/3/44/40');
+ $categoryMock->expects($this->exactly(3))->method('move')
+ ->withConsecutive([$parentId, '7'], [$parentId, null], [$parentId, null]);
+
$this->assertTrue($this->model->move($categoryId, $parentId, $afterId));
+ $this->assertTrue($this->model->move($categoryId, $parentId));
+ $this->assertTrue($this->model->move($categoryId, $parentId));
}
public function testMoveWithException()
@@ -251,8 +258,8 @@ public function testMoveWithException()
[$categoryId, null, $categoryMock],
[$parentId, null, $parentCategoryMock],
]);
- $categoryMock->expects($this->once())->method('getPath')->willReturn('test');
- $parentCategoryMock->expects($this->once())->method('getPath')->willReturn('test');
+ $categoryMock->expects($this->once())->method('getPath')->willReturn('test/2');
+ $parentCategoryMock->expects($this->once())->method('getPath')->willReturn('test/2/1');
$this->model->move($categoryId, $parentId, $afterId);
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Price/Action/RowsTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Price/Action/RowsTest.php
index 816dc923ebc0a..7a6a91028b517 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Price/Action/RowsTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Price/Action/RowsTest.php
@@ -7,6 +7,8 @@
namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Price\Action;
+use Magento\Framework\Indexer\DimensionalIndexerInterface;
+use Magento\Framework\Search\Request\Dimension;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Directory\Model\CurrencyFactory;
use Magento\Catalog\Model\Product\Type;
@@ -94,36 +96,17 @@ class RowsTest extends TestCase
protected function setUp(): void
{
- $this->config = $this->getMockBuilder(ScopeConfigInterface::class)
- ->getMockForAbstractClass();
- $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class)
- ->getMockForAbstractClass();
- $this->currencyFactory = $this->getMockBuilder(CurrencyFactory::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->localeDate = $this->getMockBuilder(TimezoneInterface::class)
- ->getMockForAbstractClass();
- $this->dateTime = $this->getMockBuilder(DateTime::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->catalogProductType = $this->getMockBuilder(Type::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->indexerPriceFactory = $this->getMockBuilder(Factory::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->defaultIndexerResource = $this->getMockBuilder(DefaultPrice::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->tierPriceIndexResource = $this->getMockBuilder(TierPrice::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->dimensionCollectionFactory = $this->getMockBuilder(DimensionCollectionFactory::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->tableMaintainer = $this->getMockBuilder(TableMaintainer::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $this->config = $this->createMock(ScopeConfigInterface::class);
+ $this->storeManager = $this->createMock(StoreManagerInterface::class);
+ $this->currencyFactory = $this->createMock(CurrencyFactory::class);
+ $this->localeDate = $this->createMock(TimezoneInterface::class);
+ $this->dateTime = $this->createMock(DateTime::class);
+ $this->catalogProductType = $this->createMock(Type::class);
+ $this->indexerPriceFactory = $this->createMock(Factory::class);
+ $this->defaultIndexerResource = $this->createMock(DefaultPrice::class);
+ $this->tierPriceIndexResource = $this->createMock(TierPrice::class);
+ $this->dimensionCollectionFactory = $this->createMock(DimensionCollectionFactory::class);
+ $this->tableMaintainer = $this->createMock(TableMaintainer::class);
$batchSize = 2;
$this->actionRows = new Rows(
@@ -144,7 +127,7 @@ protected function setUp(): void
public function testEmptyIds()
{
- $this->expectException('Magento\Framework\Exception\InputException');
+ $this->expectException(\Magento\Framework\Exception\InputException::class);
$this->expectExceptionMessage('Bad value was supplied.');
$this->actionRows->execute(null);
}
@@ -152,44 +135,123 @@ public function testEmptyIds()
public function testBatchProcessing()
{
$ids = [1, 2, 3, 4];
- $select = $this->getMockBuilder(Select::class)
- ->disableOriginalConstructor()
- ->getMock();
- $select->expects($this->any())->method('from')->willReturnSelf();
- $select->expects($this->any())->method('where')->willReturnSelf();
- $select->expects($this->any())->method('join')->willReturnSelf();
- $adapter = $this->getMockBuilder(AdapterInterface::class)->getMockForAbstractClass();
- $adapter->expects($this->any())->method('select')->willReturn($select);
- $this->defaultIndexerResource->expects($this->any())
- ->method('getConnection')
- ->willReturn($adapter);
- $adapter->expects($this->any())
- ->method('fetchAll')
+
+ $select = $this->createMock(Select::class);
+ $select->method('from')->willReturnSelf();
+ $select->method('joinLeft')->willReturnSelf();
+ $select->method('where')->willReturnSelf();
+ $select->method('join')->willReturnSelf();
+ $adapter = $this->createMock(AdapterInterface::class);
+ $adapter->method('select')->willReturn($select);
+ $adapter->method('describeTable')->willReturn([]);
+ $this->defaultIndexerResource->method('getConnection')->willReturn($adapter);
+ $adapter->method('fetchAll')->with($select)->willReturn([]);
+
+ $adapter->expects($this->exactly(4))
+ ->method('fetchPairs')
->with($select)
- ->willReturn([]);
- $adapter->expects($this->any())
+ ->willReturnOnConsecutiveCalls(
+ [1 => 'simple', 2 => 'virtual'],
+ [],
+ [3 => 'simple', 4 => 'virtual'],
+ [],
+ );
+ $multiDimensionProvider = $this->createMock(MultiDimensionProvider::class);
+ $this->dimensionCollectionFactory->expects($this->exactly(4))
+ ->method('create')
+ ->willReturn($multiDimensionProvider);
+ $dimension = $this->createMock(Dimension::class);
+ $dimension->method('getName')->willReturn('default');
+ $dimension->method('getValue')->willReturn('0');
+ $iterator = new \ArrayIterator([[$dimension]]);
+ $multiDimensionProvider->expects($this->exactly(4))
+ ->method('getIterator')
+ ->willReturn($iterator);
+ $this->catalogProductType->expects($this->once())
+ ->method('getTypesByPriority')
+ ->willReturn(
+ [
+ 'virtual' => ['price_indexer' => '\Price\Indexer'],
+ 'simple' => ['price_indexer' => '\Price\Indexer'],
+ ]
+ );
+ $priceIndexer = $this->createMock(DimensionalIndexerInterface::class);
+ $this->indexerPriceFactory->expects($this->exactly(2))
+ ->method('create')
+ ->with('\Price\Indexer', ['fullReindexAction' => false])
+ ->willReturn($priceIndexer);
+ $priceIndexer->expects($this->exactly(4))
+ ->method('executeByDimensions');
+ $select->expects($this->exactly(4))
+ ->method('deleteFromSelect')
+ ->with('main_table')
+ ->willReturn('');
+ $adapter->expects($this->exactly(2))
+ ->method('getIndexList')
+ ->willReturn(['entity_id'=>['COLUMNS_LIST'=>['test']]]);
+ $adapter->expects($this->exactly(2))
+ ->method('getPrimaryKeyName')
+ ->willReturn('entity_id');
+
+ $this->actionRows->execute($ids);
+ }
+
+ public function testDeletedProductsBatchProcessing()
+ {
+ $ids = [1, 2, 3, 4];
+
+ $select = $this->createMock(Select::class);
+ $select->method('from')->willReturnSelf();
+ $select->method('joinLeft')->willReturnSelf();
+ $select->method('where')->willReturnSelf();
+ $select->method('join')->willReturnSelf();
+ $adapter = $this->createMock(AdapterInterface::class);
+ $adapter->method('select')->willReturn($select);
+ $adapter->method('describeTable')->willReturn([]);
+ $this->defaultIndexerResource->method('getConnection')->willReturn($adapter);
+ $adapter->method('fetchAll')->with($select)->willReturn([]);
+
+ $adapter->expects($this->exactly(4))
->method('fetchPairs')
->with($select)
- ->willReturn([]);
- $multiDimensionProvider = $this->getMockBuilder(MultiDimensionProvider::class)
- ->disableOriginalConstructor()
- ->getMock();
+ ->willReturnOnConsecutiveCalls([], [], [], []);
+ $multiDimensionProvider = $this->createMock(MultiDimensionProvider::class);
$this->dimensionCollectionFactory->expects($this->exactly(2))
->method('create')
->willReturn($multiDimensionProvider);
- $iterator = new \ArrayIterator([]);
+ $dimension = $this->createMock(Dimension::class);
+ $dimension->method('getName')->willReturn('default');
+ $dimension->method('getValue')->willReturn('0');
+ $iterator = new \ArrayIterator([[$dimension]]);
$multiDimensionProvider->expects($this->exactly(2))
->method('getIterator')
->willReturn($iterator);
- $this->catalogProductType->expects($this->any())
+ $this->catalogProductType->expects($this->once())
->method('getTypesByPriority')
- ->willReturn([]);
+ ->willReturn(
+ [
+ 'virtual' => ['price_indexer' => '\Price\Indexer'],
+ 'simple' => ['price_indexer' => '\Price\Indexer'],
+ ]
+ );
+ $priceIndexer = $this->createMock(DimensionalIndexerInterface::class);
+ $this->indexerPriceFactory->expects($this->exactly(2))
+ ->method('create')
+ ->with('\Price\Indexer', ['fullReindexAction' => false])
+ ->willReturn($priceIndexer);
+ $priceIndexer->expects($this->never())
+ ->method('executeByDimensions');
+ $select->expects($this->exactly(2))
+ ->method('deleteFromSelect')
+ ->with('index_price')
+ ->willReturn('');
$adapter->expects($this->exactly(2))
->method('getIndexList')
->willReturn(['entity_id'=>['COLUMNS_LIST'=>['test']]]);
$adapter->expects($this->exactly(2))
->method('getPrimaryKeyName')
->willReturn('entity_id');
+
$this->actionRows->execute($ids);
}
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php
index e3465ffd0ec28..74267f4239f91 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php
@@ -9,8 +9,11 @@
namespace Magento\Catalog\Test\Unit\Model\Product\Gallery;
use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Api\Data\ProductInterfaceFactory;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\Gallery\DeleteValidator;
use Magento\Catalog\Model\Product\Gallery\GalleryManagement;
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
use Magento\Framework\Api\AttributeValue;
@@ -21,6 +24,8 @@
/**
* Tests for \Magento\Catalog\Model\Product\Gallery\GalleryManagement.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class GalleryManagementTest extends TestCase
{
@@ -54,6 +59,21 @@ class GalleryManagementTest extends TestCase
*/
protected $attributeValueMock;
+ /**
+ * @var ProductInterfaceFactory|MockObject
+ */
+ private $productInterfaceFactory;
+
+ /**
+ * @var DeleteValidator|MockObject
+ */
+ private $deleteValidator;
+
+ /**
+ * @var ProductInterface|MockObject
+ */
+ private $newProductMock;
+
/**
* @inheritDoc
*/
@@ -61,6 +81,8 @@ protected function setUp(): void
{
$this->productRepositoryMock = $this->getMockForAbstractClass(ProductRepositoryInterface::class);
$this->contentValidatorMock = $this->getMockForAbstractClass(ImageContentValidatorInterface::class);
+ $this->productInterfaceFactory = $this->createMock(ProductInterfaceFactory::class);
+ $this->deleteValidator = $this->createMock(DeleteValidator::class);
$this->productMock = $this->createPartialMock(
Product::class,
[
@@ -78,11 +100,18 @@ protected function setUp(): void
$this->getMockForAbstractClass(ProductAttributeMediaGalleryEntryInterface::class);
$this->model = new GalleryManagement(
$this->productRepositoryMock,
- $this->contentValidatorMock
+ $this->contentValidatorMock,
+ $this->productInterfaceFactory,
+ $this->deleteValidator
);
$this->attributeValueMock = $this->getMockBuilder(AttributeValue::class)
->disableOriginalConstructor()
->getMock();
+
+ $this->newProductMock = $this->getMockForAbstractClass(ProductInterface::class);
+
+ $this->productInterfaceFactory->method('create')
+ ->willReturn($this->newProductMock);
}
/**
@@ -130,7 +159,7 @@ public function testCreateWithCannotSaveException(): void
->method('getMediaAttributes')
->willReturn(['small_image' => $attributeMock]);
- $this->productRepositoryMock->expects($this->once())->method('save')->with($this->productMock)
+ $this->productRepositoryMock->expects($this->once())->method('save')->with($this->newProductMock)
->willThrowException(new \Exception());
$this->model->create($productSku, $this->mediaGalleryEntryMock);
}
@@ -152,7 +181,7 @@ public function testCreate(): void
->willReturn($this->productMock);
$this->productRepositoryMock->expects($this->once())
->method('save')
- ->with($this->productMock)
+ ->with($this->newProductMock)
->willReturn($this->productMock);
$this->contentValidatorMock->expects($this->once())->method('isValid')->with($entryContentMock)
@@ -165,7 +194,7 @@ public function testCreate(): void
$this->productMock
->method('getMediaGalleryEntries')
->willReturnOnConsecutiveCalls([], [$newEntryMock]);
- $this->productMock->expects($this->once())->method('setMediaGalleryEntries')
+ $this->newProductMock->expects($this->once())->method('setMediaGalleryEntries')
->with([$this->mediaGalleryEntryMock]);
$this->assertEquals(42, $this->model->create($productSku, $this->mediaGalleryEntryMock));
@@ -216,7 +245,7 @@ public function testUpdateWithCannotSaveException(): void
$existingEntryMock->expects($this->once())->method('getTypes')->willReturn([]);
$entryMock->expects($this->once())->method('getTypes')->willReturn([]);
$entryMock->expects($this->once())->method('getId')->willReturn($entryId);
- $this->productRepositoryMock->expects($this->once())->method('save')->with($this->productMock)
+ $this->productRepositoryMock->expects($this->once())->method('save')->with($this->newProductMock)
->willThrowException(new \Exception());
$this->model->update($productSku, $entryMock);
}
@@ -253,10 +282,10 @@ public function testUpdate(): void
$entryMock->expects($this->exactly(2))->method('getId')->willReturn($entryId);
$entryMock->expects($this->once())->method('getTypes')->willReturn(['image']);
- $this->productMock->expects($this->once())->method('setMediaGalleryEntries')
+ $this->newProductMock->expects($this->once())->method('setMediaGalleryEntries')
->with([$entryMock, $existingSecondEntryMock])
->willReturnSelf();
- $this->productRepositoryMock->expects($this->once())->method('save')->with($this->productMock);
+ $this->productRepositoryMock->expects($this->once())->method('save')->with($this->newProductMock);
$this->assertTrue($this->model->update($productSku, $entryMock));
}
@@ -294,11 +323,12 @@ public function testRemove(): void
ProductAttributeMediaGalleryEntryInterface::class
);
$existingEntryMock->expects($this->once())->method('getId')->willReturn(42);
+ $existingEntryMock->expects($this->once())->method('getFile')->willReturn('path/to/file');
$this->productMock->expects($this->once())->method('getMediaGalleryEntries')
->willReturn([$existingEntryMock]);
- $this->productMock->expects($this->once())->method('setMediaGalleryEntries')
+ $this->newProductMock->expects($this->once())->method('setMediaGalleryEntries')
->with([]);
- $this->productRepositoryMock->expects($this->once())->method('save')->with($this->productMock);
+ $this->productRepositoryMock->expects($this->once())->method('save')->with($this->newProductMock);
$this->assertTrue($this->model->remove($productSku, $entryId));
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/ValueTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/ValueTest.php
index d4c1db4ec1b28..29060480a553c 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/ValueTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/ValueTest.php
@@ -16,7 +16,7 @@
use Magento\Framework\Pricing\PriceInfoInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use PHPUnit\Framework\TestCase;
-use Magento\Catalog\Pricing\Price\CalculateCustomOptionCatalogRule;
+use Magento\Catalog\Pricing\Price\CustomOptionPriceCalculator;
use PHPUnit\Framework\MockObject\MockObject;
/**
@@ -30,9 +30,9 @@ class ValueTest extends TestCase
private $model;
/**
- * @var CalculateCustomOptionCatalogRule|MockObject
+ * @var CustomOptionPriceCalculator|MockObject
*/
- private $calculateCustomOptionCatalogRule;
+ private $customOptionPriceCalculatorMock;
/**
* @inheritDoc
@@ -42,8 +42,8 @@ protected function setUp(): void
$mockedResource = $this->getMockedResource();
$mockedCollectionFactory = $this->getMockedValueCollectionFactory();
- $this->calculateCustomOptionCatalogRule = $this->createMock(
- CalculateCustomOptionCatalogRule::class
+ $this->customOptionPriceCalculatorMock = $this->createMock(
+ CustomOptionPriceCalculator::class
);
$helper = new ObjectManager($this);
@@ -52,7 +52,7 @@ protected function setUp(): void
[
'resource' => $mockedResource,
'valueCollectionFactory' => $mockedCollectionFactory,
- 'calculateCustomOptionCatalogRule' => $this->calculateCustomOptionCatalogRule
+ 'customOptionPriceCalculator' => $this->customOptionPriceCalculatorMock
]
);
$this->model->setOption($this->getMockedOption());
@@ -80,8 +80,8 @@ public function testGetPrice()
$this->assertEquals($price, $this->model->getPrice(false));
$percentPrice = 100.0;
- $this->calculateCustomOptionCatalogRule->expects($this->atLeastOnce())
- ->method('execute')
+ $this->customOptionPriceCalculatorMock->expects($this->atLeastOnce())
+ ->method('getOptionPriceByPriceCode')
->willReturn($percentPrice);
$this->assertEquals($percentPrice, $this->model->getPrice(true));
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/BasePriceStorageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/BasePriceStorageTest.php
index 5a49f1e87690b..b533d49d879bb 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/BasePriceStorageTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/BasePriceStorageTest.php
@@ -10,6 +10,8 @@
use Magento\Catalog\Api\Data\BasePriceInterface;
use Magento\Catalog\Api\Data\BasePriceInterfaceFactory;
use Magento\Catalog\Api\Data\PriceUpdateResultInterface;
+use Magento\Catalog\Api\Data\ProductAttributeInterface;
+use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
use Magento\Catalog\Model\Product\Price\BasePriceStorage;
use Magento\Catalog\Model\Product\Price\PricePersistence;
use Magento\Catalog\Model\Product\Price\PricePersistenceFactory;
@@ -19,6 +21,7 @@
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Store\Api\Data\StoreInterface;
+use Magento\Store\Api\Data\WebsiteInterface;
use Magento\Store\Api\StoreRepositoryInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -73,6 +76,11 @@ class BasePriceStorageTest extends TestCase
*/
private $model;
+ /**
+ * @var ProductAttributeRepositoryInterface
+ */
+ private $productAttributeRepository;
+
/**
* Set up.
*
@@ -111,6 +119,10 @@ protected function setUp(): void
$this->validationResult = $this->getMockBuilder(Result::class)
->disableOriginalConstructor()
->getMock();
+ $this->productAttributeRepository = $this
+ ->getMockBuilder(ProductAttributeRepositoryInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
$objectManager = new ObjectManager($this);
$this->model = $objectManager->getObject(
@@ -123,6 +135,7 @@ protected function setUp(): void
'invalidSkuProcessor' => $this->invalidSkuProcessor,
'validationResult' => $this->validationResult,
'allowedProductTypes' => ['simple', 'virtual', 'bundle', 'downloadable'],
+ 'productAttributeRepository' => $this->productAttributeRepository
]
);
}
@@ -189,13 +202,28 @@ public function testGet()
/**
* Test update method.
*
+ * @param bool $isScopeWebsite
+ * @param bool $isScopeGlobal
+ * @param array $formattedPrices
* @return void
+ * @dataProvider updateProvider
*/
- public function testUpdate()
+ public function testUpdate(bool $isScopeWebsite, bool $isScopeGlobal, array $formattedPrices)
{
+ $website = $this->getMockBuilder(WebsiteInterface::class)
+ ->setMethods([
+ 'getStoreIds',
+ ])
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $website->method('getStoreIds')->willReturn([1 => 1, 2 => 2]);
$store = $this->getMockBuilder(StoreInterface::class)
+ ->setMethods([
+ 'getWebsite',
+ ])
->disableOriginalConstructor()
->getMockForAbstractClass();
+ $store->method('getWebsite')->willReturn($website);
$sku = 'sku_1';
$idsBySku = [
'sku_1' => [
@@ -205,15 +233,15 @@ public function testUpdate()
]
];
$this->basePriceInterface->expects($this->atLeastOnce())->method('getSku')->willReturn($sku);
- $this->invalidSkuProcessor->expects($this->once())
+ $this->invalidSkuProcessor->expects($this->any())
->method('retrieveInvalidSkuList')
->with([1 => $sku], ['simple', 'virtual', 'bundle', 'downloadable'], 1)
->willReturn([]);
$this->basePriceInterface->expects($this->atLeastOnce())->method('getPrice')->willReturn(15);
$this->basePriceInterface->expects($this->atLeastOnce())->method('getStoreId')->willReturn(1);
- $this->validationResult->expects($this->once())->method('getFailedRowIds')->willReturn([]);
+ $this->validationResult->expects($this->any())->method('getFailedRowIds')->willReturn([]);
$this->productIdLocator
- ->expects($this->once())
+ ->expects($this->any())
->method('retrieveProductIdsBySkus')->with([$sku])
->willReturn($idsBySku);
$this->pricePersistenceFactory
@@ -222,16 +250,22 @@ public function testUpdate()
->with(['attributeCode' => 'price'])
->willReturn($this->pricePersistence);
$this->pricePersistence->expects($this->atLeastOnce())->method('getEntityLinkField')->willReturn('row_id');
- $this->storeRepository->expects($this->once())->method('getById')->with(1)->willReturn($store);
- $formattedPrices = [
- [
- 'store_id' => 1,
- 'row_id' => 1,
- 'value' => 15
- ]
- ];
- $this->pricePersistence->expects($this->once())->method('update')->with($formattedPrices);
+ $this->storeRepository->expects($this->any())->method('getById')->with(1)->willReturn($store);
+ $this->pricePersistence->expects($this->any())->method('update')->with($formattedPrices);
$this->validationResult->expects($this->any())->method('getFailedItems')->willReturn([]);
+ $attribute = $this->getMockBuilder(ProductAttributeInterface::class)
+ ->setMethods([
+ 'isScopeWebsite',
+ 'isScopeGlobal'
+ ])
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $attribute->method('isScopeWebsite')->willReturn($isScopeWebsite);
+ $attribute->method('isScopeGlobal')->willReturn($isScopeGlobal);
+ $this->productAttributeRepository
+ ->method('get')
+ ->willReturn($attribute);
+
$this->assertEquals([], $this->model->update([1 => $this->basePriceInterface]));
}
@@ -296,4 +330,54 @@ public function testUpdateWithoutSkuAndWithNegativePrice()
$this->model->update([$this->basePriceInterface])
);
}
+
+ /**
+ * Data provider for update.
+ *
+ * @return array
+ */
+ public function updateProvider(): array
+ {
+ return
+ [
+ [
+ 'isScopeWebsite' => false,
+ 'isScopeGlobal' => false,
+ 'formattedPrices' => [
+ [
+ 'store_id' => 1,
+ 'row_id' => 1,
+ 'value' => 15
+ ]
+ ]
+ ],
+ [
+ 'isScopeWebsite' => true,
+ 'isScopeGlobal' => false,
+ 'formattedPrices' => [
+ [
+ 'store_id' => 1,
+ 'row_id' => 1,
+ 'value' => 15
+ ],
+ [
+ 'store_id' => 2,
+ 'row_id' => 1,
+ 'value' => 15
+ ]
+ ]
+ ],
+ [
+ 'isScopeWebsite' => false,
+ 'isScopeGlobal' => true,
+ 'formattedPrices' => [
+ [
+ 'store_id' => 0,
+ 'row_id' => 1,
+ 'value' => 15
+ ]
+ ]
+ ]
+ ];
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceFactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceFactoryTest.php
new file mode 100644
index 0000000000000..593fd98f67f8b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceFactoryTest.php
@@ -0,0 +1,164 @@
+tierPriceFactory = $this->createMock(TierPriceInterfaceFactory::class);
+ $this->tierPricePersistence = $this->createMock(TierPricePersistence::class);
+ $this->customerGroupRepository = $this->createMock(GroupRepositoryInterface::class);
+ $this->searchCriteriaBuilder = $this->createMock(SearchCriteriaBuilder::class);
+ $this->filterBuilder = $this->createMock(FilterBuilder::class);
+
+ $this->model = new TierPriceFactory(
+ $this->tierPriceFactory,
+ $this->tierPricePersistence,
+ $this->customerGroupRepository,
+ $this->searchCriteriaBuilder,
+ $this->filterBuilder
+ );
+ }
+
+ /**
+ * @dataProvider createDataProvider
+ * @param array $rawData
+ * @param array $expected
+ * @return void
+ */
+ public function testCreate(array $rawData, array $expected): void
+ {
+ $rawData = array_merge(
+ [
+ 'value_id' => 1,
+ 'entity_id' => 1,
+ 'all_groups' => 1,
+ 'customer_group_id' => 0,
+ 'qty' => 2.0000,
+ 'value' => 2.0000,
+ 'percentage_value' => null,
+ 'website_id' => 0
+ ],
+ $rawData
+ );
+ $expected = array_merge(
+ [
+ 'sku' => 'simple',
+ 'price' => 2.0,
+ 'price_type' => TierPrice::PRICE_TYPE_FIXED,
+ 'website_id' => 0,
+ 'quantity' => 2.000,
+ 'customer_group' => 'all groups'
+ ],
+ $expected
+ );
+ $customerGroupMock = $this->getMockForAbstractClass(GroupInterface::class);
+ $customerGroupMock->method('getCode')
+ ->willReturn('NOT LOGGED IN');
+
+ $isCustomerGroupResolved = isset($rawData['customer_group_code']) || $rawData['all_groups'];
+ $this->customerGroupRepository->expects($isCustomerGroupResolved ? $this->never() : $this->once())
+ ->method('getById')
+ ->willReturn($customerGroupMock);
+ $expectedTierPrice = $this->getMockBuilder(TierPrice::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $expectedTierPrice->setData($expected);
+ $tierPriceMock = $this->getMockBuilder(TierPrice::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $this->tierPriceFactory->method('create')
+ ->willReturn($tierPriceMock);
+ $tierPrice = $this->model->create($rawData, 'simple');
+ $this->assertEquals($expectedTierPrice->getSku(), $tierPrice->getSku());
+ $this->assertEquals($expectedTierPrice->getPrice(), $tierPrice->getPrice());
+ $this->assertEquals($expectedTierPrice->getPriceType(), $tierPrice->getPriceType());
+ $this->assertEquals($expectedTierPrice->getWebsiteId(), $tierPrice->getWebsiteId());
+ $this->assertEquals($expectedTierPrice->getCustomerGroup(), $tierPrice->getCustomerGroup());
+ $this->assertEquals($expectedTierPrice->getQuantity(), $tierPrice->getQuantity());
+ }
+
+ /**
+ * @return array
+ */
+ public function createDataProvider(): array
+ {
+ return [
+ [
+ [],
+ []
+ ],
+ [
+ [
+ 'all_groups' => 0,
+ 'customer_group_id' => 1,
+ ],
+ [
+ 'customer_group' => 'NOT LOGGED IN'
+ ]
+ ],
+ [
+ [
+ 'all_groups' => 0,
+ 'customer_group_id' => 2,
+ 'customer_group_code' => 'custom',
+ ],
+ [
+ 'customer_group' => 'custom'
+ ]
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php
index 189fb33fc1f23..77e91136ad4fe 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php
@@ -15,7 +15,7 @@
use Magento\Catalog\Model\Product\Price\Validation\Result as PriceValidationResult;
use Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator;
use Magento\Catalog\Model\ProductIdLocatorInterface;
-use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Customer\Model\ResourceModel\Group\GetCustomerGroupCodesByIds;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -51,6 +51,11 @@ class TierPriceStorageTest extends TestCase
*/
private $tierPriceStorage;
+ /**
+ * @var GetCustomerGroupCodesByIds|MockObject
+ */
+ private $getCustomerGroupCodesByIds;
+
/**
* {@inheritdoc}
*/
@@ -63,17 +68,15 @@ protected function setUp(): void
$this->tierPriceFactory = $this->createMock(TierPriceFactory::class);
$this->priceIndexProcessor = $this->createMock(PriceIndexerProcessor::class);
$this->productIdLocator = $this->getMockForAbstractClass(ProductIdLocatorInterface::class);
+ $this->getCustomerGroupCodesByIds = $this->createMock(GetCustomerGroupCodesByIds::class);
- $objectManager = new ObjectManager($this);
- $this->tierPriceStorage = $objectManager->getObject(
- TierPriceStorage::class,
- [
- 'tierPricePersistence' => $this->tierPricePersistence,
- 'tierPriceValidator' => $this->tierPriceValidator,
- 'tierPriceFactory' => $this->tierPriceFactory,
- 'priceIndexProcessor' => $this->priceIndexProcessor,
- 'productIdLocator' => $this->productIdLocator,
- ]
+ $this->tierPriceStorage = new TierPriceStorage(
+ $this->tierPricePersistence,
+ $this->tierPriceValidator,
+ $this->tierPriceFactory,
+ $this->priceIndexProcessor,
+ $this->productIdLocator,
+ $this->getCustomerGroupCodesByIds
);
}
@@ -85,6 +88,38 @@ protected function setUp(): void
public function testGet()
{
$skus = ['simple', 'virtual'];
+ $rawPricesData = [
+ [
+ 'value_id' => 1,
+ 'entity_id' => 2,
+ 'all_groups' => 1,
+ 'customer_group_id' => 0,
+ 'qty' => 2.0000,
+ 'value' => 2.0000,
+ 'percentage_value' => null,
+ 'website_id' => 0
+ ],
+ [
+ 'value_id' => 2,
+ 'entity_id' => 3,
+ 'all_groups' => 0,
+ 'customer_group_id' => 1,
+ 'qty' => 3.0000,
+ 'value' => 3.0000,
+ 'percentage_value' => null,
+ 'website_id' => 0
+ ],
+ [
+ 'value_id' => 3,
+ 'entity_id' => 3,
+ 'all_groups' => 0,
+ 'customer_group_id' => 2,
+ 'qty' => 3.0000,
+ 'value' => 3.0000,
+ 'percentage_value' => null,
+ 'website_id' => 0
+ ]
+ ];
$this->tierPriceValidator
->expects($this->once())
->method('validateSkus')
@@ -96,36 +131,25 @@ public function testGet()
->willReturn(['simple' => ['2' => 'simple'], 'virtual' => ['3' => 'virtual']]);
$this->tierPricePersistence->expects($this->once())
->method('get')
- ->willReturn(
- [
- [
- 'value_id' => 1,
- 'entity_id' => 2,
- 'all_groups' => 1,
- 'customer_group_id' => 0,
- 'qty' => 2.0000,
- 'value' => 2.0000,
- 'percentage_value' => null,
- 'website_id' => 0
- ],
- [
- 'value_id' => 2,
- 'entity_id' => 3,
- 'all_groups' => 1,
- 'customer_group_id' => 0,
- 'qty' => 3.0000,
- 'value' => 3.0000,
- 'percentage_value' => null,
- 'website_id' => 0
- ]
- ]
- );
+ ->willReturn($rawPricesData);
+ $this->getCustomerGroupCodesByIds->expects($this->once())
+ ->method('execute')
+ ->with([1, 2])
+ ->willReturn([1 => 'General', 2 => 'Wholesale']);
$price = $this->getMockBuilder(TierPriceInterface::class)
->getMockForAbstractClass();
- $this->tierPriceFactory->expects($this->atLeastOnce())->method('create')->willReturn($price);
+ $this->tierPriceFactory
+ ->expects($this->exactly(3))
+ ->method('create')
+ ->withConsecutive(
+ [$rawPricesData[0], 'simple'],
+ [$rawPricesData[1] + ['customer_group_code' => 'General'], 'virtual'],
+ [$rawPricesData[2] + ['customer_group_code' => 'Wholesale'], 'virtual'],
+ )
+ ->willReturn($price);
$prices = $this->tierPriceStorage->get($skus);
$this->assertNotEmpty($prices);
- $this->assertCount(2, $prices);
+ $this->assertCount(3, $prices);
}
/**
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/RepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/RepositoryTest.php
index a7eae097ef024..9c54c91480d54 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/RepositoryTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/RepositoryTest.php
@@ -131,7 +131,7 @@ public function testSave()
['product', false, null, false, $productMock],
['linkedProduct', false, null, false, $linkedProductMock],
]);
- $entityMock->expects($this->once())->method('getLinkedProductSku')->willReturn('linkedProduct');
+ $entityMock->expects($this->any())->method('getLinkedProductSku')->willReturn('linkedProduct');
$entityMock->expects($this->once())->method('getSku')->willReturn('product');
$entityMock->expects($this->exactly(1))->method('getLinkType')->willReturn('linkType');
$this->linkTypeProvider->expects($this->once())->method('getLinkTypes')->willReturn(['linkType' => $typeId]);
@@ -163,7 +163,7 @@ public function testSaveWithException()
['product', false, null, false, $productMock],
['linkedProduct', false, null, false, $linkedProductMock],
]);
- $entityMock->expects($this->once())->method('getLinkedProductSku')->willReturn('linkedProduct');
+ $entityMock->expects($this->any())->method('getLinkedProductSku')->willReturn('linkedProduct');
$entityMock->expects($this->once())->method('getSku')->willReturn('product');
$entityMock->expects($this->exactly(1))->method('getLinkType')->willReturn('linkType');
$this->linkTypeProvider->expects($this->once())->method('getLinkTypes')->willReturn(['linkType' => $typeId]);
@@ -180,6 +180,15 @@ public function testSaveWithException()
$this->model->save($entityMock);
}
+ public function testSaveWithoutLinkedProductSku()
+ {
+ $this->expectException('Magento\Framework\Exception\NoSuchEntityException');
+ $this->expectExceptionMessage('The linked product SKU is invalid. Verify the data and try again.');
+ $entityMock = $this->createMock(\Magento\Catalog\Model\ProductLink\Link::class);
+ $entityMock->expects($this->any())->method('getLinkedProductSku')->willReturn('');
+ $this->model->save($entityMock);
+ }
+
/**
* Test method
*/
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/XsdTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/XsdTest.php
index 09cc59962b505..897d8c6ad686c 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/XsdTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/XsdTest.php
@@ -42,7 +42,10 @@ protected function setUp(): void
protected function _loadDataForTest($schemaName, $xmlString, $expectedError)
{
$actualError = $this->_xsdValidator->validate($this->_xsdSchemaPath . $schemaName, $xmlString);
- $this->assertEquals($expectedError, $actualError);
+ $this->assertEquals(false, empty($actualError));
+ foreach ($expectedError as $error) {
+ $this->assertContains($error, $actualError);
+ }
}
/**
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/invalidProductOptionsXmlArray.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/invalidProductOptionsXmlArray.php
index 85edc42e1389e..34dfd614d8de9 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/invalidProductOptionsXmlArray.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/invalidProductOptionsXmlArray.php
@@ -35,9 +35,7 @@
'',
[
"Element 'option', attribute 'renderer': [facet 'pattern'] The value '123true' is not accepted by the " .
- "pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
- "Element 'option', attribute 'renderer': '123true' is not a valid value of the atomic" .
- " type 'modelName'.\nLine: 1\n"
+ "pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n"
],
],
'disabled_attribute_with_invalid_value' => [
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/XsdTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/XsdTest.php
index 606e5898b785f..0a4330662c50a 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/XsdTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/XsdTest.php
@@ -42,7 +42,10 @@ protected function setUp(): void
public function testSchemaCorrectlyIdentifiesInvalidXml($xmlString, $expectedError)
{
$actualError = $this->_xsdValidator->validate($this->_xsdSchema, $xmlString);
- $this->assertEquals($expectedError, $actualError);
+ $this->assertEquals(false, empty($actualError));
+ foreach ($expectedError as $error) {
+ $this->assertContains($error, $actualError);
+ }
}
public function testSchemaCorrectlyIdentifiesValidXml()
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/invalidProductTypesXmlArray.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/invalidProductTypesXmlArray.php
index 8175b31680741..90934c1ab93e5 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/invalidProductTypesXmlArray.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/invalidProductTypesXmlArray.php
@@ -25,9 +25,7 @@
' ',
[
"Element 'type', attribute 'modelInstance': [facet 'pattern'] The value '123' is not accepted by the" .
- " pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
- "Element 'type', attribute 'modelInstance': '123' is not a valid value of the atomic type" .
- " 'modelName'.\nLine: 1\n"
+ " pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n"
],
],
'type_indexpriority_invalid_value' => [
@@ -59,18 +57,14 @@
' ',
[
"Element 'priceModel', attribute 'instance': [facet 'pattern'] The value '123123' is not accepted " .
- "by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
- "Element 'priceModel', attribute 'instance': '123123' is not a valid value of the atomic type" .
- " 'modelName'.\nLine: 1\n"
+ "by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n"
],
],
'type_indexermodel_instance_invalid_value' => [
' ',
[
"Element 'indexerModel', attribute 'instance': [facet 'pattern'] The value '123' is not accepted by " .
- "the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
- "Element 'indexerModel', attribute 'instance': '123' is not a valid value of the atomic type" .
- " 'modelName'.\nLine: 1\n"
+ "the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n"
],
],
'type_indexermodel_without_required_instance_attribute' => [
@@ -85,9 +79,7 @@
' ',
[
"Element 'stockIndexerModel', attribute 'instance': [facet 'pattern'] The value '1234' is not " .
- "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
- "Element 'stockIndexerModel', attribute 'instance': '1234' is not a valid value of the atomic " .
- "type 'modelName'.\nLine: 1\n"
+ "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n"
],
],
'allowedselectiontypes_without_required_type_handle' => [
diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/Component/Listing/Columns/PriceTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/Component/Listing/Columns/PriceTest.php
new file mode 100644
index 0000000000000..e8d1c86c603bb
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/Component/Listing/Columns/PriceTest.php
@@ -0,0 +1,114 @@
+context = $this->createMock(ContextInterface::class);
+ $this->uiComponentFactory = $this->createMock(UiComponentFactory::class);
+ $this->localeCurrency = $this->createMock(CurrencyInterface::class);
+ $this->storeManager = $this->createMock(StoreManagerInterface::class);
+ $this->priceCurrency = $this->createMock(PriceCurrencyInterface::class);
+ $this->model = new Price(
+ $this->context,
+ $this->uiComponentFactory,
+ $this->localeCurrency,
+ $this->storeManager,
+ [],
+ ['name' => 'price'],
+ $this->priceCurrency
+ );
+ }
+
+ /**
+ * @param array $input
+ * @param array $output
+ * @return void
+ * @dataProvider prepareDataSourceDataProvider
+ */
+ public function testPrepareDataSource(array $input, array $output): void
+ {
+ $this->priceCurrency->method('format')
+ ->willReturn('formatted');
+ $this->assertEquals($output, $this->model->prepareDataSource($input));
+ }
+
+ /**
+ * @return array
+ */
+ public function prepareDataSourceDataProvider(): array
+ {
+ return [
+ [
+ [
+ 'data' => [
+ 'items' => [
+ [
+ 'price' => '10.00'
+ ]
+ ]
+ ]
+ ],
+ [
+ 'data' => [
+ 'items' => [
+ [
+ 'price' => 'formatted'
+ ]
+ ]
+ ]
+ ]
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CurrencySymbolProviderTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CurrencySymbolProviderTest.php
index 07b3de40c31f8..c1ddc16a18a47 100644
--- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CurrencySymbolProviderTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CurrencySymbolProviderTest.php
@@ -7,11 +7,12 @@
namespace Magento\Catalog\Test\Unit\Ui\DataProvider\Product\Form\Modifier;
-use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CurrencySymbolProvider;
use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Catalog\Model\Product;
+use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CurrencySymbolProvider;
use Magento\Directory\Model\Currency as CurrencyModel;
use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\Currency\Data\Currency as CurrencyData;
use Magento\Framework\Locale\CurrencyInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Store\Api\Data\StoreInterface;
@@ -19,7 +20,6 @@
use Magento\Store\Model\StoreManagerInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
-use Zend_Currency;
/**
* Test class for Website Currency Symbol provider
@@ -62,7 +62,7 @@ class CurrencySymbolProviderTest extends TestCase
private $currencyMock;
/**
- * @var Zend_Currency|MockObject
+ * @var CurrencyData|MockObject
*/
private $websiteCurrencyMock;
@@ -103,7 +103,7 @@ protected function setUp(): void
['getBaseCurrency']
);
$this->currencyMock = $this->createMock(CurrencyModel::class);
- $this->websiteCurrencyMock = $this->createMock(Zend_Currency::class);
+ $this->websiteCurrencyMock = $this->createMock(CurrencyData::class);
$this->productMock = $this->createMock(Product::class);
$this->locatorMock = $this->getMockForAbstractClass(
LocatorInterface::class,
diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php
index 52833e27bb635..0520c7f797b29 100644
--- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php
@@ -24,12 +24,12 @@
*/
class WebsitesTest extends AbstractModifierTest
{
- const PRODUCT_ID = 1;
- const WEBSITE_ID = 1;
- const GROUP_ID = 1;
- const STORE_VIEW_NAME = 'StoreView';
- const STORE_VIEW_ID = 1;
- const SECOND_WEBSITE_ID = 2;
+ public const PRODUCT_ID = 1;
+ public const WEBSITE_ID = 1;
+ public const GROUP_ID = 1;
+ public const STORE_VIEW_NAME = 'StoreView';
+ public const STORE_VIEW_ID = 1;
+ public const SECOND_WEBSITE_ID = 2;
/**
* @var WebsiteRepositoryInterface|MockObject
@@ -76,16 +76,24 @@ class WebsitesTest extends AbstractModifierTest
*/
protected $storeViewMock;
+ /**
+ * @var array
+ */
+ private $websitesList;
+
+ /**
+ * @var int
+ */
+ private $productId;
+
/**
* @inheritdoc
*/
protected function setUp(): void
{
parent::setUp();
- $this->productMock->expects($this->any())
- ->method('getId')
- ->willReturn(self::PRODUCT_ID);
$this->assignedWebsites = [self::SECOND_WEBSITE_ID];
+ $this->productId = self::PRODUCT_ID;
$this->websiteMock = $this->getMockBuilder(Website::class)
->setMethods(['getId', 'getName'])
->disableOriginalConstructor()
@@ -94,6 +102,7 @@ protected function setUp(): void
->setMethods(['getId', 'getName'])
->disableOriginalConstructor()
->getMock();
+ $this->websitesList = [$this->websiteMock, $this->secondWebsiteMock];
$this->websiteRepositoryMock = $this->getMockBuilder(WebsiteRepositoryInterface::class)
->setMethods(['getList'])
->getMockForAbstractClass();
@@ -106,17 +115,9 @@ protected function setUp(): void
$this->storeRepositoryMock = $this->getMockBuilder(StoreRepositoryInterface::class)
->setMethods(['getList'])
->getMockForAbstractClass();
- $this->productMock = $this->getMockBuilder(ProductInterface::class)
- ->setMethods(['getId'])
- ->getMockForAbstractClass();
- $this->locatorMock->expects($this->any())
- ->method('getWebsiteIds')
- ->willReturn($this->assignedWebsites);
$this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class)
->setMethods(['isSingleStoreMode', 'getWebsites'])
->getMockForAbstractClass();
- $this->storeManagerMock->method('getWebsites')
- ->willReturn([$this->websiteMock, $this->secondWebsiteMock]);
$this->storeManagerMock->expects($this->any())
->method('isSingleStoreMode')
->willReturn(false);
@@ -174,12 +175,30 @@ protected function createModel()
);
}
+ /**
+ * Initialize return values
+ * @return void
+ */
+ private function init()
+ {
+ $this->productMock->expects($this->any())
+ ->method('getId')
+ ->willReturn($this->productId);
+ $this->locatorMock->expects($this->any())
+ ->method('getWebsiteIds')
+ ->willReturn($this->assignedWebsites);
+ $this->storeManagerMock->method('getWebsites')
+ ->willReturn($this->websitesList);
+ }
+
/**
* @return void
*/
public function testModifyMeta()
{
+ $this->init();
$meta = $this->getModel()->modifyMeta([]);
+
$this->assertArrayHasKey('websites', $meta);
$this->assertArrayHasKey(self::SECOND_WEBSITE_ID, $meta['websites']['children']);
$this->assertArrayHasKey(self::WEBSITE_ID, $meta['websites']['children']);
@@ -190,7 +209,7 @@ public function testModifyMeta()
);
$this->assertEquals(
$meta['websites']['children'][self::WEBSITE_ID]['arguments']['data']['config']['value'],
- "0"
+ '0'
);
}
@@ -214,10 +233,46 @@ public function testModifyData()
]
],
];
+ $this->init();
$this->assertEquals(
$expectedData,
$this->getModel()->modifyData([])
);
}
+
+ public function testModifyDataNoWebsitesExistingProduct()
+ {
+ $this->assignedWebsites = [];
+ $this->websitesList = [$this->websiteMock];
+ $this->init();
+
+ $meta = $this->getModel()->modifyMeta([]);
+
+ $this->assertArrayHasKey(self::WEBSITE_ID, $meta['websites']['children']);
+ $this->assertArrayHasKey('copy_to_stores.' . self::WEBSITE_ID, $meta['websites']['children']);
+ $this->assertEquals(
+ '0',
+ $meta['websites']['children'][self::WEBSITE_ID]['arguments']['data']['config']['value']
+ );
+ }
+
+ public function testModifyDataNoWebsitesNewProduct()
+ {
+ $this->assignedWebsites = [];
+ $this->websitesList = [$this->websiteMock];
+ $this->productId = false;
+ $this->init();
+ $this->productMock->expects($this->any())
+ ->method('getId')
+ ->willReturn(false);
+
+ $meta = $this->getModel()->modifyMeta([]);
+
+ $this->assertArrayHasKey(self::WEBSITE_ID, $meta['websites']['children']);
+ $this->assertEquals(
+ '1',
+ $meta['websites']['children'][self::WEBSITE_ID]['arguments']['data']['config']['value']
+ );
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Modifier/PriceAttributesTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Modifier/PriceAttributesTest.php
new file mode 100644
index 0000000000000..468aafef43294
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Modifier/PriceAttributesTest.php
@@ -0,0 +1,124 @@
+storeManager = $this->createMock(StoreManagerInterface::class);
+ $this->localeCurrency = $this->createMock(CurrencyInterface::class);
+ $this->priceCurrency = $this->createMock(PriceCurrencyInterface::class);
+ $this->model = new PriceAttributes(
+ $this->storeManager,
+ $this->localeCurrency,
+ ['attr1', 'attr3'],
+ ['type2'],
+ $this->priceCurrency
+ );
+ }
+
+ /**
+ * @param array $input
+ * @param array $output
+ * @return void
+ * @dataProvider modifyDataProvider
+ */
+ public function testModifyData(array $input, array $output): void
+ {
+ $this->priceCurrency->method('format')
+ ->willReturn('formatted');
+ $this->assertEquals($output, $this->model->modifyData($input));
+ }
+
+ /**
+ * @return array
+ */
+ public function modifyDataProvider(): array
+ {
+ return [
+ [
+ [
+ 'items' => [
+ [
+ 'type_id' => 'type1',
+ 'attr1' => '11',
+ 'attr2' => '111',
+ 'attr3' => '1111',
+ ],
+ [
+ 'type_id' => 'type2',
+ 'attr1' => '22',
+ 'attr2' => '222',
+ 'attr3' => '2222',
+ ],
+ [
+ 'type_id' => 'type3',
+ 'attr1' => '33',
+ 'attr2' => '333',
+ 'attr3' => '3333',
+ ]
+ ]
+ ],
+ [
+ 'items' => [
+ [
+ 'type_id' => 'type1',
+ 'attr1' => 'formatted',
+ 'attr2' => '111',
+ 'attr3' => 'formatted',
+ ],
+ [
+ 'type_id' => 'type2',
+ 'attr1' => '22',
+ 'attr2' => '222',
+ 'attr3' => '2222',
+ ],
+ [
+ 'type_id' => 'type3',
+ 'attr1' => 'formatted',
+ 'attr2' => '333',
+ 'attr3' => 'formatted',
+ ]
+ ]
+ ]
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Price.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Price.php
index c35dad5e37bdf..2237aa4cc5b07 100644
--- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Price.php
+++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Price.php
@@ -5,6 +5,8 @@
*/
namespace Magento\Catalog\Ui\Component\Listing\Columns;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Pricing\PriceCurrencyInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
@@ -17,7 +19,7 @@ class Price extends \Magento\Ui\Component\Listing\Columns\Column
/**
* Column name
*/
- const NAME = 'column.price';
+ public const NAME = 'column.price';
/**
* @var \Magento\Framework\Locale\CurrencyInterface
@@ -29,6 +31,11 @@ class Price extends \Magento\Ui\Component\Listing\Columns\Column
*/
private $storeManager;
+ /**
+ * @var PriceCurrencyInterface
+ */
+ private $priceCurrency;
+
/**
* @param ContextInterface $context
* @param UiComponentFactory $uiComponentFactory
@@ -36,6 +43,7 @@ class Price extends \Magento\Ui\Component\Listing\Columns\Column
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param array $components
* @param array $data
+ * @param PriceCurrencyInterface|null $priceCurrency
*/
public function __construct(
ContextInterface $context,
@@ -43,11 +51,13 @@ public function __construct(
\Magento\Framework\Locale\CurrencyInterface $localeCurrency,
\Magento\Store\Model\StoreManagerInterface $storeManager,
array $components = [],
- array $data = []
+ array $data = [],
+ ?PriceCurrencyInterface $priceCurrency = null
) {
parent::__construct($context, $uiComponentFactory, $components, $data);
$this->localeCurrency = $localeCurrency;
$this->storeManager = $storeManager;
+ $this->priceCurrency = $priceCurrency ?? ObjectManager::getInstance()->get(PriceCurrencyInterface::class);
}
/**
@@ -62,12 +72,16 @@ public function prepareDataSource(array $dataSource)
$store = $this->storeManager->getStore(
$this->context->getFilterParam('store_id', \Magento\Store\Model\Store::DEFAULT_STORE_ID)
);
- $currency = $this->localeCurrency->getCurrency($store->getBaseCurrencyCode());
$fieldName = $this->getData('name');
foreach ($dataSource['data']['items'] as & $item) {
if (isset($item[$fieldName])) {
- $item[$fieldName] = $currency->toCurrency(sprintf("%f", $item[$fieldName]));
+ $item[$fieldName] = $this->priceCurrency->format(
+ sprintf("%F", $item[$fieldName]),
+ false,
+ PriceCurrencyInterface::DEFAULT_PRECISION,
+ $store
+ );
}
}
}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php
index 1d813fa5263ad..50c8e3bcf00b8 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php
@@ -372,8 +372,8 @@ protected function getOptionsGridConfig($sortOrder)
'config' => [
'addButtonLabel' => __('Add Option'),
'componentType' => DynamicRows::NAME,
- 'component' => 'Magento_Catalog/js/components/dynamic-rows-import-custom-options',
- 'template' => 'ui/dynamic-rows/templates/collapsible',
+ 'component' => 'Magento_Catalog/js/components/dynamic-rows-import-custom-options-per-page',
+ 'template' => 'Magento_Catalog/components/dynamic-rows-import-custom-options-per-page',
'additionalClasses' => 'admin__field-wide',
'deleteProperty' => static::FIELD_IS_DELETE,
'deleteValue' => '1',
@@ -387,6 +387,9 @@ protected function getOptionsGridConfig($sortOrder)
'insertData' => '${ $.provider }:${ $.dataProvider }',
'__disableTmpl' => ['insertData' => false],
],
+ 'sizesConfig' => [
+ 'enabled' => true
+ ]
],
],
],
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php
index 6915265b48818..60ef9e83aed23 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php
@@ -23,7 +23,7 @@
*/
class Websites extends AbstractModifier
{
- const SORT_ORDER = 40;
+ public const SORT_ORDER = 40;
/**
* @var LocatorInterface
@@ -210,8 +210,9 @@ protected function getFieldsForFieldset()
$sortOrder++;
}
}
-
- $children = $this->setDefaultWebsiteIdIfNoneAreSelected($children);
+ if ($isNewProduct) {
+ $children = $this->setDefaultWebsiteIdIfNoneAreSelected($children);
+ }
return $children;
}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Modifier/PriceAttributes.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Modifier/PriceAttributes.php
index 7f333441dab34..74da9af1e83da 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Modifier/PriceAttributes.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Modifier/PriceAttributes.php
@@ -7,10 +7,10 @@
namespace Magento\Catalog\Ui\DataProvider\Product\Modifier;
-use Magento\Framework\Currency;
-use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Locale\CurrencyInterface;
-use Magento\Store\Api\Data\StoreInterface;
+use Magento\Framework\Pricing\PriceCurrencyInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Ui\DataProvider\Modifier\ModifierInterface;
@@ -30,9 +30,14 @@ class PriceAttributes implements ModifierInterface
private $storeManager;
/**
- * @var CurrencyInterface
+ * @var array
*/
- private $localeCurrency;
+ private $excludeProductTypes;
+
+ /**
+ * @var PriceCurrencyInterface
+ */
+ private $priceCurrency;
/**
* PriceAttributes constructor.
@@ -40,15 +45,21 @@ class PriceAttributes implements ModifierInterface
* @param StoreManagerInterface $storeManager
* @param CurrencyInterface $localeCurrency
* @param array $priceAttributeList
+ * @param array $excludeProductTypes
+ * @param PriceCurrencyInterface|null $priceCurrency
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct(
StoreManagerInterface $storeManager,
CurrencyInterface $localeCurrency,
- array $priceAttributeList = []
+ array $priceAttributeList = [],
+ array $excludeProductTypes = [],
+ ?PriceCurrencyInterface $priceCurrency = null
) {
$this->storeManager = $storeManager;
- $this->localeCurrency = $localeCurrency;
$this->priceAttributeList = $priceAttributeList;
+ $this->excludeProductTypes = $excludeProductTypes;
+ $this->priceCurrency = $priceCurrency ?? ObjectManager::getInstance()->get(PriceCurrencyInterface::class);
}
/**
@@ -61,9 +72,18 @@ public function modifyData(array $data): array
}
foreach ($data['items'] as &$item) {
- foreach ($this->priceAttributeList as $priceAttribute) {
- if (isset($item[$priceAttribute])) {
- $item[$priceAttribute] = $this->getCurrency()->toCurrency(sprintf("%f", $item[$priceAttribute]));
+ if (!isset($item[ProductInterface::TYPE_ID])
+ || !in_array($item[ProductInterface::TYPE_ID], $this->excludeProductTypes, true)
+ ) {
+ foreach ($this->priceAttributeList as $priceAttribute) {
+ if (isset($item[$priceAttribute])) {
+ $item[$priceAttribute] = $this->priceCurrency->format(
+ sprintf("%F", $item[$priceAttribute]),
+ false,
+ PriceCurrencyInterface::DEFAULT_PRECISION,
+ $this->storeManager->getStore($item['store_id'] ?? null)
+ );
+ }
}
}
}
@@ -78,28 +98,4 @@ public function modifyMeta(array $meta): array
{
return $meta;
}
-
- /**
- * Retrieve store
- *
- * @return StoreInterface
- * @throws NoSuchEntityException
- */
- private function getStore(): StoreInterface
- {
- return $this->storeManager->getStore();
- }
-
- /**
- * Retrieve currency
- *
- * @return Currency
- * @throws NoSuchEntityException
- */
- private function getCurrency(): Currency
- {
- $baseCurrencyCode = $this->getStore()->getBaseCurrencyCode();
-
- return $this->localeCurrency->getCurrency($baseCurrencyCode);
- }
}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
index f4334bc25efd8..4f9ecdc80350b 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
@@ -5,6 +5,8 @@
*/
namespace Magento\Catalog\Ui\DataProvider\Product;
+use Magento\Framework\DB\Select;
+
/**
* Collection which is used for rendering product list in the backend.
*
@@ -25,4 +27,52 @@ protected function _productLimitationJoinPrice()
$this->_productLimitationFilters->setUsePriceIndex(false);
return $this->_productLimitationPrice(true);
}
+
+ /**
+ * @inheritdoc
+ */
+ public function getSize()
+ {
+ if ($this->_totalRecords === null) {
+ if ($this->_scopeConfig->getValue('admin/grid/limit_total_number_of_products')) {
+ $sql = $this->getSelectCountSql();
+ $estimatedRowsCount = $this->analyzeRows($sql);
+ $recordsLimit = $this->_scopeConfig->getValue('admin/grid/records_limit');
+
+ if ($estimatedRowsCount > $recordsLimit) {
+ $columns = $sql->getPart(Select::COLUMNS);
+ $sql->reset(Select::COLUMNS);
+
+ foreach ($columns as &$column) {
+ if ($column[1] instanceof \Zend_Db_Expr && $column[1] == "COUNT(DISTINCT e.entity_id)") {
+ $column[1] = new \Zend_Db_Expr('e.entity_id');
+ }
+ }
+ $sql->setPart(Select::COLUMNS, $columns);
+ $sql->limit($recordsLimit);
+ $query = new \Zend_Db_Expr('SELECT COUNT(*) FROM (' . $sql->assemble() . ') AS c');
+ $this->_totalRecords = (int)$this->getConnection()->query($query)->fetchColumn();
+ } else {
+ return parent::getSize();
+ }
+ return $this->_totalRecords;
+ }
+ return parent::getSize();
+ }
+ return $this->_totalRecords;
+ }
+
+ /**
+ * Analyze number of rows to be examined to execute the query.
+ *
+ * @param Select $sql
+ * @return mixed
+ * @throws \Zend_Db_Statement_Exception
+ */
+ private function analyzeRows(Select $sql)
+ {
+ $results = $this->getConnection()->query('EXPLAIN ' . $sql)->fetchAll();
+
+ return max(array_column($results, 'rows'));
+ }
}
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/Catalog/etc/adminhtml/di.xml b/app/code/Magento/Catalog/etc/adminhtml/di.xml
index 8e17c61c3926a..eeacd0f0970f0 100644
--- a/app/code/Magento/Catalog/etc/adminhtml/di.xml
+++ b/app/code/Magento/Catalog/etc/adminhtml/di.xml
@@ -94,6 +94,7 @@
\Magento\Catalog\Ui\DataProvider\Product\ProductCollectionFactory
Magento\Catalog\Ui\DataProvider\Product\Listing\Modifier\Pool
+
diff --git a/app/code/Magento/Catalog/etc/mview.xml b/app/code/Magento/Catalog/etc/mview.xml
index 2c9d7d448afd2..76c026a4b6914 100644
--- a/app/code/Magento/Catalog/etc/mview.xml
+++ b/app/code/Magento/Catalog/etc/mview.xml
@@ -50,6 +50,7 @@
+
diff --git a/app/code/Magento/Catalog/i18n/en_US.csv b/app/code/Magento/Catalog/i18n/en_US.csv
index d5ba3b75493eb..a5b2944a45fa2 100644
--- a/app/code/Magento/Catalog/i18n/en_US.csv
+++ b/app/code/Magento/Catalog/i18n/en_US.csv
@@ -816,4 +816,6 @@ Details,Details
"Are you sure you want to delete this category?","Are you sure you want to delete this category?"
"Attribute Set Information","Attribute Set Information"
"Failed to retrieve product links for ""%1""","Failed to retrieve product links for ""%1"""
+"The linked product SKU is invalid. Verify the data and try again.","The linked product SKU is invalid. Verify the data and try again."
+"The linked products data is invalid. Verify the data and try again.","The linked products data is invalid. Verify the data and try again."
diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml
index 2cd2a15b04900..3c0a4efca48a0 100644
--- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml
+++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml
@@ -192,6 +192,16 @@
Websites
+
+
+ dateRange
+ date
+ Last Updated At
+
+
entity_id
diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/components/dynamic-rows-import-custom-options-per-page.js b/app/code/Magento/Catalog/view/adminhtml/web/js/components/dynamic-rows-import-custom-options-per-page.js
new file mode 100644
index 0000000000000..04e889866f226
--- /dev/null
+++ b/app/code/Magento/Catalog/view/adminhtml/web/js/components/dynamic-rows-import-custom-options-per-page.js
@@ -0,0 +1,110 @@
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+ 'Magento_Catalog/js/components/dynamic-rows-import-custom-options',
+ 'underscore',
+ 'mageUtils',
+ 'uiLayout',
+ 'rjsResolver'
+], function (DrCustomOptions, _, utils, layout, resolver) {
+ 'use strict';
+
+ return DrCustomOptions.extend({
+ defaults: {
+ sizesConfig: {
+ component: 'Magento_Ui/js/grid/paging/sizes',
+ name: '${ $.name }_sizes',
+ options: {
+ '20': {
+ value: 20,
+ label: 20
+ },
+ '30': {
+ value: 30,
+ label: 30
+ },
+ '50': {
+ value: 50,
+ label: 50
+ },
+ '100': {
+ value: 100,
+ label: 100
+ },
+ '200': {
+ value: 200,
+ label: 200
+ }
+ },
+ storageConfig: {
+ provider: '${ $.storageConfig.provider }',
+ namespace: '${ $.storageConfig.namespace }'
+ },
+ enabled: false
+ },
+ links: {
+ options: '${ $.sizesConfig.name }:options',
+ pageSize: '${ $.sizesConfig.name }:value'
+ },
+ listens: {
+ 'pageSize': 'onPageSizeChange'
+ },
+ modules: {
+ sizes: '${ $.sizesConfig.name }'
+ }
+ },
+
+ /**
+ * Initializes paging component.
+ *
+ * @returns {Paging} Chainable.
+ */
+ initialize: function () {
+ this._super()
+ .initSizes();
+
+ return this;
+ },
+
+ /**
+ * Initializes sizes component.
+ *
+ * @returns {Paging} Chainable.
+ */
+ initSizes: function () {
+ if (this.sizesConfig.enabled) {
+ layout([this.sizesConfig]);
+ }
+
+ return this;
+ },
+
+ /**
+ * Initializes observable properties.
+ *
+ * @returns {Paging} Chainable.
+ */
+ initObservable: function () {
+ this._super()
+ .track([
+ 'pageSize'
+ ]);
+
+ return this;
+ },
+
+ /**
+ * Handles changes of the page size.
+ */
+ onPageSizeChange: function () {
+ resolver(function () {
+ if (this.elems().length) {
+ this.reload();
+ }
+ }, this);
+ }
+ });
+});
diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/components/dynamic-rows-import-custom-options.js b/app/code/Magento/Catalog/view/adminhtml/web/js/components/dynamic-rows-import-custom-options.js
index bcc5ad451a533..5bacb5038059a 100644
--- a/app/code/Magento/Catalog/view/adminhtml/web/js/components/dynamic-rows-import-custom-options.js
+++ b/app/code/Magento/Catalog/view/adminhtml/web/js/components/dynamic-rows-import-custom-options.js
@@ -40,10 +40,6 @@ define([
_.each(item.options, function (option) {
currentOption = utils.copy(option);
- if (currentOption.hasOwnProperty('sort_order')) {
- delete currentOption['sort_order'];
- }
-
if (currentOption.hasOwnProperty('option_id')) {
delete currentOption['option_id'];
}
diff --git a/app/code/Magento/Catalog/view/adminhtml/web/template/components/dynamic-rows-import-custom-options-per-page.html b/app/code/Magento/Catalog/view/adminhtml/web/template/components/dynamic-rows-import-custom-options-per-page.html
new file mode 100644
index 0000000000000..e125c894cad32
--- /dev/null
+++ b/app/code/Magento/Catalog/view/adminhtml/web/template/components/dynamic-rows-import-custom-options-per-page.html
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/view/base/web/images/product/placeholder/swatch_image.jpg b/app/code/Magento/Catalog/view/base/web/images/product/placeholder/swatch_image.jpg
index 0ee9d1622bc26..f73ce76cee5d1 100644
Binary files a/app/code/Magento/Catalog/view/base/web/images/product/placeholder/swatch_image.jpg and b/app/code/Magento/Catalog/view/base/web/images/product/placeholder/swatch_image.jpg differ
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/limiter.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/limiter.phtml
index fd20210af389f..04b0fe6966289 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/limiter.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar/limiter.phtml
@@ -23,7 +23,9 @@
isLimitCurrent($_key)):?>
selected="selected"
>
- = $block->escapeHtml($localeFormatter->formatNumber((int) $_limit)) ?>
+ = $block->escapeHtml(
+ is_numeric($_limit) ? $localeFormatter->formatNumber((int) $_limit) : $_limit
+ ) ?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml
index 0792b92c7f5ae..5f8353b0e7358 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml
@@ -5,11 +5,13 @@
*/
/** @var \Magento\Catalog\Block\Product\View\Details $block */
+/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */
?>
getGroupSortedChildNames('detailed_info', 'getChildHtml')):?>
getLayout(); ?>
+
renderElement($name);
@@ -19,7 +21,7 @@
$alias = $layout->getElementAlias($name);
$label = $block->getChildData($alias, 'title');
?>
-
+ = $hideTabContent
+ ? /* @noEscape */ $secureRenderer->renderStyleAsTag(
+ 'display: none;',
+ '#' . $block->escapeHtmlAttr($alias)
+ )
+ : '' ?>
+
diff --git a/app/code/Magento/Catalog/view/frontend/web/js/related-products.js b/app/code/Magento/Catalog/view/frontend/web/js/related-products.js
index bc54fdeac5c28..fc1f88554cfd7 100644
--- a/app/code/Magento/Catalog/view/frontend/web/js/related-products.js
+++ b/app/code/Magento/Catalog/view/frontend/web/js/related-products.js
@@ -51,7 +51,7 @@ define([
this.options.selectAllMessage : this.options.unselectAllMessage;
$(e.target).html(innerHTML);
- $(this.options.relatedCheckbox).attr(
+ $(this.options.relatedCheckbox + ':visible').attr(
'checked',
this.options.relatedProductsCheckFlag = !this.options.relatedProductsCheckFlag
);
diff --git a/app/code/Magento/Catalog/view/frontend/web/js/view/compare-products.js b/app/code/Magento/Catalog/view/frontend/web/js/view/compare-products.js
index 5b73f477dea63..9340052d82b00 100644
--- a/app/code/Magento/Catalog/view/frontend/web/js/view/compare-products.js
+++ b/app/code/Magento/Catalog/view/frontend/web/js/view/compare-products.js
@@ -7,12 +7,14 @@ define([
'uiComponent',
'Magento_Customer/js/customer-data',
'jquery',
+ 'underscore',
'mage/mage',
'mage/decorate'
-], function (Component, customerData, $) {
+], function (Component, customerData, $, _) {
'use strict';
- var sidebarInitialized = false;
+ var sidebarInitialized = false,
+ compareProductsReloaded = false;
/**
* Initialize sidebar
@@ -31,7 +33,19 @@ define([
initialize: function () {
this._super();
this.compareProducts = customerData.get('compare-products');
-
+ if (!compareProductsReloaded
+ && !_.isEmpty(this.compareProducts())
+ //Expired section names are reloaded on page load
+ && _.indexOf(customerData.getExpiredSectionNames(), 'compare-products') === -1
+ && window.checkout
+ && window.checkout.websiteId
+ && window.checkout.websiteId !== this.compareProducts().websiteId
+ ) {
+ //set count to 0 to prevent "compared products" blocks and count to show with wrong count and items
+ this.compareProducts().count = 0;
+ customerData.reload(['compare-products'], false);
+ compareProductsReloaded = true;
+ }
initSidebar();
}
});
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/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php
index 978e4f6b406d2..09342ceb2f602 100644
--- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php
@@ -110,7 +110,7 @@ public function getOptions(array $optionIds, ?int $storeId, array $attributeCode
if (!empty($attributeCodes)) {
$select->orWhere(
- 'a.attribute_code in (?) AND a.frontend_input = \'boolean\'',
+ 'a.attribute_code in (?) AND a.frontend_input in (\'boolean\', \'price\')',
$attributeCodes
);
}
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php
index e8c43b437f0c5..b8689cc8868d7 100644
--- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php
@@ -17,6 +17,7 @@
use Magento\Framework\Api\Search\AggregationValueInterface;
use Magento\Framework\Api\Search\BucketInterface;
use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\GraphQl\Query\Uid;
/**
* Category layer builder
@@ -75,6 +76,9 @@ class Category implements LayerBuilderInterface
*/
private Aggregations\Category\IncludeDirectChildrenOnly $includeDirectChildrenOnly;
+ /** @var Uid */
+ private Uid $uidEncoder;
+
/**
* @param CategoryAttributeQuery $categoryAttributeQuery
* @param CategoryAttributesMapper $attributesMapper
@@ -83,6 +87,7 @@ class Category implements LayerBuilderInterface
* @param LayerFormatter $layerFormatter
* @param Aggregations\Category\IncludeDirectChildrenOnly $includeDirectChildrenOnly
* @param CollectionFactory $categoryCollectionFactory
+ * @param Uid $uidEncoder
*/
public function __construct(
CategoryAttributeQuery $categoryAttributeQuery,
@@ -91,7 +96,8 @@ public function __construct(
ResourceConnection $resourceConnection,
LayerFormatter $layerFormatter,
Aggregations\Category\IncludeDirectChildrenOnly $includeDirectChildrenOnly,
- CollectionFactory $categoryCollectionFactory
+ CollectionFactory $categoryCollectionFactory,
+ Uid $uidEncoder
) {
$this->categoryAttributeQuery = $categoryAttributeQuery;
$this->attributesMapper = $attributesMapper;
@@ -100,6 +106,7 @@ public function __construct(
$this->layerFormatter = $layerFormatter;
$this->includeDirectChildrenOnly = $includeDirectChildrenOnly;
$this->categoryCollectionFactory = $categoryCollectionFactory;
+ $this->uidEncoder = $uidEncoder;
}
/**
@@ -155,7 +162,7 @@ function (AggregationValueInterface $value) {
}
$result['options'][] = $this->layerFormatter->buildItem(
$categoryLabels[$categoryId] ?? $categoryId,
- $categoryId,
+ $this->uidEncoder->encode((string) $categoryId),
$value->getMetrics()['count']
);
}
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php
index de7d1db79af1e..e765023f34a05 100644
--- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php
@@ -7,10 +7,13 @@
namespace Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder;
+use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\AttributeOptionProvider;
use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\LayerBuilderInterface;
use Magento\Framework\Api\Search\AggregationInterface;
+use Magento\Framework\Api\Search\AggregationValueInterface;
use Magento\Framework\Api\Search\BucketInterface;
use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Formatter\LayerFormatter;
+use Zend_Db_Statement_Exception;
/**
* @inheritdoc
@@ -27,6 +30,11 @@ class Price implements LayerBuilderInterface
*/
private $layerFormatter;
+ /**
+ * @var AttributeOptionProvider
+ */
+ private $attributeOptionProvider;
+
/**
* @var array
*/
@@ -39,11 +47,14 @@ class Price implements LayerBuilderInterface
/**
* @param LayerFormatter $layerFormatter
+ * @param AttributeOptionProvider $attributeOptionProvider
*/
public function __construct(
- LayerFormatter $layerFormatter
+ LayerFormatter $layerFormatter,
+ AttributeOptionProvider $attributeOptionProvider
) {
$this->layerFormatter = $layerFormatter;
+ $this->attributeOptionProvider = $attributeOptionProvider;
}
/**
@@ -52,13 +63,16 @@ public function __construct(
*/
public function build(AggregationInterface $aggregation, ?int $storeId): array
{
+ $attributeOptions = $this->getAttributeOptions($aggregation, $storeId);
+ $attributeCode = self::$bucketMap[self::PRICE_BUCKET]['request_name'];
+ $attribute = $attributeOptions[$attributeCode] ?? [];
$bucket = $aggregation->getBucket(self::PRICE_BUCKET);
if ($this->isBucketEmpty($bucket)) {
return [];
}
$result = $this->layerFormatter->buildLayer(
- self::$bucketMap[self::PRICE_BUCKET]['label'],
+ $attribute['attribute_label'] ?? self::$bucketMap[self::PRICE_BUCKET]['label'],
\count($bucket->getValues()),
self::$bucketMap[self::PRICE_BUCKET]['request_name']
);
@@ -85,4 +99,38 @@ private function isBucketEmpty(?BucketInterface $bucket): bool
{
return null === $bucket || !$bucket->getValues();
}
+
+ /**
+ * Get list of attributes with options
+ *
+ * @param AggregationInterface $aggregation
+ * @param int|null $storeId
+ * @return array
+ * @throws Zend_Db_Statement_Exception
+ */
+ private function getAttributeOptions(AggregationInterface $aggregation, ?int $storeId): array
+ {
+ $attributeOptionIds = [];
+ $attributes = [];
+
+ $bucket = $aggregation->getBucket(self::PRICE_BUCKET);
+
+ if ($this->isBucketEmpty($bucket)) {
+ return [];
+ }
+
+ $attributes[] = \preg_replace('~_bucket$~', '', $bucket->getName());
+ $attributeOptionIds[] = \array_map(
+ function (AggregationValueInterface $value) {
+ return $value->getValue();
+ },
+ $bucket->getValues()
+ );
+
+ return $this->attributeOptionProvider->getOptions(
+ \array_merge([], ...$attributeOptionIds),
+ $storeId,
+ $attributes
+ );
+ }
}
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/Product.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product.php
index 889735a5f4d88..df725c02eb5bd 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product.php
@@ -63,8 +63,8 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
$fields = $this->productFieldsSelector->getProductFieldsFromInfo($info);
$this->productDataProvider->addEavAttributes($fields);
- $result = function () use ($value) {
- $data = $value['product'] ?? $this->productDataProvider->getProductBySku($value['sku']);
+ $result = function () use ($value, $context) {
+ $data = $value['product'] ?? $this->productDataProvider->getProductBySku($value['sku'], $context);
if (empty($data)) {
return null;
}
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 21835adc92eae..a5cc522d7ccf0 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php
@@ -7,35 +7,36 @@
namespace Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider;
+use Exception;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\NodeKind;
-
-use Magento\Store\Api\Data\StoreInterface;
+use Iterator;
use Magento\Catalog\Api\Data\CategoryInterface;
use Magento\Catalog\Model\Category;
use Magento\Catalog\Model\ResourceModel\Category\Collection;
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
-use Magento\GraphQl\Model\Query\ContextInterface;
use Magento\CatalogGraphQl\Model\AttributesJoiner;
use Magento\CatalogGraphQl\Model\Category\DepthCalculator;
use Magento\CatalogGraphQl\Model\Category\LevelCalculator;
use Magento\CatalogGraphQl\Model\Resolver\Categories\DataProvider\Category\CollectionProcessorInterface;
-use Magento\CatalogGraphQl\Model\Category\Filter\SearchCriteria;
+use Magento\Framework\Api\Search\SearchCriteria;
use Magento\Framework\EntityManager\MetadataPool;
-use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\Exception\LocalizedException;
-use \Exception;
-use \Iterator;
+use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
+use Magento\GraphQl\Model\Query\ContextInterface;
+use Magento\Store\Api\Data\StoreInterface;
/**
* Category tree data provider
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CategoryTree
{
/**
* In depth we need to calculate only children nodes, so the first wrapped node should be ignored
*/
- const DEPTH_OFFSET = 1;
+ private const DEPTH_OFFSET = 1;
/**
* @var CollectionFactory
@@ -67,11 +68,6 @@ class CategoryTree
*/
private $collectionProcessor;
- /**
- * @var SearchCriteria
- */
- private $searchCriteria;
-
/**
* @param CollectionFactory $collectionFactory
* @param AttributesJoiner $attributesJoiner
@@ -79,7 +75,6 @@ class CategoryTree
* @param LevelCalculator $levelCalculator
* @param MetadataPool $metadata
* @param CollectionProcessorInterface $collectionProcessor
- * @param SearchCriteria $searchCriteria
*/
public function __construct(
CollectionFactory $collectionFactory,
@@ -87,8 +82,7 @@ public function __construct(
DepthCalculator $depthCalculator,
LevelCalculator $levelCalculator,
MetadataPool $metadata,
- CollectionProcessorInterface $collectionProcessor,
- SearchCriteria $searchCriteria
+ CollectionProcessorInterface $collectionProcessor
) {
$this->collectionFactory = $collectionFactory;
$this->attributesJoiner = $attributesJoiner;
@@ -96,7 +90,6 @@ public function __construct(
$this->levelCalculator = $levelCalculator;
$this->metadata = $metadata;
$this->collectionProcessor = $collectionProcessor;
- $this->searchCriteria = $searchCriteria;
}
/**
@@ -125,7 +118,8 @@ public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId, int $stor
* @throws LocalizedException
* @throws Exception
*/
- private function getCollection(ResolveInfo $resolveInfo, int $rootCategoryId) : Collection {
+ private function getCollection(ResolveInfo $resolveInfo, int $rootCategoryId) : Collection
+ {
$categoryQuery = $resolveInfo->fieldNodes[0];
$collection = $this->collectionFactory->create();
$this->joinAttributesRecursively($collection, $categoryQuery, $resolveInfo);
@@ -139,6 +133,9 @@ private function getCollection(ResolveInfo $resolveInfo, int $rootCategoryId) :
$regExpPathFilter = sprintf('.*/%s/[/0-9]*$', $rootCategoryId);
}
+ //Add `is_anchor` attribute to selected field
+ $collection->addAttributeToSelect('is_anchor');
+
//Search for desired part of category tree
$collection->addPathFilter($regExpPathFilter);
@@ -196,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/Model/Resolver/Products/DataProvider/Deferred/Product.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Deferred/Product.php
index 8218c22b8056d..a528efcb4a81a 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Deferred/Product.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Deferred/Product.php
@@ -62,10 +62,7 @@ public function __construct(
*/
public function addProductSku(string $sku) : void
{
- if (!in_array($sku, $this->productSkus) && !empty($this->productList)) {
- $this->productList = [];
- $this->productSkus[] = $sku;
- } elseif (!in_array($sku, $this->productSkus)) {
+ if (!in_array($sku, $this->productSkus)) {
$this->productSkus[] = $sku;
}
}
@@ -79,12 +76,7 @@ public function addProductSku(string $sku) : void
public function addProductSkus(array $skus) : void
{
foreach ($skus as $sku) {
- if (!in_array($sku, $this->productSkus) && !empty($this->productList)) {
- $this->productList = [];
- $this->productSkus[] = $sku;
- } elseif (!in_array($sku, $this->productSkus)) {
- $this->productSkus[] = $sku;
- }
+ $this->addProductSku($sku);
}
}
@@ -103,43 +95,53 @@ public function addEavAttributes(array $attributeCodes) : void
* Get product from result set.
*
* @param string $sku
+ * @param null|ContextInterface $context
* @return array
*/
- public function getProductBySku(string $sku) : array
+ public function getProductBySku(string $sku, ContextInterface $context = null) : array
{
- $products = $this->fetch();
+ if (isset($this->productList[$sku])) {
+ return $this->productList[$sku];
+ }
- if (!isset($products[$sku])) {
+ $this->fetch($context);
+
+ if (!isset($this->productList[$sku])) {
return [];
}
- return $products[$sku];
+ return $this->productList[$sku];
}
/**
* Fetch product data and return in array format. Keys for products will be their skus.
*
- * @return array
+ * @param null|ContextInterface $context
*/
- private function fetch() : array
+ private function fetch(ContextInterface $context = null): void
{
- if (empty($this->productSkus) || !empty($this->productList)) {
- return $this->productList;
+ if (empty($this->productSkus)) {
+ return;
}
- $this->searchCriteriaBuilder->addFilter(ProductInterface::SKU, $this->productSkus, 'in');
+ $skusToFetch = array_diff($this->productSkus, array_keys($this->productList));
+
+ if (empty($skusToFetch)) {
+ return;
+ }
+
+ $this->searchCriteriaBuilder->addFilter(ProductInterface::SKU, $skusToFetch, 'in');
$result = $this->productDataProvider->getList(
$this->searchCriteriaBuilder->create(),
$this->attributeCodes,
false,
- false
+ true,
+ $context
);
/** @var \Magento\Catalog\Model\Product $product */
foreach ($result->getItems() as $product) {
$this->productList[$product->getSku()] = ['model' => $product];
}
-
- return $this->productList;
}
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product/CollectionProcessor/VisibilityStatusProcessor.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product/CollectionProcessor/VisibilityStatusProcessor.php
index 964edc9d5a0ad..8f8a9bb7e5c8f 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product/CollectionProcessor/VisibilityStatusProcessor.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product/CollectionProcessor/VisibilityStatusProcessor.php
@@ -37,6 +37,13 @@ public function process(
): Collection {
$collection->joinAttribute('status', 'catalog_product/status', 'entity_id', null, 'inner');
$collection->joinAttribute('visibility', 'catalog_product/visibility', 'entity_id', null, 'inner');
+ if ($context) {
+ $store = $context->getExtensionAttributes()->getStore();
+ if ($store) {
+ $websiteId = $store->getWebsiteId();
+ $collection->addWebsiteFilter([$websiteId]);
+ }
+ }
return $collection;
}
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 5273a8a15cd8d..0e0fa9d95580f 100644
--- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml
+++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml
@@ -191,4 +191,37 @@
+
+
+ Magento\CatalogGraphQl\Category\DataObjectProcessor
+
+
+
+
+
+ Magento\Catalog\Model\ResourceModel\Product\ChildCollectionFactory
+
+
+
+
+
+
+ Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ChildProduct
+
+
+
+
+
+
+ -
+
- getChildren
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php
index 900f70dadb07d..4d3dceeb3eb62 100644
--- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php
@@ -9,6 +9,7 @@
use Magento\Catalog\Model\ResourceModel\Product\Option\Collection;
use Magento\CatalogImportExport\Model\Import\Product as ImportProduct;
use Magento\CatalogImportExport\Model\Import\Product\CategoryProcessor;
+use Magento\CatalogInventory\Api\StockConfigurationInterface;
use Magento\Framework\App\ObjectManager;
use Magento\ImportExport\Model\Import;
use Magento\Store\Model\Store;
@@ -169,6 +170,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
*
* @var array
* @deprecated 100.2.0
+ * @see we don't use this variable anymore
*/
protected $_headerColumns = [];
@@ -368,6 +370,11 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
*/
private $filter;
+ /**
+ * @var StockConfigurationInterface
+ */
+ private $stockConfiguration;
+
/**
* Product constructor.
*
@@ -388,7 +395,8 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
* @param ProductEntity\LinkTypeProvider $linkTypeProvider
* @param RowCustomizerInterface $rowCustomizer
* @param array $dateAttrCodes
- * @param ProductFilterInterface $filter
+ * @param ProductFilterInterface|null $filter
+ * @param StockConfigurationInterface|null $stockConfiguration
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function __construct(
@@ -409,7 +417,8 @@ public function __construct(
\Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider,
\Magento\CatalogImportExport\Model\Export\RowCustomizerInterface $rowCustomizer,
array $dateAttrCodes = [],
- ?ProductFilterInterface $filter = null
+ ?ProductFilterInterface $filter = null,
+ ?StockConfigurationInterface $stockConfiguration = null
) {
$this->_entityCollectionFactory = $collectionFactory;
$this->_exportConfig = $exportConfig;
@@ -426,7 +435,8 @@ public function __construct(
$this->rowCustomizer = $rowCustomizer;
$this->dateAttrCodes = array_merge($this->dateAttrCodes, $dateAttrCodes);
$this->filter = $filter ?? ObjectManager::getInstance()->get(ProductFilterInterface::class);
-
+ $this->stockConfiguration = $stockConfiguration ?? ObjectManager::getInstance()
+ ->get(StockConfigurationInterface::class);
parent::__construct($localeDate, $config, $resource, $storeManager);
$this->initTypeModels()
@@ -460,6 +470,7 @@ protected function initCategories()
{
$collection = $this->_categoryColFactory->create()->addNameToResult();
/* @var $collection \Magento\Catalog\Model\ResourceModel\Category\Collection */
+ $collection->setStoreId(\Magento\Store\Model\Store::DEFAULT_STORE_ID);
foreach ($collection as $category) {
$structure = preg_split('#/+#', $category->getPath());
$pathSize = count($structure);
@@ -625,6 +636,14 @@ protected function prepareCatalogInventory(array $productIds)
$stockItemRow['stock_id'],
$stockItemRow['stock_status_changed_auto']
);
+
+ if ($stockItemRow['use_config_max_sale_qty']) {
+ $stockItemRow['max_sale_qty'] = $this->stockConfiguration->getMaxSaleQty();
+ }
+
+ if ($stockItemRow['use_config_min_sale_qty']) {
+ $stockItemRow['min_sale_qty'] = $this->stockConfiguration->getMinSaleQty();
+ }
$stockItemRows[$productId] = $stockItemRow;
}
return $stockItemRows;
@@ -755,7 +774,8 @@ private function getNonSystemAttributes(): array
* @param array $customOptionsData
* @param array $stockItemRows
* @return void
- * @deprecated 100.2.0 Logic will be moved to _getHeaderColumns in future release
+ * @deprecated 100.2.0
+ * @see Logic is moved to _getHeaderColumns
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
@@ -1182,6 +1202,7 @@ protected function collectMultirawData()
* @param int $storeId
* @return bool
* @deprecated 100.2.3
+ * @see This protected method is not used anymore
*/
protected function hasMultiselectData($item, $storeId)
{
@@ -1410,6 +1431,7 @@ private function updateGalleryImageData(&$dataRow, $rawData)
* Add multi row data to export
*
* @deprecated 100.1.0
+ * @see This protected method is not used anymore
* @param array $dataRow
* @param array $multiRawData
* @return array
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index ac18211b44bd4..e7c7ede1ca3db 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -13,9 +13,11 @@
use Magento\CatalogImportExport\Model\Import\Product\LinkProcessor;
use Magento\CatalogImportExport\Model\Import\Product\MediaGalleryProcessor;
use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface as ValidatorInterface;
+use Magento\CatalogImportExport\Model\Import\Product\Skip;
use Magento\CatalogImportExport\Model\Import\Product\StatusProcessor;
use Magento\CatalogImportExport\Model\Import\Product\StockProcessor;
use Magento\CatalogImportExport\Model\StockItemImporterInterface;
+use Magento\CatalogImportExport\Model\StockItemProcessorInterface;
use Magento\CatalogInventory\Api\Data\StockItemInterface;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\App\ObjectManager;
@@ -23,7 +25,6 @@
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Filesystem;
use Magento\Framework\Filesystem\Driver\File;
-use Magento\Framework\Filesystem\DriverPool;
use Magento\Framework\Intl\DateTimeFactory;
use Magento\Framework\Model\ResourceModel\Db\ObjectRelationProcessor;
use Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface;
@@ -49,7 +50,6 @@ class Product extends AbstractEntity
{
private const DEFAULT_GLOBAL_MULTIPLE_VALUE_SEPARATOR = ',';
public const CONFIG_KEY_PRODUCT_TYPES = 'global/importexport/import_product_types';
- private const HASH_ALGORITHM = 'sha256';
/**
* Size of bunch - part of products to save in one step.
@@ -228,6 +228,7 @@ class Product extends AbstractEntity
*
* @deprecated 101.1.0 use DI for LinkProcessor class if you want to add additional types
*
+ * @see Magento_CatalogImportExport::etc/di.xml
* @var array
*/
protected $_linkNameToId = [
@@ -261,6 +262,11 @@ class Product extends AbstractEntity
*/
protected $_mediaGalleryAttributeId = null;
+ /**
+ * @var string
+ */
+ private $hashAlgorithm = 'crc32c';
+
/**
* @var array
* @codingStandardsIgnoreStart
@@ -548,6 +554,7 @@ class Product extends AbstractEntity
/**
* @var \Magento\CatalogInventory\Model\ResourceModel\Stock\ItemFactory
* @deprecated 101.0.0 this variable isn't used anymore.
+ * @see we don't recommend this approach anymore
*/
protected $_stockResItemFac;
@@ -612,7 +619,9 @@ class Product extends AbstractEntity
/**
* @var array
* @deprecated 100.0.3
+ *
* @since 100.0.3
+ * @see we don't recommend this approach anymore
*/
protected $productUrlKeys = [];
@@ -751,6 +760,11 @@ class Product extends AbstractEntity
*/
private $linkProcessor;
+ /**
+ * @var StockItemProcessorInterface
+ */
+ private $stockItemProcessor;
+
/**
* @param \Magento\Framework\Json\Helper\Data $jsonHelper
* @param \Magento\ImportExport\Helper\Data $importExportData
@@ -800,6 +814,7 @@ class Product extends AbstractEntity
* @param StockProcessor|null $stockProcessor
* @param LinkProcessor|null $linkProcessor
* @param File|null $fileDriver
+ * @param StockItemProcessorInterface|null $stockItemProcessor
* @throws LocalizedException
* @throws \Magento\Framework\Exception\FileSystemException
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
@@ -854,7 +869,8 @@ public function __construct(
StatusProcessor $statusProcessor = null,
StockProcessor $stockProcessor = null,
LinkProcessor $linkProcessor = null,
- ?File $fileDriver = null
+ ?File $fileDriver = null,
+ ?StockItemProcessorInterface $stockItemProcessor = null
) {
$this->_eventManager = $eventManager;
$this->stockRegistry = $stockRegistry;
@@ -897,7 +913,7 @@ public function __construct(
$this->linkProcessor = $linkProcessor ?? ObjectManager::getInstance()
->get(LinkProcessor::class);
$this->linkProcessor->addNameToIds($this->_linkNameToId);
-
+ $this->hashAlgorithm = (version_compare(PHP_VERSION, '8.1.0') >= 0) ? 'xxh128' : 'crc32c';
parent::__construct(
$jsonHelper,
$importExportData,
@@ -918,6 +934,8 @@ public function __construct(
$this->dateTimeFactory = $dateTimeFactory ?? ObjectManager::getInstance()->get(DateTimeFactory::class);
$this->productRepository = $productRepository ?? ObjectManager::getInstance()
->get(ProductRepositoryInterface::class);
+ $this->stockItemProcessor = $stockItemProcessor ?? ObjectManager::getInstance()
+ ->get(StockItemProcessorInterface::class);
}
/**
@@ -1048,7 +1066,7 @@ protected function _deleteProducts()
{
$productEntityTable = $this->_resourceFactory->create()->getEntityTable();
- while ($bunch = $this->_dataSourceModel->getNextBunch()) {
+ while ($bunch = $this->_dataSourceModel->getNextUniqueBunch($this->getIds())) {
$idsToDelete = [];
foreach ($bunch as $rowNum => $rowData) {
@@ -1144,13 +1162,18 @@ protected function _saveProductsData()
foreach ($this->_productTypeModels as $productTypeModel) {
$productTypeModel->saveData();
}
- $this->linkProcessor->saveLinks($this, $this->_dataSourceModel, $this->getProductEntityLinkField());
+ $this->linkProcessor->saveLinks(
+ $this,
+ $this->_dataSourceModel,
+ $this->getProductEntityLinkField(),
+ $this->getIds()
+ );
$this->_saveStockItem();
if ($this->_replaceFlag) {
$this->getOptionEntity()->clearProductsSkuToId();
}
+ $this->getOptionEntity()->setIds($this->getIds());
$this->getOptionEntity()->importData();
-
return $this;
}
@@ -1280,12 +1303,13 @@ protected function _prepareRowForDb(array $rowData)
* Must be called after ALL products saving done.
*
* @deprecated 101.1.0 use linkProcessor Directly
+ * @see linkProcessor
*
* @return $this
*/
protected function _saveLinks()
{
- $this->linkProcessor->saveLinks($this, $this->_dataSourceModel, $this->getProductEntityLinkField());
+ $this->linkProcessor->saveLinks($this, $this->_dataSourceModel, $this->getProductEntityLinkField(), []);
return $this;
}
@@ -1404,7 +1428,6 @@ public function saveProductEntity(array $entityRowsIn, array $entityRowsUp)
static $entityTable = null;
$this->countItemsCreated += count($entityRowsIn);
$this->countItemsUpdated += count($entityRowsUp);
-
if (!$entityTable) {
$entityTable = $this->_resourceFactory->create()->getEntityTable();
}
@@ -1413,7 +1436,6 @@ public function saveProductEntity(array $entityRowsIn, array $entityRowsUp)
}
if ($entityRowsIn) {
$this->_connection->insertMultiple($entityTable, $entityRowsIn);
-
$select = $this->_connection->select()->from(
$entityTable,
array_merge($this->getNewSkuFieldsForSelect(), $this->getOldSkuFieldsForSelect())
@@ -1428,10 +1450,8 @@ public function saveProductEntity(array $entityRowsIn, array $entityRowsUp)
$this->skuProcessor->setNewSkuData($sku, $key, $value);
}
}
-
$this->updateOldSku($newProducts);
}
-
return $this;
}
@@ -1489,6 +1509,7 @@ private function getNewSkuFieldsForSelect()
* @return void
* @since 100.0.4
* @deprecated 100.2.3
+ * @see \Magento\CatalogImportExport\Model\Import\Product\MediaGalleryProcessor::initMediaGalleryResources
*/
protected function initMediaGalleryResources()
{
@@ -1566,14 +1587,12 @@ public function getImagesFromRow(array $rowData)
protected function _saveProducts()
{
$priceIsGlobal = $this->_catalogData->isPriceGlobal();
- $productLimit = null;
- $productsQty = null;
- $entityLinkField = $this->getProductEntityLinkField();
-
- while ($bunch = $this->_dataSourceModel->getNextBunch()) {
+ $previousType = null;
+ $prevAttributeSet = null;
+ $productMediaPath = $this->getProductMediaPath();
+ while ($bunch = $this->_dataSourceModel->getNextUniqueBunch($this->getIds())) {
$entityRowsIn = [];
$entityRowsUp = [];
- $attributes = [];
$this->websitesCache = [];
$this->categoriesCache = [];
$tierPrices = [];
@@ -1581,430 +1600,490 @@ protected function _saveProducts()
$labelsForUpdate = [];
$imagesForChangeVisibility = [];
$uploadedImages = [];
- $previousType = null;
- $prevAttributeSet = null;
-
$existingImages = $this->getExistingImages($bunch);
- $this->addImageHashes($existingImages);
-
+ $attributes = [];
foreach ($bunch as $rowNum => $rowData) {
- // reset category processor's failed categories array
- $this->categoryProcessor->clearFailedCategories();
-
- if (!$this->validateRow($rowData, $rowNum)) {
- continue;
- }
- if ($this->getErrorAggregator()->hasToBeTerminated()) {
- $this->getErrorAggregator()->addRowToSkip($rowNum);
- continue;
- }
- $rowScope = $this->getRowScope($rowData);
-
- $urlKey = $this->getUrlKey($rowData);
- if (!empty($rowData[self::URL_KEY])) {
- // If url_key column and its value were in the CSV file
- $rowData[self::URL_KEY] = $urlKey;
- } elseif ($this->isNeedToChangeUrlKey($rowData)) {
- // If url_key column was empty or even not declared in the CSV file but by the rules it is need to
- // be setteed. In case when url_key is generating from name column we have to ensure that the bunch
- // of products will pass for the event with url_key column.
- $bunch[$rowNum][self::URL_KEY] = $rowData[self::URL_KEY] = $urlKey;
- }
-
- $rowSku = $rowData[self::COL_SKU];
- $rowSkuNormalized = mb_strtolower($rowSku);
-
- if (null === $rowSku) {
- $this->getErrorAggregator()->addRowToSkip($rowNum);
- continue;
- }
-
- $storeId = !empty($rowData[self::COL_STORE])
- ? $this->getStoreIdByCode($rowData[self::COL_STORE])
- : Store::DEFAULT_STORE_ID;
- $rowExistingImages = $existingImages[$storeId][$rowSkuNormalized] ?? [];
- $rowStoreMediaGalleryValues = $rowExistingImages;
- $rowExistingImages += $existingImages[Store::DEFAULT_STORE_ID][$rowSkuNormalized] ?? [];
-
- if (self::SCOPE_STORE == $rowScope) {
- // set necessary data from SCOPE_DEFAULT row
- $rowData[self::COL_TYPE] = $this->skuProcessor->getNewSku($rowSku)['type_id'];
- $rowData['attribute_set_id'] = $this->skuProcessor->getNewSku($rowSku)['attr_set_id'];
- $rowData[self::COL_ATTR_SET] = $this->skuProcessor->getNewSku($rowSku)['attr_set_code'];
- }
-
- // 1. Entity phase
- if ($this->isSkuExist($rowSku)) {
- // existing row
- if (isset($rowData['attribute_set_code'])) {
- $attributeSetId = $this->catalogConfig->getAttributeSetId(
- $this->getEntityTypeId(),
- $rowData['attribute_set_code']
- );
-
- // wrong attribute_set_code was received
- if (!$attributeSetId) {
- throw new LocalizedException(
- __(
- 'Wrong attribute set code "%1", please correct it and try again.',
- $rowData['attribute_set_code']
- )
- );
- }
- } else {
- $attributeSetId = $this->skuProcessor->getNewSku($rowSku)['attr_set_id'];
+ try {
+ // reset category processor's failed categories array
+ $this->categoryProcessor->clearFailedCategories();
+ if (!$this->validateRow($rowData, $rowNum)) {
+ continue;
}
-
- $entityRowsUp[] = [
- 'updated_at' => (new \DateTime())->format(DateTime::DATETIME_PHP_FORMAT),
- 'attribute_set_id' => $attributeSetId,
- $entityLinkField => $this->getExistingSku($rowSku)[$entityLinkField]
- ];
- } else {
- if (!$productLimit || $productsQty < $productLimit) {
- $entityRowsIn[strtolower($rowSku)] = [
- 'attribute_set_id' => $this->skuProcessor->getNewSku($rowSku)['attr_set_id'],
- 'type_id' => $this->skuProcessor->getNewSku($rowSku)['type_id'],
- 'sku' => $rowSku,
- 'has_options' => isset($rowData['has_options']) ? $rowData['has_options'] : 0,
- 'created_at' => (new \DateTime())->format(DateTime::DATETIME_PHP_FORMAT),
- 'updated_at' => (new \DateTime())->format(DateTime::DATETIME_PHP_FORMAT),
- ];
- $productsQty++;
- } else {
- $rowSku = null;
- // sign for child rows to be skipped
+ if ($this->getErrorAggregator()->hasToBeTerminated()) {
$this->getErrorAggregator()->addRowToSkip($rowNum);
continue;
}
- }
-
- if (!array_key_exists($rowSku, $this->websitesCache)) {
- $this->websitesCache[$rowSku] = [];
- }
- // 2. Product-to-Website phase
- if (!empty($rowData[self::COL_PRODUCT_WEBSITES])) {
- $websiteCodes = explode($this->getMultipleValueSeparator(), $rowData[self::COL_PRODUCT_WEBSITES]);
- foreach ($websiteCodes as $websiteCode) {
- $websiteId = $this->storeResolver->getWebsiteCodeToId($websiteCode);
- $this->websitesCache[$rowSku][$websiteId] = true;
+ $rowScope = $this->getRowScope($rowData);
+ $urlKey = $this->getUrlKey($rowData);
+ if (!empty($rowData[self::URL_KEY])) {
+ // If url_key column and its value were in the CSV file
+ $rowData[self::URL_KEY] = $urlKey;
+ } elseif ($this->isNeedToChangeUrlKey($rowData)) {
+ // If url_key column was empty or even not declared in the CSV file but by the rules it needs
+ // to be settled. In case when url_key is generating from name column we have to ensure that
+ // the bunch of products will pass for the event with url_key column.
+ $bunch[$rowNum][self::URL_KEY] = $rowData[self::URL_KEY] = $urlKey;
}
- } else {
- $product = $this->retrieveProductBySku($rowSku);
- if ($product) {
- $websiteIds = $product->getWebsiteIds();
- foreach ($websiteIds as $websiteId) {
- $this->websitesCache[$rowSku][$websiteId] = true;
- }
+ $rowSku = $rowData[self::COL_SKU];
+ if (null === $rowSku) {
+ $this->getErrorAggregator()->addRowToSkip($rowNum);
+ continue;
}
+ $storeId = !empty($rowData[self::COL_STORE])
+ ? $this->getStoreIdByCode($rowData[self::COL_STORE])
+ : Store::DEFAULT_STORE_ID;
+ if (self::SCOPE_STORE == $rowScope) {
+ // set necessary data from SCOPE_DEFAULT row
+ $rowData[self::COL_TYPE] = $this->skuProcessor->getNewSku($rowSku)['type_id'];
+ $rowData['attribute_set_id'] = $this->skuProcessor->getNewSku($rowSku)['attr_set_id'];
+ $rowData[self::COL_ATTR_SET] = $this->skuProcessor->getNewSku($rowSku)['attr_set_code'];
+ }
+ $this->saveProductEntityPhase($rowData, $entityRowsUp, $entityRowsIn);
+ $this->saveProductToWebsitePhase($rowData);
+ $this->saveProductCategoriesPhase($rowNum, $rowData);
+ $this->saveProductTierPricesPhase($rowData, $priceIsGlobal, $tierPrices);
+ $this->saveProductMediaGalleryPhase(
+ $rowNum,
+ $rowData,
+ $storeId,
+ $existingImages,
+ $productMediaPath,
+ $uploadedImages,
+ $imagesForChangeVisibility,
+ $labelsForUpdate,
+ $mediaGallery
+ );
+ $this->saveProductAttributesPhase(
+ $rowData,
+ $rowScope,
+ $previousType,
+ $prevAttributeSet,
+ $attributes
+ );
+ // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch
+ } catch (Skip $skip) {
+ // Product is skipped. Go on to the next one.
}
-
- // 3. Categories phase
- if (!array_key_exists($rowSku, $this->categoriesCache)) {
- $this->categoriesCache[$rowSku] = [];
- }
- $rowData['rowNum'] = $rowNum;
- $categoryIds = $this->processRowCategories($rowData);
- foreach ($categoryIds as $id) {
- $this->categoriesCache[$rowSku][$id] = true;
- }
- unset($rowData['rowNum']);
-
- // 4.1. Tier prices phase
- if (!empty($rowData['_tier_price_website'])) {
- $tierPrices[$rowSku][] = [
- 'all_groups' => $rowData['_tier_price_customer_group'] == self::VALUE_ALL,
- 'customer_group_id' => $rowData['_tier_price_customer_group'] ==
- self::VALUE_ALL ? 0 : $rowData['_tier_price_customer_group'],
- 'qty' => $rowData['_tier_price_qty'],
- 'value' => $rowData['_tier_price_price'],
- 'website_id' => self::VALUE_ALL == $rowData['_tier_price_website'] ||
- $priceIsGlobal ? 0 : $this->storeResolver->getWebsiteCodeToId($rowData['_tier_price_website']),
- ];
+ }
+ foreach ($bunch as $rowNum => $rowData) {
+ if ($this->getErrorAggregator()->isRowInvalid($rowNum)) {
+ unset($bunch[$rowNum]);
}
+ }
+ $this->saveProductEntity($entityRowsIn, $entityRowsUp);
+ $this->_saveProductWebsites($this->websitesCache);
+ $this->_saveProductCategories($this->categoriesCache);
+ $this->_saveProductTierPrices($tierPrices);
+ $this->_saveMediaGallery($mediaGallery);
+ $this->updateMediaGalleryVisibility($imagesForChangeVisibility);
+ $this->updateMediaGalleryLabels($labelsForUpdate);
+ $this->_saveProductAttributes($attributes);
+ $this->_eventManager->dispatch(
+ 'catalog_product_import_bunch_save_after',
+ ['adapter' => $this, 'bunch' => $bunch]
+ );
+ }
+ return $this;
+ }
+ //phpcs:enable Generic.Metrics.NestingLevel
- if (!$this->validateRow($rowData, $rowNum)) {
- continue;
- }
+ // phpcs:enable
- // 5. Media gallery phase
- list($rowImages, $rowLabels) = $this->getImagesFromRow($rowData);
- $imageHiddenStates = $this->getImagesHiddenStates($rowData);
- foreach (array_keys($imageHiddenStates) as $image) {
- //Mark image as uploaded if it exists
- if (array_key_exists($image, $rowExistingImages)) {
- $uploadedImages[$image] = $image;
- }
- //Add image to hide to images list if it does not exist
- if (empty($rowImages[self::COL_MEDIA_IMAGE])
- || !in_array($image, $rowImages[self::COL_MEDIA_IMAGE])
- ) {
- $rowImages[self::COL_MEDIA_IMAGE][] = $image;
- }
+ /**
+ * In _saveProducts loop, save product entity
+ *
+ * @param array $rowData
+ * @param array $entityRowsUp
+ * @param array $entityRowsIn
+ * @return void
+ * @throws LocalizedException
+ */
+ private function saveProductEntityPhase(array $rowData, array &$entityRowsUp, array &$entityRowsIn) : void
+ {
+ $rowSku = $rowData[self::COL_SKU];
+ if ($this->isSkuExist($rowSku)) {
+ // existing row
+ if (isset($rowData['attribute_set_code'])) {
+ $attributeSetId = $this->catalogConfig->getAttributeSetId(
+ $this->getEntityTypeId(),
+ $rowData['attribute_set_code']
+ );
+ // wrong attribute_set_code was received
+ if (!$attributeSetId) {
+ throw new LocalizedException(
+ __(
+ 'Wrong attribute set code "%1", please correct it and try again.',
+ $rowData['attribute_set_code']
+ )
+ );
}
+ } else {
+ $attributeSetId = $this->skuProcessor->getNewSku($rowSku)['attr_set_id'];
+ }
+ $entityLinkField = $this->getProductEntityLinkField();
+ $entityRowsUp[] = [
+ 'updated_at' => (new \DateTime())->format(DateTime::DATETIME_PHP_FORMAT),
+ 'attribute_set_id' => $attributeSetId,
+ $entityLinkField => $this->getExistingSku($rowSku)[$entityLinkField]
+ ];
+ } else {
+ $entityRowsIn[strtolower($rowSku)] = [
+ 'attribute_set_id' => $this->skuProcessor->getNewSku($rowSku)['attr_set_id'],
+ 'type_id' => $this->skuProcessor->getNewSku($rowSku)['type_id'],
+ 'sku' => $rowSku,
+ 'has_options' => isset($rowData['has_options']) ? $rowData['has_options'] : 0,
+ 'created_at' => (new \DateTime())->format(DateTime::DATETIME_PHP_FORMAT),
+ 'updated_at' => (new \DateTime())->format(DateTime::DATETIME_PHP_FORMAT),
+ ];
+ }
+ }
- $rowData[self::COL_MEDIA_IMAGE] = [];
- list($rowImages, $rowData) = $this->clearNoSelectionImages($rowImages, $rowData);
-
- /*
- * Note: to avoid problems with undefined sorting, the value of media gallery items positions
- * must be unique in scope of one product.
- */
- $position = 0;
- foreach ($rowImages as $column => $columnImages) {
- foreach ($columnImages as $columnImageKey => $columnImage) {
- $hash = filter_var($columnImage, FILTER_VALIDATE_URL)
- ? $this->getRemoteFileHash($columnImage)
- : $this->getFileHash($this->joinFilePaths($this->getUploader()->getTmpDir(), $columnImage));
- $uploadedFile = $this->findImageByHash($rowExistingImages, $hash);
- if (!$uploadedFile && !isset($uploadedImages[$columnImage])) {
- $uploadedFile = $this->uploadMediaFiles($columnImage);
- $uploadedFile = $uploadedFile ?: $this->getSystemFile($columnImage);
- if ($uploadedFile) {
- $uploadedImages[$columnImage] = $uploadedFile;
- } else {
- unset($rowData[$column]);
- $this->addRowError(
- ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE,
- $rowNum,
- null,
- null,
- ProcessingError::ERROR_LEVEL_NOT_CRITICAL
- );
- }
- } elseif (isset($uploadedImages[$columnImage])) {
- $uploadedFile = $uploadedImages[$columnImage];
- }
-
- if ($uploadedFile && $column !== self::COL_MEDIA_IMAGE) {
- $rowData[$column] = $uploadedFile;
- }
+ /**
+ * In _saveProducts loop, save product to website
+ *
+ * @param array $rowData
+ * @return void
+ */
+ private function saveProductToWebsitePhase(array $rowData) : void
+ {
+ $rowSku = $rowData[self::COL_SKU];
+ if (!array_key_exists($rowSku, $this->websitesCache)) {
+ $this->websitesCache[$rowSku] = [];
+ }
+ if (!empty($rowData[self::COL_PRODUCT_WEBSITES])) {
+ $websiteCodes = explode($this->getMultipleValueSeparator(), $rowData[self::COL_PRODUCT_WEBSITES]);
+ foreach ($websiteCodes as $websiteCode) {
+ $websiteId = $this->storeResolver->getWebsiteCodeToId($websiteCode);
+ $this->websitesCache[$rowSku][$websiteId] = true;
+ }
+ } else {
+ $product = $this->retrieveProductBySku($rowSku);
+ if ($product) {
+ $websiteIds = $product->getWebsiteIds();
+ foreach ($websiteIds as $websiteId) {
+ $this->websitesCache[$rowSku][$websiteId] = true;
+ }
+ }
+ }
+ }
- if (!$uploadedFile || isset($mediaGallery[$storeId][$rowSku][$uploadedFile])) {
- continue;
- }
+ /**
+ * In _saveProducts loop, save product's categories
+ *
+ * @param int $rowNum
+ * @param array $rowData
+ * @return void
+ */
+ private function saveProductCategoriesPhase(int $rowNum, array $rowData) : void
+ {
+ $rowSku = $rowData[self::COL_SKU];
+ if (!array_key_exists($rowSku, $this->categoriesCache)) {
+ $this->categoriesCache[$rowSku] = [];
+ }
+ $rowData['rowNum'] = $rowNum;
+ $categoryIds = $this->processRowCategories($rowData);
+ foreach ($categoryIds as $id) {
+ $this->categoriesCache[$rowSku][$id] = true;
+ }
+ }
- $uploadedFileNormalized = ltrim($uploadedFile, '/\\');
- if (isset($rowExistingImages[$uploadedFileNormalized])) {
- $currentFileData = $rowExistingImages[$uploadedFileNormalized];
- $currentFileData['store_id'] = $storeId;
- $storeMediaGalleryValueExists = isset($rowStoreMediaGalleryValues[$uploadedFileNormalized]);
- if (array_key_exists($uploadedFile, $imageHiddenStates)
- && $currentFileData['disabled'] != $imageHiddenStates[$uploadedFile]
- ) {
- $imagesForChangeVisibility[] = [
- 'disabled' => $imageHiddenStates[$uploadedFile],
- 'imageData' => $currentFileData,
- 'exists' => $storeMediaGalleryValueExists
- ];
- $storeMediaGalleryValueExists = true;
- }
-
- if (isset($rowLabels[$column][$columnImageKey])
- && $rowLabels[$column][$columnImageKey] !== $currentFileData['label']
- ) {
- $labelsForUpdate[] = [
- 'label' => $rowLabels[$column][$columnImageKey],
- 'imageData' => $currentFileData,
- 'exists' => $storeMediaGalleryValueExists
- ];
- }
- } else {
- if ($column === self::COL_MEDIA_IMAGE) {
- $rowData[$column][] = $uploadedFile;
- }
- $mediaGalleryStoreData = [
- 'attribute_id' => $this->getMediaGalleryAttributeId(),
- 'label' => isset($rowLabels[$column][$columnImageKey])
- ? $rowLabels[$column][$columnImageKey]
- : '',
- 'position' => ++$position,
- 'disabled' => isset($imageHiddenStates[$columnImage])
- ? $imageHiddenStates[$columnImage] : '0',
- 'value' => $uploadedFile,
- ];
- $mediaGallery[$storeId][$rowSku][$uploadedFile] = $mediaGalleryStoreData;
- // Add record for default scope if it does not exist
- if (!($mediaGallery[Store::DEFAULT_STORE_ID][$rowSku][$uploadedFile] ?? [])) {
- //Set label and disabled values to their default values
- $mediaGalleryStoreData['label'] = null;
- $mediaGalleryStoreData['disabled'] = 0;
- $mediaGallery[Store::DEFAULT_STORE_ID][$rowSku][$uploadedFile] = $mediaGalleryStoreData;
- }
+ /**
+ * In _saveProducts loop, save product's tier prices
+ *
+ * @param array $rowData
+ * @param bool $priceIsGlobal
+ * @param array $tierPrices
+ * @return void
+ */
+ private function saveProductTierPricesPhase(array $rowData, bool $priceIsGlobal, array &$tierPrices) : void
+ {
+ $rowSku = $rowData[self::COL_SKU];
+ if (!empty($rowData['_tier_price_website'])) {
+ $tierPrices[$rowSku][] = [
+ 'all_groups' => $rowData['_tier_price_customer_group'] == self::VALUE_ALL,
+ 'customer_group_id' => $rowData['_tier_price_customer_group'] ==
+ self::VALUE_ALL ? 0 : $rowData['_tier_price_customer_group'],
+ 'qty' => $rowData['_tier_price_qty'],
+ 'value' => $rowData['_tier_price_price'],
+ 'website_id' => self::VALUE_ALL == $rowData['_tier_price_website'] ||
+ $priceIsGlobal ? 0 : $this->storeResolver->getWebsiteCodeToId($rowData['_tier_price_website']),
+ ];
+ }
+ }
- }
+ /**
+ * In _saveProducts loop, save product's media gallery
+ *
+ * @param int $rowNum
+ * @param array $rowData
+ * @param int $storeId
+ * @param array $existingImages
+ * @param string $productMediaPath
+ * @param array $uploadedImages
+ * @param array $imagesForChangeVisibility
+ * @param array $labelsForUpdate
+ * @param array $mediaGallery
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ * @return void
+ */
+ private function saveProductMediaGalleryPhase(
+ int $rowNum,
+ array &$rowData,
+ int $storeId,
+ array $existingImages,
+ string $productMediaPath,
+ array &$uploadedImages,
+ array &$imagesForChangeVisibility,
+ array &$labelsForUpdate,
+ array &$mediaGallery
+ ) : void {
+ $rowSku = $rowData[self::COL_SKU];
+ $rowSkuNormalized = mb_strtolower($rowSku);
+ $rowExistingImages = $existingImages[$storeId][$rowSkuNormalized] ?? [];
+ $rowStoreMediaGalleryValues = $rowExistingImages;
+ $rowExistingImages += $existingImages[Store::DEFAULT_STORE_ID][$rowSkuNormalized] ?? [];
+ list($rowImages, $rowLabels) = $this->getImagesFromRow($rowData);
+ $imageHiddenStates = $this->getImagesHiddenStates($rowData);
+ foreach (array_keys($imageHiddenStates) as $image) {
+ //Mark image as uploaded if it exists
+ if (array_key_exists($image, $rowExistingImages)) {
+ $uploadedImages[$image] = $image;
+ }
+ //Add image to hide to images list if it does not exist
+ if (empty($rowImages[self::COL_MEDIA_IMAGE])
+ || !in_array($image, $rowImages[self::COL_MEDIA_IMAGE])
+ ) {
+ $rowImages[self::COL_MEDIA_IMAGE][] = $image;
+ }
+ }
+ $rowData[self::COL_MEDIA_IMAGE] = [];
+ list($rowImages, $rowData) = $this->clearNoSelectionImages($rowImages, $rowData);
+ /*
+ * Note: to avoid problems with undefined sorting, the value of media gallery items positions
+ * must be unique in scope of one product.
+ */
+ $position = 0;
+ $imagesByHash = [];
+ foreach ($rowImages as $column => $columnImages) {
+ foreach ($columnImages as $columnImageKey => $columnImage) {
+ $uploadedFile = $this->findImageByColumnImage(
+ $productMediaPath,
+ $rowExistingImages,
+ $columnImage,
+ $imagesByHash
+ );
+ if (!$uploadedFile && !isset($uploadedImages[$columnImage])) {
+ $uploadedFile = $this->uploadMediaFiles($columnImage);
+ $uploadedFile = $uploadedFile ?: $this->getSystemFile($columnImage);
+ if ($uploadedFile) {
+ $uploadedImages[$columnImage] = $uploadedFile;
+ } else {
+ unset($rowData[$column]);
+ $this->addRowError(
+ ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE,
+ $rowNum,
+ null,
+ null,
+ ProcessingError::ERROR_LEVEL_NOT_CRITICAL
+ );
}
+ } elseif (isset($uploadedImages[$columnImage])) {
+ $uploadedFile = $uploadedImages[$columnImage];
}
-
- // 6. Attributes phase
- $rowStore = (self::SCOPE_STORE == $rowScope)
- ? $this->storeResolver->getStoreCodeToId($rowData[self::COL_STORE])
- : 0;
- $productType = isset($rowData[self::COL_TYPE]) ? $rowData[self::COL_TYPE] : null;
- if ($productType !== null) {
- $previousType = $productType;
+ if ($uploadedFile && $column !== self::COL_MEDIA_IMAGE) {
+ $rowData[$column] = $uploadedFile;
}
- if (isset($rowData[self::COL_ATTR_SET])) {
- $prevAttributeSet = $rowData[self::COL_ATTR_SET];
+ if (!$uploadedFile || isset($mediaGallery[$storeId][$rowSku][$uploadedFile])) {
+ continue;
}
- if (self::SCOPE_NULL == $rowScope) {
- // for multiselect attributes only
- if ($prevAttributeSet !== null) {
- $rowData[self::COL_ATTR_SET] = $prevAttributeSet;
+ $uploadedFileNormalized = ltrim($uploadedFile, '/\\');
+ if (isset($rowExistingImages[$uploadedFileNormalized])) {
+ $currentFileData = $rowExistingImages[$uploadedFileNormalized];
+ $currentFileData['store_id'] = $storeId;
+ $storeMediaGalleryValueExists = isset($rowStoreMediaGalleryValues[$uploadedFileNormalized]);
+ if (array_key_exists($uploadedFile, $imageHiddenStates)
+ && $currentFileData['disabled'] != $imageHiddenStates[$uploadedFile]
+ ) {
+ $imagesForChangeVisibility[] = [
+ 'disabled' => $imageHiddenStates[$uploadedFile],
+ 'imageData' => $currentFileData,
+ 'exists' => $storeMediaGalleryValueExists
+ ];
+ $storeMediaGalleryValueExists = true;
}
- if ($productType === null && $previousType !== null) {
- $productType = $previousType;
+ if (isset($rowLabels[$column][$columnImageKey])
+ && $rowLabels[$column][$columnImageKey] !== $currentFileData['label']
+ ) {
+ $labelsForUpdate[] = [
+ 'label' => $rowLabels[$column][$columnImageKey],
+ 'imageData' => $currentFileData,
+ 'exists' => $storeMediaGalleryValueExists
+ ];
}
- if ($productType === null) {
- continue;
+ } else {
+ if ($column === self::COL_MEDIA_IMAGE) {
+ $rowData[$column][] = $uploadedFile;
+ }
+ $mediaGalleryStoreData = [
+ 'attribute_id' => $this->getMediaGalleryAttributeId(),
+ 'label' => isset($rowLabels[$column][$columnImageKey])
+ ? $rowLabels[$column][$columnImageKey]
+ : '',
+ 'position' => ++$position,
+ 'disabled' => isset($imageHiddenStates[$columnImage])
+ ? $imageHiddenStates[$columnImage] : '0',
+ 'value' => $uploadedFile,
+ ];
+ $mediaGallery[$storeId][$rowSku][$uploadedFile] = $mediaGalleryStoreData;
+ // Add record for default scope if it does not exist
+ if (!($mediaGallery[Store::DEFAULT_STORE_ID][$rowSku][$uploadedFile] ?? [])) {
+ //Set label and disabled values to their default values
+ $mediaGalleryStoreData['label'] = null;
+ $mediaGalleryStoreData['disabled'] = 0;
+ $mediaGallery[Store::DEFAULT_STORE_ID][$rowSku][$uploadedFile] = $mediaGalleryStoreData;
}
}
+ }
+ }
+ }
- $productTypeModel = $this->_productTypeModels[$productType];
- if (isset($rowData['tax_class_name']) && strlen($rowData['tax_class_name'])) {
- $rowData['tax_class_id'] =
- $this->taxClassProcessor->upsertTaxClass($rowData['tax_class_name'], $productTypeModel);
- }
-
- if ($this->getBehavior() == Import::BEHAVIOR_APPEND ||
- empty($rowData[self::COL_SKU])
- ) {
- $rowData = $productTypeModel->clearEmptyData($rowData);
- }
-
- $rowData = $productTypeModel->prepareAttributesWithDefaultValueForSave(
- $rowData,
- !$this->isSkuExist($rowSku)
+ /**
+ * In _saveProducts loop, save product's attributes
+ *
+ * @param array $rowData
+ * @param int $rowScope
+ * @param mixed $previousType
+ * @param mixed $prevAttributeSet
+ * @param array $attributes
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ * @return void
+ */
+ private function saveProductAttributesPhase(
+ array $rowData,
+ int $rowScope,
+ &$previousType,
+ &$prevAttributeSet,
+ array &$attributes
+ ) : void {
+ $rowSku = $rowData[self::COL_SKU];
+ $rowStore = (self::SCOPE_STORE == $rowScope)
+ ? $this->storeResolver->getStoreCodeToId($rowData[self::COL_STORE])
+ : 0;
+ $productType = isset($rowData[self::COL_TYPE]) ? $rowData[self::COL_TYPE] : null;
+ if ($productType !== null) {
+ $previousType = $productType;
+ }
+ if (isset($rowData[self::COL_ATTR_SET])) {
+ $prevAttributeSet = $rowData[self::COL_ATTR_SET];
+ }
+ if (self::SCOPE_NULL == $rowScope) {
+ // for multiselect attributes only
+ if ($prevAttributeSet !== null) {
+ $rowData[self::COL_ATTR_SET] = $prevAttributeSet;
+ }
+ if ($productType === null && $previousType !== null) {
+ $productType = $previousType;
+ }
+ if ($productType === null) {
+ throw new Skip(__('Unknown Product Type'));
+ }
+ }
+ $productTypeModel = $this->_productTypeModels[$productType];
+ if (isset($rowData['tax_class_name']) && strlen($rowData['tax_class_name'])) {
+ $rowData['tax_class_id'] =
+ $this->taxClassProcessor->upsertTaxClass($rowData['tax_class_name'], $productTypeModel);
+ }
+ if ($this->getBehavior() == Import::BEHAVIOR_APPEND ||
+ empty($rowData[self::COL_SKU])
+ ) {
+ $rowData = $productTypeModel->clearEmptyData($rowData);
+ }
+ $rowData = $productTypeModel->prepareAttributesWithDefaultValueForSave(
+ $rowData,
+ !$this->isSkuExist($rowSku)
+ );
+ $product = $this->_proxyProdFactory->create(['data' => $rowData]);
+ foreach ($rowData as $attrCode => $attrValue) {
+ $attribute = $this->retrieveAttributeByCode($attrCode);
+ if ('multiselect' != $attribute->getFrontendInput() && self::SCOPE_NULL == $rowScope) {
+ // skip attribute processing for SCOPE_NULL rows
+ continue;
+ }
+ $attrId = $attribute->getId();
+ $backModel = $attribute->getBackendModel();
+ $attrTable = $attribute->getBackend()->getTable();
+ $storeIds = [0];
+ if ('datetime' == $attribute->getBackendType()
+ && (
+ in_array($attribute->getAttributeCode(), $this->dateAttrCodes)
+ || $attribute->getIsUserDefined()
+ )
+ ) {
+ $attrValue = $this->dateTime->formatDate($attrValue, false);
+ } elseif ('datetime' == $attribute->getBackendType() && strtotime($attrValue)) {
+ $attrValue = gmdate(
+ 'Y-m-d H:i:s',
+ $this->_localeDate->date($attrValue)->getTimestamp()
);
- $product = $this->_proxyProdFactory->create(['data' => $rowData]);
-
- foreach ($rowData as $attrCode => $attrValue) {
- $attribute = $this->retrieveAttributeByCode($attrCode);
-
- if ('multiselect' != $attribute->getFrontendInput() && self::SCOPE_NULL == $rowScope) {
- // skip attribute processing for SCOPE_NULL rows
- continue;
- }
- $attrId = $attribute->getId();
- $backModel = $attribute->getBackendModel();
- $attrTable = $attribute->getBackend()->getTable();
- $storeIds = [0];
-
- if ('datetime' == $attribute->getBackendType()
- && (
- in_array($attribute->getAttributeCode(), $this->dateAttrCodes)
- || $attribute->getIsUserDefined()
- )
- ) {
- $attrValue = $this->dateTime->formatDate($attrValue, false);
- } elseif ('datetime' == $attribute->getBackendType() && strtotime($attrValue)) {
- $attrValue = gmdate(
- 'Y-m-d H:i:s',
- $this->_localeDate->date($attrValue)->getTimestamp()
- );
- } elseif ($backModel) {
- $attribute->getBackend()->beforeSave($product);
- $attrValue = $product->getData($attribute->getAttributeCode());
- }
- if (self::SCOPE_STORE == $rowScope) {
- if (self::SCOPE_WEBSITE == $attribute->getIsGlobal()) {
- // check website defaults already set
- if (!isset($attributes[$attrTable][$rowSku][$attrId][$rowStore])) {
- $storeIds = $this->storeResolver->getStoreIdToWebsiteStoreIds($rowStore);
- }
- } elseif (self::SCOPE_STORE == $attribute->getIsGlobal()) {
- $storeIds = [$rowStore];
- }
- if (!$this->isSkuExist($rowSku)) {
- $storeIds[] = 0;
- }
- }
- foreach ($storeIds as $storeId) {
- if (!isset($attributes[$attrTable][$rowSku][$attrId][$storeId])) {
- $attributes[$attrTable][$rowSku][$attrId][$storeId] = $attrValue;
- }
+ } elseif ($backModel) {
+ $attribute->getBackend()->beforeSave($product);
+ $attrValue = $product->getData($attribute->getAttributeCode());
+ }
+ if (self::SCOPE_STORE == $rowScope) {
+ if (self::SCOPE_WEBSITE == $attribute->getIsGlobal()) {
+ // check website defaults already set
+ if (!isset($attributes[$attrTable][$rowSku][$attrId][$rowStore])) {
+ $storeIds = $this->storeResolver->getStoreIdToWebsiteStoreIds($rowStore);
}
- // restore 'backend_model' to avoid 'default' setting
- $attribute->setBackendModel($backModel);
+ } elseif (self::SCOPE_STORE == $attribute->getIsGlobal()) {
+ $storeIds = [$rowStore];
+ }
+ if (!$this->isSkuExist($rowSku)) {
+ $storeIds[] = 0;
}
}
-
- foreach ($bunch as $rowNum => $rowData) {
- if ($this->getErrorAggregator()->isRowInvalid($rowNum)) {
- unset($bunch[$rowNum]);
+ foreach ($storeIds as $storeId) {
+ if (!isset($attributes[$attrTable][$rowSku][$attrId][$storeId])) {
+ $attributes[$attrTable][$rowSku][$attrId][$storeId] = $attrValue;
}
}
-
- $this->saveProductEntity($entityRowsIn, $entityRowsUp)
- ->_saveProductWebsites($this->websitesCache)
- ->_saveProductCategories($this->categoriesCache)
- ->_saveProductTierPrices($tierPrices)
- ->_saveMediaGallery($mediaGallery)
- ->_saveProductAttributes($attributes)
- ->updateMediaGalleryVisibility($imagesForChangeVisibility)
- ->updateMediaGalleryLabels($labelsForUpdate);
-
- $this->_eventManager->dispatch(
- 'catalog_product_import_bunch_save_after',
- ['adapter' => $this, 'bunch' => $bunch]
- );
+ // restore 'backend_model' to avoid 'default' setting
+ $attribute->setBackendModel($backModel);
}
-
- return $this;
}
- //phpcs:enable Generic.Metrics.NestingLevel
-
- // phpcs:enable
/**
- * Returns image hash by path
+ * Returns image content by path
*
* @param string $path
* @return string
* @throws \Magento\Framework\Exception\FileSystemException
*/
- private function getFileHash(string $path): string
+ private function getFileContent(string $path): string
{
- $content = '';
if ($this->_mediaDirectory->isFile($path)
&& $this->_mediaDirectory->isReadable($path)
) {
- $content = $this->_mediaDirectory->readFile($path);
+ return $this->_mediaDirectory->readFile($path);
}
- return $content ? hash(self::HASH_ALGORITHM, $content) : '';
+ return '';
}
/**
- * Returns hash for remote file
+ * Returns content for remote file
*
* @param string $filename
* @return string
*/
- private function getRemoteFileHash(string $filename): string
+ private function getRemoteFileContent(string $filename): string
{
- $hash = hash_file(self::HASH_ALGORITHM, $filename);
- return $hash !== false ? $hash : '';
- }
-
- /**
- * Generate hashes for existing images for comparison with newly uploaded images.
- *
- * @param array $images
- * @return void
- */
- private function addImageHashes(array &$images): void
- {
- $productMediaPath = $this->getProductMediaPath();
- foreach ($images as $storeId => $skus) {
- foreach ($skus as $sku => $files) {
- foreach ($files as $path => $file) {
- $hash = $this->getFileHash($this->joinFilePaths($productMediaPath, $file['value']));
- if ($hash) {
- $images[$storeId][$sku][$path]['hash'] = $hash;
- }
- }
- }
- }
+ // phpcs:disable Magento2.Functions.DiscouragedFunction
+ $content = file_get_contents($filename);
+ // phpcs:enable Magento2.Functions.DiscouragedFunction
+ return $content !== false ? $content : '';
}
/**
@@ -2320,8 +2399,9 @@ protected function _saveProductWebsites(array $websiteData)
*/
protected function _saveStockItem()
{
- while ($bunch = $this->_dataSourceModel->getNextBunch()) {
+ while ($bunch = $this->_dataSourceModel->getNextUniqueBunch($this->getIds())) {
$stockData = [];
+ $importedData = [];
$productIdsToReindex = [];
$stockChangedProductIds = [];
// Format bunch to stock data rows
@@ -2347,12 +2427,13 @@ protected function _saveStockItem()
if (!isset($stockData[$sku])) {
$stockData[$sku] = $row;
+ $importedData[$sku] = $rowData;
}
}
// Insert rows
if (!empty($stockData)) {
- $this->stockItemImporter->import($stockData);
+ $this->stockItemProcessor->process($stockData, $importedData);
}
$this->reindexStockStatus($stockChangedProductIds);
@@ -2457,7 +2538,7 @@ public function getNewSku($sku = null)
*/
public function getNextBunch()
{
- return $this->_dataSourceModel->getNextBunch();
+ return $this->_dataSourceModel->getNextUniqueBunch($this->getIds());
}
/**
@@ -2944,7 +3025,7 @@ protected function checkUrlKeyDuplicates()
)->joinLeft(
['cpe' => $resource->getTable('catalog_product_entity')],
"cpe.entity_id = url_rewrite.entity_id"
- )->where('request_path IN (?)', array_keys($urlKeys))
+ )->where('request_path IN (?)', array_map('strval', array_keys($urlKeys)))
->where('store_id IN (?)', $storeId)
->where('cpe.sku not in (?)', array_values($urlKeys))
);
@@ -3018,7 +3099,7 @@ protected function getResource()
}
/**
- * Whether a url key is needed to be change.
+ * Whether a url key needs to change.
*
* @param array $rowData
* @return bool
@@ -3252,24 +3333,63 @@ private function getRowExistingStockItem(array $rowData): StockItemInterface
}
/**
- * Returns image that matches the provided hash
+ * Returns image that matches the provided image content
*
+ * @param string $productMediaPath
* @param array $images
- * @param string $hash
+ * @param string $columnImage
+ * @param array $imagesByHash
* @return string
*/
- private function findImageByHash(array $images, string $hash): string
- {
- $value = '';
- if ($hash) {
- foreach ($images as $image) {
- if (isset($image['hash']) && $image['hash'] === $hash) {
- $value = $image['value'];
- break;
+ private function findImageByColumnImage(
+ string $productMediaPath,
+ array &$images,
+ string $columnImage,
+ array &$imagesByHash
+ ): string {
+ $content = filter_var($columnImage, FILTER_VALIDATE_URL)
+ ? $this->getRemoteFileContent($columnImage)
+ : $this->getFileContent($this->joinFilePaths($this->getUploader()->getTmpDir(), $columnImage));
+ if (!$content) {
+ return '';
+ }
+ return $this->findImageByColumnImageUsingHash($productMediaPath, $images, $content, $imagesByHash);
+ }
+
+ /**
+ * Returns image that matches the provided image content using hash
+ *
+ * @param string $productMediaPath
+ * @param array $images
+ * @param string $content
+ * @param array $imagesByHash
+ * @return string
+ */
+ private function findImageByColumnImageUsingHash(
+ string $productMediaPath,
+ array &$images,
+ string $content,
+ array &$imagesByHash
+ ): string {
+ $hash = hash($this->hashAlgorithm, $content);
+ if (!empty($imagesByHash[$hash])) {
+ return $imagesByHash[$hash];
+ }
+ foreach ($images as &$image) {
+ if (!isset($image['hash'])) {
+ $imageContent = $this->getFileContent($this->joinFilePaths($productMediaPath, $image['value']));
+ if (!$imageContent) {
+ $image['hash'] = '';
+ continue;
}
+ $image['hash'] = hash($this->hashAlgorithm, $imageContent);
+ $imagesByHash[$image['hash']] = $image['value'];
+ }
+ if (!empty($image['hash']) && $image['hash'] === $hash) {
+ return $image['value'];
}
}
- return $value;
+ return '';
}
/**
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php
index f8633a85afa61..47b5528e956d9 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php
@@ -78,13 +78,15 @@ public function __construct(
* @param Product $importEntity
* @param Data $dataSourceModel
* @param string $linkField
+ * @param array $ids
* @return void
* @throws LocalizedException
*/
public function saveLinks(
Product $importEntity,
Data $dataSourceModel,
- string $linkField
+ string $linkField,
+ array $ids
): void {
$resource = $this->linkFactory->create();
$mainTable = $resource->getMainTable();
@@ -101,7 +103,7 @@ public function saveLinks(
$bind = [':link_id' => $linkId, ':position' => 'position'];
$positionAttrId[$linkId] = $importEntity->getConnection()->fetchOne($select, $bind);
}
- while ($bunch = $dataSourceModel->getNextBunch()) {
+ while ($bunch = $dataSourceModel->getNextUniqueBunch($ids)) {
$nextLinkId = $this->resourceHelper->getNextAutoincrement($mainTable);
$this->processLinkBunches($importEntity, $linkField, $bunch, $resource, $nextLinkId, $positionAttrId);
}
@@ -111,6 +113,7 @@ public function saveLinks(
* Add link types (exists for backwards compatibility)
*
* @deprecated 101.1.0 Use DI to inject to the constructor
+ * @see Nothing
* @param array $nameToIds
*/
public function addNameToIds(array $nameToIds): void
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php
index f13b1a640c2aa..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,14 +1279,17 @@ 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;
- while ($bunch = $this->_dataSourceModel->getNextBunch()) {
+ $this->optionNewIdExistingIdMap = [];
+ $this->optionTypeNewIdExistingIdMap = [];
+ $prevRowSku = null;
+ while ($bunch = $this->_dataSourceModel->getNextUniqueBunch($this->getIds())) {
$products = [];
$options = [];
$titles = [];
@@ -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/Model/Import/Product/Skip.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Skip.php
new file mode 100644
index 0000000000000..6416456dabc59
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Skip.php
@@ -0,0 +1,15 @@
+_attrSetColFac = $attrSetColFac;
$this->_prodAttrColFac = $prodAttrColFac;
$this->_resource = $resource;
$this->connection = $resource->getConnection();
$this->metadataPool = $metadataPool ?: $this->getMetadataPool();
+ $this->attributeOptionCollectionFactory = $attributeOptionCollectionFactory
+ ?: ObjectManager::getInstance()->get(AttributeOptionCollectionFactory::class);
if ($this->isSuitable()) {
if (!isset($params[0])
|| !isset($params[1])
@@ -183,11 +195,9 @@ public function __construct(
}
$this->_entityModel = $params[0];
$this->_type = $params[1];
-
$this->initMessageTemplates(
array_merge($this->_genericMessageTemplates, $this->_messageTemplates)
);
-
$this->_initAttributes();
}
}
@@ -257,6 +267,10 @@ protected function _getProductAttributes($attrSetData)
/**
* Initialize attributes parameters for all attributes' sets.
*
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ *
* @return $this
*/
protected function _initAttributes()
@@ -284,80 +298,168 @@ protected function _initAttributes()
$absentKeys[$attributeRow['attribute_set_name']][] = $attributeRow['attribute_id'];
}
}
- foreach ($absentKeys as $attributeSetName => $attributeIds) {
+ $addedAttributes = [];
+ foreach ($absentKeys as $attributeIds) {
$unknownAttributeIds = array_diff(
$attributeIds,
array_keys(self::$commonAttributesCache),
self::$invAttributesCache
);
- if ($unknownAttributeIds || $this->_forcedAttributesCodes) {
- $this->attachAttributesById($attributeSetName, $attributeIds);
+ if ($unknownAttributeIds) {
+ $addedAttributes[] = $this->attachAttributesByOnlyId($unknownAttributeIds);
+ }
+ }
+ if ($this->_forcedAttributesCodes) {
+ $addedAttributes[] = $this->attachAttributesByForcedCodes();
+ }
+ $addedAttributes = array_merge(...$addedAttributes);
+ $attributesToLoadFromTable = [];
+ foreach ($addedAttributes as $addedAttribute) {
+ if (isset($addedAttribute['options_use_table'])) {
+ $attributesToLoadFromTable[] = $addedAttribute['id'];
+ unset(self::$commonAttributesCache[$addedAttribute['id']]['options_use_table']);
+ }
+ }
+ foreach (array_chunk($attributesToLoadFromTable, 1000) as $attributesToLoadFromTableChunk) {
+ $collection = $this->attributeOptionCollectionFactory->create();
+ $collection->setAttributeFilter(['in' => $attributesToLoadFromTableChunk]);
+ $collection->setStoreFilter(\Magento\Store\Model\Store::DEFAULT_STORE_ID);
+ foreach ($collection as $option) {
+ $attributeId = $option->getAttributeId();
+ $value = strtolower($option->getValue());
+ self::$commonAttributesCache[$attributeId]['options'][$value] = $option->getOptionId();
}
}
foreach ($entityAttributes as $attributeRow) {
if (isset(self::$commonAttributesCache[$attributeRow['attribute_id']])) {
$attribute = self::$commonAttributesCache[$attributeRow['attribute_id']];
- $this->_addAttributeParams(
- $attributeRow['attribute_set_name'],
- self::$commonAttributesCache[$attributeRow['attribute_id']],
- $attribute
- );
+ $this->_addAttributeParams($attributeRow['attribute_set_name'], $attribute, $attribute);
+ }
+ }
+ foreach (array_keys($this->_attributes) as $setName) {
+ foreach ($this->_forcedAttributesCodes as $code) {
+ $attributeId = self::$attributeCodeToId[$code] ?? null;
+ if (null === $attributeId) {
+ continue;
+ }
+ if (isset($this->_attributes[$setName][$code])) {
+ continue;
+ }
+ $attribute = self::$commonAttributesCache[$attributeId] ?? null;
+ if (!$attribute) {
+ continue;
+ }
+ $this->_addAttributeParams($setName, $attribute, $attribute);
}
}
return $this;
}
/**
- * Attach Attributes By Id
+ * Attach Attributes By Id and _forcedAttributesCodes
*
* @param string $attributeSetName
* @param array $attributeIds
* @return void
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ * @deprecated use attachAttributesByOnlyId and attachAttributesByForcedCodes
+ * @see attachAttributesByOnlyId() and attachAttributesByForcedCodes()
*/
protected function attachAttributesById($attributeSetName, $attributeIds)
{
foreach ($this->_prodAttrColFac->create()->addFieldToFilter(
['main_table.attribute_id', 'main_table.attribute_code'],
- [
- ['in' => $attributeIds],
- ['in' => $this->_forcedAttributesCodes]
- ]
+ [['in' => $attributeIds], ['in' => $this->_forcedAttributesCodes]]
) as $attribute) {
- $attributeCode = $attribute->getAttributeCode();
- $attributeId = $attribute->getId();
-
- if ($attribute->getIsVisible() || in_array($attributeCode, $this->_forcedAttributesCodes)) {
- if (!isset(self::$commonAttributesCache[$attributeId])) {
- $defaultValue = $attribute->getDefaultValue();
- self::$commonAttributesCache[$attributeId] = [
- 'id' => $attributeId,
- 'code' => $attributeCode,
- 'is_global' => $attribute->getIsGlobal(),
- 'is_required' => $attribute->getIsRequired(),
- 'is_unique' => $attribute->getIsUnique(),
- 'frontend_label' => $attribute->getFrontendLabel(),
- 'is_static' => $attribute->isStatic(),
- 'apply_to' => $attribute->getApplyTo(),
- 'type' => \Magento\ImportExport\Model\Import::getAttributeType($attribute),
- 'default_value' => (is_string($defaultValue) && strlen($defaultValue)) ?
- $attribute->getDefaultValue() : null,
- 'options' => $this->_entityModel->getAttributeOptions(
- $attribute,
- $this->_indexValueAttributes
- ),
- ];
- }
+ $this->attachAttribute($attribute);
+ }
+ }
+
+ /**
+ * Attach Attributes By Id
+ *
+ * @param array $attributeIds
+ * @return array
+ */
+ private function attachAttributesByOnlyId(array $attributeIds) : array
+ {
+ $addedAttributes = [];
+ foreach ($this->_prodAttrColFac->create()->addFieldToFilter(
+ ['main_table.attribute_id'],
+ [['in' => $attributeIds]]
+ ) as $attribute) {
+ $cachedAttribute = $this->attachAttribute($attribute);
+ if (null !== $cachedAttribute) {
+ $addedAttributes[] = $cachedAttribute;
+ }
+ }
+ return $addedAttributes;
+ }
+ /**
+ * Attach Attributes By _forcedAttributesCodes
+ *
+ * @return array
+ */
+ private function attachAttributesByForcedCodes() : array
+ {
+ $addedAttributes = [];
+ foreach ($this->_prodAttrColFac->create()->addFieldToFilter(
+ ['main_table.attribute_code'],
+ [['in' => $this->_forcedAttributesCodes]]
+ ) as $attribute) {
+ $cachedAttribute = $this->attachAttribute($attribute);
+ if (null !== $cachedAttribute) {
+ $addedAttributes[] = $cachedAttribute;
+ }
+ }
+ return $addedAttributes;
+ }
+
+ /**
+ * Attach Attribute to self::$commonAttributesCache or self::$invAttributesCache
+ *
+ * @param Attribute $attribute
+ * @return array|null
+ */
+ private function attachAttribute(Attribute $attribute)
+ {
+ $cachedAttribute = null;
+ $attributeCode = $attribute->getAttributeCode();
+ $attributeId = $attribute->getId();
+ if ($attribute->getIsVisible() || in_array($attributeCode, $this->_forcedAttributesCodes)) {
+ if (!isset(self::$commonAttributesCache[$attributeId])) {
+ $defaultValue = $attribute->getDefaultValue();
+ $cachedAttribute = [
+ 'id' => $attributeId,
+ 'code' => $attributeCode,
+ 'is_global' => $attribute->getIsGlobal(),
+ 'is_required' => $attribute->getIsRequired(),
+ 'is_unique' => $attribute->getIsUnique(),
+ 'frontend_label' => $attribute->getFrontendLabel(),
+ 'is_static' => $attribute->isStatic(),
+ 'apply_to' => $attribute->getApplyTo(),
+ 'type' => \Magento\ImportExport\Model\Import::getAttributeType($attribute),
+ 'default_value' => (is_string($defaultValue) && strlen($defaultValue)) ?
+ $attribute->getDefaultValue() : null,
+ 'options' => [],
+ ];
+ $sourceModel = $attribute->getSourceModel();
+ if (Table::class === $sourceModel) {
+ $cachedAttribute['options_use_table'] = true;
+ } else {
+ $cachedAttribute['options'] = $this->_entityModel->getAttributeOptions(
+ $attribute,
+ $this->_indexValueAttributes
+ );
+ }
+ self::$commonAttributesCache[$attributeId] = $cachedAttribute;
self::$attributeCodeToId[$attributeCode] = $attributeId;
- $this->_addAttributeParams(
- $attributeSetName,
- self::$commonAttributesCache[$attributeId],
- $attribute
- );
- } else {
- self::$invAttributesCache[] = $attributeId;
}
+ } else {
+ self::$invAttributesCache[] = $attributeId;
}
+ return $cachedAttribute;
}
/**
@@ -524,7 +626,6 @@ public function isSuitable()
public function prepareAttributesWithDefaultValueForSave(array $rowData, $withDefaultValue = true)
{
$resultAttrs = [];
-
foreach ($this->_getProductAttributes($rowData) as $attrCode => $attrParams) {
if ($attrParams['is_static']) {
continue;
@@ -548,7 +649,6 @@ public function prepareAttributesWithDefaultValueForSave(array $rowData, $withDe
$resultAttrs[$attrCode] = $attrParams['default_value'];
}
}
-
return $resultAttrs;
}
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Proxy/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Proxy/Product.php
index cd7ab79e7b490..f0a2a5f8094fe 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Proxy/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Proxy/Product.php
@@ -6,8 +6,6 @@
/**
* Import proxy product model
- *
- * @author Magento Core Team
*/
namespace Magento\CatalogImportExport\Model\Import\Proxy;
@@ -17,6 +15,7 @@ class Product extends \Magento\Catalog\Model\Product
* DO NOT Initialize resources.
*
* @return void
+ * @phpcs:disable Magento2.CodeAnalysis.EmptyBlock.DetectedFunction
*/
protected function _construct()
{
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Proxy/Product/ResourceModel.php b/app/code/Magento/CatalogImportExport/Model/Import/Proxy/Product/ResourceModel.php
index c7dc0d0c183ac..86003fdec9b7b 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Proxy/Product/ResourceModel.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Proxy/Product/ResourceModel.php
@@ -6,8 +6,6 @@
/**
* Import proxy product resource
- *
- * @author Magento Core Team
*/
namespace Magento\CatalogImportExport\Model\Import\Proxy\Product;
diff --git a/app/code/Magento/CatalogImportExport/Model/StockItemProcessor.php b/app/code/Magento/CatalogImportExport/Model/StockItemProcessor.php
new file mode 100644
index 0000000000000..c1923b0d52ae8
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Model/StockItemProcessor.php
@@ -0,0 +1,33 @@
+stockItemImporter = $stockItemImporter;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function process(array $stockData, array $importedData): void
+ {
+ $this->stockItemImporter->import($stockData);
+ }
+}
diff --git a/app/code/Magento/CatalogImportExport/Model/StockItemProcessorInterface.php b/app/code/Magento/CatalogImportExport/Model/StockItemProcessorInterface.php
new file mode 100644
index 0000000000000..870bddde0116b
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Model/StockItemProcessorInterface.php
@@ -0,0 +1,27 @@
+createMock(Data::class);
- $this->linkProcessor->saveLinks($importEntity, $dataSourceModel, '_related_');
+ $this->linkProcessor->saveLinks($importEntity, $dataSourceModel, '_related_', []);
}
/**
diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/AbstractTypeTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/AbstractTypeTest.php
index 9453075f99e7c..0cb99efe512ff 100644
--- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/AbstractTypeTest.php
+++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/AbstractTypeTest.php
@@ -6,12 +6,12 @@
*/
namespace Magento\CatalogImportExport\Test\Unit\Model\Import\Product\Type;
+use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory as AttributeCollectionFactory;
use Magento\CatalogImportExport\Model\Import\Product;
use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface;
use Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType as AbstractType;
use Magento\CatalogImportExport\Model\Import\Product\Type\Simple;
-use Magento\Eav\Model\Entity\Attribute;
use Magento\Eav\Model\Entity\Attribute\Set;
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection;
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory as AttributeSetCollectionFactory;
@@ -87,7 +87,7 @@ protected function setUp(): void
]
);
$attribute = $this->getMockBuilder(Attribute::class)
- ->addMethods(['getIsVisible', 'getIsGlobal', 'getFrontendLabel', 'getApplyTo'])
+ ->addMethods(['getFrontendLabel'])
->onlyMethods(
[
'getAttributeCode',
@@ -97,7 +97,10 @@ protected function setUp(): void
'isStatic',
'getDefaultValue',
'usesSource',
- 'getFrontendInput'
+ 'getFrontendInput',
+ 'getIsVisible',
+ 'getApplyTo',
+ 'getIsGlobal',
]
)
->disableOriginalConstructor()
@@ -118,21 +121,23 @@ protected function setUp(): void
->willReturn('default_value');
$attribute->method('usesSource')
->willReturn(true);
-
$entityAttributes = [
[
- 'attribute_id' => 'attribute_id',
+ 'attribute_id' => '1',
'attribute_set_name' => 'attributeSetName',
],
[
- 'attribute_id' => 'boolean_attribute',
+ 'attribute_id' => '2',
'attribute_set_name' => 'attributeSetName'
- ]
+ ],
+ [
+ 'attribute_id' => '3',
+ 'attribute_set_name' => 'attributeSetName'
+ ],
];
$attribute1 = clone $attribute;
$attribute2 = clone $attribute;
$attribute3 = clone $attribute;
-
$attribute1->method('getId')
->willReturn('1');
$attribute1->method('getAttributeCode')
@@ -141,7 +146,6 @@ protected function setUp(): void
->willReturn('multiselect');
$attribute1->method('isStatic')
->willReturn(true);
-
$attribute2->method('getId')
->willReturn('2');
$attribute2->method('getAttributeCode')
@@ -150,7 +154,6 @@ protected function setUp(): void
->willReturn('boolean');
$attribute2->method('isStatic')
->willReturn(false);
-
$attribute3->method('getId')
->willReturn('3');
$attribute3->method('getAttributeCode')
@@ -159,7 +162,6 @@ protected function setUp(): void
->willReturn('text');
$attribute3->method('isStatic')
->willReturn(false);
-
$this->entityModel->method('getEntityTypeId')
->willReturn(3);
$this->entityModel->method('getAttributeOptions')
@@ -179,32 +181,34 @@ protected function setUp(): void
->willReturn(1);
$attributeSet->method('getAttributeSetName')
->willReturn('attribute_set_name');
-
$attrCollection->method('addFieldToFilter')
- ->with(
- ['main_table.attribute_id', 'main_table.attribute_code'],
+ ->withConsecutive(
[
+ ['main_table.attribute_id'],
[
- 'in' => [
- 'attribute_id',
- 'boolean_attribute',
+ [
+ 'in' => ['1', '2', '3'],
],
- ],
+ ]
+ ],
+ [
+ ['main_table.attribute_code'],
[
- 'in' => [
- 'related_tgtr_position_behavior',
- 'related_tgtr_position_limit',
- 'upsell_tgtr_position_behavior',
- 'upsell_tgtr_position_limit',
- 'thumbnail_label',
- 'small_image_label',
- 'image_label',
+ [
+ 'in' => [
+ 'related_tgtr_position_behavior',
+ 'related_tgtr_position_limit',
+ 'upsell_tgtr_position_behavior',
+ 'upsell_tgtr_position_limit',
+ 'thumbnail_label',
+ 'small_image_label',
+ 'image_label',
+ ],
],
- ],
- ]
+ ]
+ ],
)
- ->willReturn([$attribute1, $attribute2, $attribute3]);
-
+ ->willReturnOnConsecutiveCalls([$attribute1, $attribute2, $attribute3], []);
$this->connection = $this->getMockBuilder(Mysql::class)
->addMethods(['joinLeft'])
->onlyMethods(['select', 'fetchAll', 'fetchPairs', 'insertOnDuplicate', 'delete', 'quoteInto'])
@@ -240,7 +244,6 @@ protected function setUp(): void
->willReturn('');
$this->connection->method('fetchAll')
->willReturn($entityAttributes);
-
$this->resource = $this->createPartialMock(
ResourceConnection::class,
[
@@ -252,7 +255,6 @@ protected function setUp(): void
->willReturn($this->connection);
$this->resource->method('getTableName')
->willReturn('tableName');
-
$this->objectManagerHelper = new ObjectManagerHelper($this);
$this->simpleType = $this->objectManagerHelper->getObject(
Simple::class,
@@ -263,12 +265,24 @@ protected function setUp(): void
'resource' => $this->resource,
]
);
-
$this->abstractType = $this->getMockBuilder(AbstractType::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
}
+ /**
+ * Because AbstractType has static member variables, we must clean them in between tests.
+ * Luckily they are publicly accessible.
+ *
+ * @return void
+ */
+ protected function tearDown(): void
+ {
+ AbstractType::$commonAttributesCache = [];
+ AbstractType::$invAttributesCache = [];
+ AbstractType::$attributeCodeToId = [];
+ }
+
/**
* @dataProvider addAttributeOptionDataProvider
*/
@@ -428,7 +442,6 @@ public function testPrepareAttributesWithDefaultValueForSave()
'_attribute_set' => 'attributeSetName',
'boolean_attribute' => 'Yes',
];
-
$expected = [
'boolean_attribute' => 1,
'text_attribute' => 'default_value'
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 83d19f81d7d2d..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;
@@ -40,7 +41,7 @@ class OptionTest extends AbstractImportTestCase
/**
* Path to csv file to import
*/
- const PATH_TO_CSV_FILE = '/_files/product_with_custom_options.csv';
+ public const PATH_TO_CSV_FILE = '/_files/product_with_custom_options.csv';
/**
* Parameters for Test stores.
@@ -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(
@@ -409,12 +402,12 @@ protected function _getSourceDataMocks(bool $addExpectations, bool $doubleOption
{
$csvData = $this->_loadCsvFile();
- $dataSourceModel = $this->getMockBuilder(\stdClass::class)->addMethods(['getNextBunch'])
+ $dataSourceModel = $this->getMockBuilder(\stdClass::class)->addMethods(['getNextUniqueBunch'])
->disableOriginalConstructor()
->getMock();
if ($addExpectations) {
$dataSourceModel
- ->method('getNextBunch')
+ ->method('getNextUniqueBunch')
->willReturnOnConsecutiveCalls($csvData['data'], null);
}
@@ -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);
@@ -1039,11 +1038,11 @@ public function validateAmbiguousDataDataProvider(): array
*/
public function testParseRequiredData(): void
{
- $modelData = $this->getMockBuilder(\stdClass::class)->addMethods(['getNextBunch'])
+ $modelData = $this->getMockBuilder(\stdClass::class)->addMethods(['getNextUniqueBunch'])
->disableOriginalConstructor()
->getMock();
$modelData
- ->method('getNextBunch')
+ ->method('getNextUniqueBunch')
->willReturnOnConsecutiveCalls(
[
[
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/CatalogImportExport/etc/di.xml b/app/code/Magento/CatalogImportExport/etc/di.xml
index c35bcbd849511..43fdda6227ac7 100644
--- a/app/code/Magento/CatalogImportExport/etc/di.xml
+++ b/app/code/Magento/CatalogImportExport/etc/di.xml
@@ -8,6 +8,7 @@
+
diff --git a/app/code/Magento/CatalogInventory/Block/Plugin/ProductView.php b/app/code/Magento/CatalogInventory/Block/Plugin/ProductView.php
index 8355a96e3d0e8..c8245a53c147c 100644
--- a/app/code/Magento/CatalogInventory/Block/Plugin/ProductView.php
+++ b/app/code/Magento/CatalogInventory/Block/Plugin/ProductView.php
@@ -24,6 +24,8 @@ public function __construct(
}
/**
+ * Adds quantities validator.
+ *
* @param \Magento\Catalog\Block\Product\View $block
* @param array $validators
* @return array
@@ -38,7 +40,6 @@ public function afterGetQuantityValidators(
);
$params = [];
- $params['minAllowed'] = (float)$stockItem->getMinSaleQty();
if ($stockItem->getMaxSaleQty()) {
$params['maxAllowed'] = (float)$stockItem->getMaxSaleQty();
}
diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock.php
index 4b35bfdc7ab1e..c91d79c726416 100644
--- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock.php
+++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock.php
@@ -54,7 +54,6 @@ public function __construct(
public function execute($ids)
{
$this->_productStockIndexerRows->execute($ids);
- $this->getCacheContext()->registerEntities(\Magento\Catalog\Model\Product::CACHE_TAG, $ids);
}
/**
@@ -102,6 +101,7 @@ public function executeRow($id)
*
* @return \Magento\Framework\Indexer\CacheContext
* @deprecated 100.0.7
+ * @see we don't add dependecies this way anymore
*/
protected function getCacheContext()
{
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/Fixture/SourceItem.php b/app/code/Magento/CatalogInventory/Test/Fixture/SourceItem.php
deleted file mode 100644
index 68a887c580860..0000000000000
--- a/app/code/Magento/CatalogInventory/Test/Fixture/SourceItem.php
+++ /dev/null
@@ -1,90 +0,0 @@
- 'SKU-%uniqid%',
- 'source_code' => 'Source%uniqid%',
- 'quantity' => 5,
- 'status' => SourceItemInterface::STATUS_IN_STOCK,
- ];
-
- /**
- * @var ServiceFactory
- */
- private ServiceFactory $serviceFactory;
-
- /**
- * @var ProcessorInterface
- */
- private ProcessorInterface $dataProcessor;
-
- /**
- * @var DataMerger
- */
- private DataMerger $dataMerger;
-
- /**
- * @param ServiceFactory $serviceFactory
- * @param ProcessorInterface $dataProcessor
- * @param DataMerger $dataMerger
- */
- public function __construct(
- ServiceFactory $serviceFactory,
- ProcessorInterface $dataProcessor,
- DataMerger $dataMerger
- ) {
- $this->serviceFactory = $serviceFactory;
- $this->dataProcessor = $dataProcessor;
- $this->dataMerger = $dataMerger;
- }
-
- /**
- * @param array $data
- * @return DataObject|null
- */
- public function apply(array $data = []): ?DataObject
- {
- $service = $this->serviceFactory->create(SourceItemsSaveInterface::class, 'execute');
-
- return $service->execute(['sourceItems' => [$this->prepareData($data)]]);
- }
-
- /**
- * @inheritdoc
- */
- public function revert(DataObject $data): void
- {
- $service = $this->serviceFactory->create(SourceItemsDeleteInterface::class, 'execute');
- $service->execute(['sourceItems' => [$this->prepareData($data->getData())]]);
- }
-
- /**
- * Prepare product data
- *
- * @param array $data
- * @return array
- */
- private function prepareData(array $data): array
- {
- $data = $this->dataMerger->merge(self::DEFAULT_DATA, $data, false);
-
- return $this->dataProcessor->process($this, $data);
- }
-}
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml
index eb49519c78de7..ba513a88c3bba 100644
--- a/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml
@@ -53,4 +53,12 @@
cataloginventory/item_options/manage_stock
0
+
+ cataloginventory/options/enable_inventory_check
+ 1
+
+
+ cataloginventory/options/enable_inventory_check
+ 0
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml
index fdf4662a6e080..6f388c3e6c6d1 100644
--- a/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml
@@ -13,7 +13,9 @@
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Section/InventorySection.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Section/InventorySection.xml
index 929f43467b947..12f6e5e8c1ade 100644
--- a/app/code/Magento/CatalogInventory/Test/Mftf/Section/InventorySection.xml
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Section/InventorySection.xml
@@ -11,5 +11,8 @@
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminAllowMultipleBoxesForShippingAttributeTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminAllowMultipleBoxesForShippingAttributeTest.xml
new file mode 100644
index 0000000000000..6133d952158be
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminAllowMultipleBoxesForShippingAttributeTest.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/DisabledInventoryCheckOnePageCheckoutTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/DisabledInventoryCheckOnePageCheckoutTest.xml
new file mode 100644
index 0000000000000..7d00422aa1f6e
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/DisabledInventoryCheckOnePageCheckoutTest.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/StorefrontSelectionOfOutOfStockChildProductsOfConfigurableProductDisabledTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/StorefrontSelectionOfOutOfStockChildProductsOfConfigurableProductDisabledTest.xml
new file mode 100644
index 0000000000000..2d239c0f33a63
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/StorefrontSelectionOfOutOfStockChildProductsOfConfigurableProductDisabledTest.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Block/Plugin/ProductViewTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Block/Plugin/ProductViewTest.php
index 5732247ae4116..5322f61e31360 100644
--- a/app/code/Magento/CatalogInventory/Test/Unit/Block/Plugin/ProductViewTest.php
+++ b/app/code/Magento/CatalogInventory/Test/Unit/Block/Plugin/ProductViewTest.php
@@ -59,7 +59,6 @@ public function testAfterGetQuantityValidators()
{
$result = [
'validate-item-quantity' => [
- 'minAllowed' => 0.5,
'maxAllowed' => 5.0,
'qtyIncrements' => 3.0
]
@@ -85,7 +84,6 @@ public function testAfterGetQuantityValidators()
->method('getStockItem')
->with('productId', 'websiteId')
->willReturn($this->stockItem);
- $this->stockItem->expects($this->once())->method('getMinSaleQty')->willReturn(0.5);
$this->stockItem->expects($this->any())->method('getMaxSaleQty')->willReturn(5);
$this->stockItem->expects($this->any())->method('getQtyIncrements')->willReturn(3);
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 @@
+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/CatalogInventory/etc/mview.xml b/app/code/Magento/CatalogInventory/etc/mview.xml
index 9733fa32583f1..3fee2c2e2fc22 100644
--- a/app/code/Magento/CatalogInventory/etc/mview.xml
+++ b/app/code/Magento/CatalogInventory/etc/mview.xml
@@ -14,9 +14,4 @@
-
-
-
-
-
diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
index 6c33233704148..1408ecbe4326a 100644
--- a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
+++ b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
@@ -355,7 +355,15 @@
-
+
+
+ -
+
-
+
- ${$.provider}:data.product.stock_data.is_qty_decimal
+
+
+
+
-
- 0
@@ -377,6 +385,7 @@
+
-
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/Controller/Adminhtml/Promo/Catalog/NewConditionHtml.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/NewConditionHtml.php
index 2aaaddf1e2b34..5e1f401219b90 100644
--- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/NewConditionHtml.php
+++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/NewConditionHtml.php
@@ -6,10 +6,12 @@
*/
namespace Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog;
+use Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog as CatalogAction;
+use Magento\CatalogRule\Model\Rule;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Rule\Model\Condition\AbstractCondition;
-use Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog as CatalogAction;
+use Magento\Rule\Model\Condition\ConditionInterface;
class NewConditionHtml extends CatalogAction implements HttpPostActionInterface, HttpGetActionInterface
{
@@ -20,28 +22,36 @@ class NewConditionHtml extends CatalogAction implements HttpPostActionInterface,
*/
public function execute()
{
- $id = $this->getRequest()->getParam('id');
- $formName = $this->getRequest()->getParam('form_namespace');
- $typeArr = explode('|', str_replace('-', '/', $this->getRequest()->getParam('type', '')));
- $type = $typeArr[0];
+ $objectId = $this->getRequest()->getParam('id');
+ $formNamespace = $this->getRequest()->getParam('form_namespace');
+ $types = explode(
+ '|',
+ str_replace('-', '/', $this->getRequest()->getParam('type', ''))
+ );
+ $objectType = $types[0];
+ $reponseBody = '';
- $model = $this->_objectManager->create($type)
- ->setId($id)
- ->setType($type)
- ->setRule($this->_objectManager->create(\Magento\CatalogRule\Model\Rule::class))
+ if (class_exists($objectType) && !in_array(ConditionInterface::class, class_implements($objectType))) {
+ $this->getResponse()->setBody($reponseBody);
+ return;
+ }
+
+ $conditionModel = $this->_objectManager->create($objectType)
+ ->setId($objectId)
+ ->setType($objectType)
+ ->setRule($this->_objectManager->create(Rule::class))
->setPrefix('conditions');
- if (!empty($typeArr[1])) {
- $model->setAttribute($typeArr[1]);
+ if (!empty($types[1])) {
+ $conditionModel->setAttribute($types[1]);
}
- if ($model instanceof AbstractCondition) {
- $model->setJsFormObject($this->getRequest()->getParam('form'));
- $model->setFormName($formName);
- $html = $model->asHtmlRecursive();
- } else {
- $html = '';
+ if ($conditionModel instanceof AbstractCondition) {
+ $conditionModel->setJsFormObject($this->getRequest()->getParam('form'));
+ $conditionModel->setFormName($formNamespace);
+ $reponseBody = $conditionModel->asHtmlRecursive();
}
- $this->getResponse()->setBody($html);
+
+ $this->getResponse()->setBody($reponseBody);
}
}
diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php
index 6d499b93e411f..55eea085985c5 100644
--- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php
+++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php
@@ -6,12 +6,13 @@
*/
namespace Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog;
-use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Backend\App\Action\Context;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
+use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Filter\FilterInput;
use Magento\Framework\Registry;
use Magento\Framework\Stdlib\DateTime\Filter\Date;
-use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
/**
@@ -80,7 +81,7 @@ public function execute()
if ($this->getRequest()->getParam('to_date')) {
$filterValues['to_date'] = $this->_dateFilter;
}
- $inputFilter = new \Zend_Filter_Input(
+ $inputFilter = new FilterInput(
$filterValues,
[],
$data
@@ -146,8 +147,9 @@ public function execute()
__('Something went wrong while saving the rule data. Please review the error log.')
);
$this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e);
- $this->_objectManager->get(\Magento\Backend\Model\Session::class)->setPageData($data);
- $this->dataPersistor->set('catalog_rule', $data);
+ $ruleData = $data ?? $this->getRequest()->getPostValue();
+ $this->_objectManager->get(\Magento\Backend\Model\Session::class)->setPageData($ruleData);
+ $this->dataPersistor->set('catalog_rule', $ruleData);
$this->_redirect('catalog_rule/*/edit', ['id' => $this->getRequest()->getParam('rule_id')]);
return;
}
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 @@
+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/Test/Fixture/Data/ActionsSerializer.php b/app/code/Magento/CatalogRule/Test/Fixture/Data/ActionsSerializer.php
new file mode 100644
index 0000000000000..73eb8deb2da98
--- /dev/null
+++ b/app/code/Magento/CatalogRule/Test/Fixture/Data/ActionsSerializer.php
@@ -0,0 +1,57 @@
+json = $json;
+ }
+
+ /**
+ * Normalizes and serializes actions data
+ *
+ * @param array $data
+ * @return string
+ */
+ public function serialize(array $data): string
+ {
+ return $this->json->serialize($this->normalize($data));
+ }
+
+ /**
+ * Normalizes actions data
+ *
+ * @param array $data
+ * @return array
+ */
+ private function normalize(array $data) : array
+ {
+ $actions = $data;
+ $actions += [
+ 'type' => Collection::class,
+ 'attribute' => null,
+ 'value' => null,
+ 'operator' => '=',
+ ];
+ return $actions;
+ }
+}
diff --git a/app/code/Magento/CatalogRule/Test/Fixture/Data/ConditionsSerializer.php b/app/code/Magento/CatalogRule/Test/Fixture/Data/ConditionsSerializer.php
new file mode 100644
index 0000000000000..bd7d380c18ee5
--- /dev/null
+++ b/app/code/Magento/CatalogRule/Test/Fixture/Data/ConditionsSerializer.php
@@ -0,0 +1,86 @@
+json = $json;
+ }
+
+ /**
+ * Normalizes and serializes conditions data
+ *
+ * @param array $data
+ * @return string
+ */
+ public function serialize(array $data): string
+ {
+ return $this->json->serialize($this->normalize($data));
+ }
+
+ /**
+ * Normalizes conditions data
+ *
+ * @param array $data
+ * @return array
+ */
+ private function normalize(array $data) : array
+ {
+ $conditions = $data;
+ if (array_is_list($conditions)) {
+ $conditions = [
+ 'conditions' => $conditions,
+ ];
+ }
+ $conditions += [
+ 'type' => Combine::class,
+ 'attribute' => null,
+ 'value' => true,
+ 'operator' => null,
+ 'aggregator' => 'all',
+ 'is_value_processed' => null,
+ 'conditions' => [
+
+ ],
+ ];
+ $subConditions = $conditions['conditions'];
+ $conditions['conditions'] = [];
+
+ foreach ($subConditions as $condition) {
+ if (isset($condition['conditions']) && array_is_list($condition)) {
+ $condition = $this->normalize($condition);
+ } else {
+ $condition += [
+ 'type' => Product::class,
+ 'attribute' => null,
+ 'value' => null,
+ 'operator' => '==',
+ 'is_value_processed' => false,
+ ];
+ }
+
+ $conditions['conditions'][] = $condition;
+ }
+ return $conditions;
+ }
+}
diff --git a/app/code/Magento/CatalogRule/Test/Fixture/Rule.php b/app/code/Magento/CatalogRule/Test/Fixture/Rule.php
new file mode 100644
index 0000000000000..7f2d3fbde25ce
--- /dev/null
+++ b/app/code/Magento/CatalogRule/Test/Fixture/Rule.php
@@ -0,0 +1,164 @@
+ 'catalogrule%uniqid%',
+ 'sort_order' => 0,
+ 'is_active' => 1,
+ 'description' => null,
+ 'website_ids' => [],
+ 'customer_group_ids' => [],
+ 'stop_rules_processing' => true,
+ 'simple_action' => 'by_percent',
+ 'discount_amount' => 0,
+ 'conditions' => [],
+ 'actions' => [],
+ ];
+
+ /**
+ * @var ProcessorInterface
+ */
+ private ProcessorInterface $dataProcessor;
+
+ /**
+ * @var ResourceModel
+ */
+ private ResourceModel $catalogRuleResourceModel;
+
+ /**
+ * @var RuleFactory
+ */
+ private RuleFactory $catalogRuleFactory;
+
+ /**
+ * @var CollectionFactory
+ */
+ private CollectionFactory $customerGroupCollectionFactory;
+
+ /**
+ * @var StoreManagerInterface
+ */
+ private StoreManagerInterface $storeManager;
+
+ /**
+ * @var ConditionsSerializer
+ */
+ private ConditionsSerializer $conditionsSerializer;
+
+ /**
+ * @var ActionsSerializer
+ */
+ private ActionsSerializer $actionsSerializer;
+
+ /**
+ * @param ProcessorInterface $dataProcessor
+ * @param ResourceModel $catalogRuleResourceModel
+ * @param RuleFactory $catalogRuleFactory
+ * @param CollectionFactory $customerGroupCollectionFactory
+ * @param StoreManagerInterface $storeManager
+ * @param ConditionsSerializer $conditionsSerializer
+ * @param ActionsSerializer $actionsSerializer
+ */
+ public function __construct(
+ ProcessorInterface $dataProcessor,
+ ResourceModel $catalogRuleResourceModel,
+ RuleFactory $catalogRuleFactory,
+ CollectionFactory $customerGroupCollectionFactory,
+ StoreManagerInterface $storeManager,
+ ConditionsSerializer $conditionsSerializer,
+ ActionsSerializer $actionsSerializer
+ ) {
+ $this->dataProcessor = $dataProcessor;
+ $this->catalogRuleResourceModel = $catalogRuleResourceModel;
+ $this->catalogRuleFactory = $catalogRuleFactory;
+ $this->customerGroupCollectionFactory = $customerGroupCollectionFactory;
+ $this->storeManager = $storeManager;
+ $this->conditionsSerializer = $conditionsSerializer;
+ $this->actionsSerializer = $actionsSerializer;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply(array $data = []): ?DataObject
+ {
+ /** @var \Magento\CatalogRule\Model\Rule $model */
+ $model = $this->catalogRuleFactory->create();
+ $data = $this->prepareData($data);
+ $model->setData($data);
+ $this->catalogRuleResourceModel->save($model);
+
+ return $model;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @param array $data Parameters. Same format as Rule::DEFAULT_DATA.
+ * - $data['conditions']: can be supplied in following formats:
+ * - [['attribute'=>'..','value'=>'..'],['attribute'=>'..','value'=>'..','operator'=>'..'], [..]]
+ * - ['aggregator'=>'any', 'conditions' => [[..],[..]]]
+ */
+ public function revert(DataObject $data): void
+ {
+ /** @var \Magento\CatalogRule\Model\Rule $model */
+ $model = $this->catalogRuleFactory->create();
+ $this->catalogRuleResourceModel->load($model, $data->getId());
+ if ($model->getId()) {
+ $this->catalogRuleResourceModel->delete($model);
+ }
+ }
+
+ /**
+ * Prepares rule default data
+ *
+ * @return array
+ */
+ private function prepareDefaultData(): array
+ {
+ $data = self::DEFAULT_DATA;
+ $customerGroupCollection = $this->customerGroupCollectionFactory->create();
+ foreach ($customerGroupCollection->getAllIds() as $customerGroupId) {
+ $data['customer_group_ids'][] = $customerGroupId;
+ }
+
+ foreach ($this->storeManager->getWebsites() as $website) {
+ $data['website_ids'][] = $website->getId();
+ }
+
+ return $data;
+ }
+
+ /**
+ * Prepare CatalogRule data
+ *
+ * @param array $data
+ * @return array
+ */
+ private function prepareData(array $data): array
+ {
+ $data = array_merge($this->prepareDefaultData(), $data);
+ $data['conditions_serialized'] = $this->conditionsSerializer->serialize($data['conditions']);
+ $data['actions_serialized'] = $this->actionsSerializer->serialize($data['actions']);
+ unset($data['conditions'], $data['actions']);
+
+ return $this->dataProcessor->process($this, $data);
+ }
+}
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/FillMainProductWithSpecifiedPriceActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/FillMainProductWithSpecifiedPriceActionGroup.xml
new file mode 100644
index 0000000000000..98e8a62fdba94
--- /dev/null
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/FillMainProductWithSpecifiedPriceActionGroup.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ Fills in the provided Product details (Name, SKU, Price, Quantity, Stock Status and Weight Type) on the Admin Products creation/edit page.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForDownloadableProductTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForDownloadableProductTest.xml
new file mode 100644
index 0000000000000..d3a349fb3a19c
--- /dev/null
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForDownloadableProductTest.xml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForFixedBundleProductWithCustomOptionsTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForFixedBundleProductWithCustomOptionsTest.xml
new file mode 100644
index 0000000000000..ee32fa1901f43
--- /dev/null
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForFixedBundleProductWithCustomOptionsTest.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontApplyCatalogRuleToSimpleProductNotCustomOptionsTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontApplyCatalogRuleToSimpleProductNotCustomOptionsTest.xml
new file mode 100644
index 0000000000000..c3078a052116a
--- /dev/null
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontApplyCatalogRuleToSimpleProductNotCustomOptionsTest.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 56.78
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/Block/Plugin/FrontTabPlugin.php b/app/code/Magento/CatalogSearch/Block/Plugin/FrontTabPlugin.php
index 85ad66013cf32..0d72db87b1112 100644
--- a/app/code/Magento/CatalogSearch/Block/Plugin/FrontTabPlugin.php
+++ b/app/code/Magento/CatalogSearch/Block/Plugin/FrontTabPlugin.php
@@ -45,6 +45,7 @@ public function beforeSetForm(ProductAttributeFrontTabBlock $subject, Form $form
[
'name' => 'search_weight',
'label' => __('Search Weight'),
+ 'note' => __('10 is the highest priority/heaviest weighting.'),
'values' => $this->weightSource->getOptions()
],
'is_searchable'
diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php
index 2dfc3b78ea821..36e0a85fa4307 100644
--- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php
+++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php
@@ -6,10 +6,8 @@
namespace Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection;
-use Magento\Framework\Data\Collection;
-use Magento\Framework\Search\Adapter\Mysql\TemporaryStorage;
-use Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory;
use Magento\Framework\Api\Search\SearchResultInterface;
+use Magento\Framework\Data\Collection;
/**
* Resolve specific attributes for search criteria.
@@ -29,11 +27,6 @@ class SearchResultApplier implements SearchResultApplierInterface
*/
private $searchResult;
- /**
- * @var TemporaryStorageFactory
- */
- private $temporaryStorageFactory;
-
/**
* @var array
*/
@@ -42,18 +35,15 @@ class SearchResultApplier implements SearchResultApplierInterface
/**
* @param Collection $collection
* @param SearchResultInterface $searchResult
- * @param TemporaryStorageFactory $temporaryStorageFactory
* @param array $orders
*/
public function __construct(
Collection $collection,
SearchResultInterface $searchResult,
- TemporaryStorageFactory $temporaryStorageFactory,
array $orders
) {
$this->collection = $collection;
$this->searchResult = $searchResult;
- $this->temporaryStorageFactory = $temporaryStorageFactory;
$this->orders = $orders;
}
@@ -62,21 +52,22 @@ public function __construct(
*/
public function apply()
{
- $temporaryStorage = $this->temporaryStorageFactory->create();
- $table = $temporaryStorage->storeApiDocuments($this->searchResult->getItems());
+ if (empty($this->searchResult->getItems())) {
+ $this->collection->getSelect()->where('NULL');
+ return;
+ }
+ $ids = [];
+ foreach ($this->searchResult->getItems() as $item) {
+ $ids[] = (int)$item->getId();
+ }
- $this->collection->getSelect()->joinInner(
- [
- 'search_result' => $table->getName(),
- ],
- 'e.entity_id = search_result.' . TemporaryStorage::FIELD_ENTITY_ID,
- []
- );
+ $orderList = implode(',', $ids);
+ $this->collection->getSelect()->where('e.entity_id IN (?)', $ids);
if (isset($this->orders['relevance'])) {
- $this->collection->getSelect()->order(
- 'search_result.' . TemporaryStorage::FIELD_SCORE . ' ' . $this->orders['relevance']
- );
+ $this->collection->getSelect()
+ ->reset(\Magento\Framework\DB\Select::ORDER)
+ ->order(new \Magento\Framework\DB\Sql\Expression("FIELD(e.entity_id, $orderList)"));
}
}
}
diff --git a/app/code/Magento/CatalogSearch/Setup/Patch/Data/MySQLSearchRemovalNotification.php b/app/code/Magento/CatalogSearch/Setup/Patch/Data/MySQLSearchRemovalNotification.php
index d3bc328257e75..174cfeea4b040 100644
--- a/app/code/Magento/CatalogSearch/Setup/Patch/Data/MySQLSearchRemovalNotification.php
+++ b/app/code/Magento/CatalogSearch/Setup/Patch/Data/MySQLSearchRemovalNotification.php
@@ -48,7 +48,7 @@ public function apply(): DataPatchInterface
if ($this->scopeConfig->getValue('catalog/search/engine') === 'mysql') {
$message = <<notifier->addNotice(__('Disable Notice'), __($message));
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml
index dc6d6b034dabd..7e13be4bf7b65 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml
@@ -18,7 +18,7 @@
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchProductByNameWithThreeLettersTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchProductByNameWithThreeLettersTest.xml
index 36977f476756e..dd6b616ff5385 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchProductByNameWithThreeLettersTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchProductByNameWithThreeLettersTest.xml
@@ -15,7 +15,7 @@
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchTwoProductsWithDifferentWeightTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchTwoProductsWithDifferentWeightTest.xml
index 493e07d8ac5b4..3939d03cf74cd 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchTwoProductsWithDifferentWeightTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchTwoProductsWithDifferentWeightTest.xml
@@ -15,7 +15,7 @@
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchTwoProductsWithSameWeightTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchTwoProductsWithSameWeightTest.xml
index 5c0e4f04dbbb3..af0e1930729c0 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchTwoProductsWithSameWeightTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchTwoProductsWithSameWeightTest.xml
@@ -15,7 +15,7 @@
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontAdvancedSearchByPartialNameTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontAdvancedSearchByPartialNameTest.xml
index 927bc4cb814f1..bee4791a07e2c 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontAdvancedSearchByPartialNameTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontAdvancedSearchByPartialNameTest.xml
@@ -18,7 +18,7 @@
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchNotSearchableTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchNotSearchableTest.xml
index 8bd263df8f06f..5637c9cd0c0df 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchNotSearchableTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchNotSearchableTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchStemmingTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchStemmingTest.xml
index 98b2fe55a4f23..e1a59fef1fddc 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchStemmingTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchStemmingTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchUsingElasticSearchTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchUsingElasticSearchTest.xml
index 894deadad82fa..b724644f54efb 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchUsingElasticSearchTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchUsingElasticSearchTest.xml
@@ -16,7 +16,7 @@
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontQuickSearchSkuWithSpecialCharRankingTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontQuickSearchSkuWithSpecialCharRankingTest.xml
index d5a7110e96e80..7b888b69f30ea 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontQuickSearchSkuWithSpecialCharRankingTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontQuickSearchSkuWithSpecialCharRankingTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Block/Plugin/FrontTabPluginTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Block/Plugin/FrontTabPluginTest.php
index 8348061dfb7e7..08d47a5b67320 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Block/Plugin/FrontTabPluginTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Block/Plugin/FrontTabPluginTest.php
@@ -111,6 +111,7 @@ public function testBeforeSetForm()
[
'name' => 'search_weight',
'label' => __('Search Weight'),
+ 'note' => __('10 is the highest priority/heaviest weighting.'),
'values' => $weightOptions
],
'is_searchable',
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/CatalogSearch/etc/mview.xml b/app/code/Magento/CatalogSearch/etc/mview.xml
index 494b97a816886..28737ce23b1e0 100644
--- a/app/code/Magento/CatalogSearch/etc/mview.xml
+++ b/app/code/Magento/CatalogSearch/etc/mview.xml
@@ -15,7 +15,6 @@
-
diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml
index bec3e57b44798..fffc5205362b1 100644
--- a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml
+++ b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml
@@ -4,11 +4,14 @@
* See COPYING.txt for license details.
*/
+use Magento\Framework\Escaper;
+
/**
* Catalog advanced search form
*
* @var $block \Magento\CatalogSearch\Block\Advanced\Form
* @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer
+ * @var Escaper $escaper
*/
?>
@@ -73,7 +76,11 @@ $catalogSearchHelper = $block->getData('catalogSearchHelper'); ?>
class="input-text"
type="text"
maxlength="= $block->escapeHtmlAttr($maxQueryLength) ?>"
- data-validate="{number:true, 'validate-not-negative-number':true, 'less-than-equals-to':'#= $block->escapeHtmlAttr($_code) ?>_to'}" />
+ data-validate="{
+ number:true,
+ 'validate-not-negative-number':true,
+ 'less-than-equals-to':'#= $block->escapeHtmlAttr($_code) ?>_to'
+ }" />
@@ -86,11 +93,18 @@ $catalogSearchHelper = $block->getData('catalogSearchHelper'); ?>
class="input-text"
type="text"
maxlength="= $block->escapeHtmlAttr($maxQueryLength) ?>"
- data-validate="{number:true, 'validate-not-negative-number':true, 'greater-than-equals-to':'#= $block->escapeHtmlAttr($_code) ?>'}" />
+ data-validate="{
+ number:true,
+ 'validate-not-negative-number':true,
+ 'greater-than-equals-to':'#= $block->escapeHtmlAttr($_code) ?>'
+ }" />
= $block->escapeHtml($block->getCurrency($_attribute)) ?>
+
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php
index 7bf1da2b814e3..e68b38b046afd 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php
@@ -7,6 +7,7 @@
use Magento\Catalog\Api\CategoryRepositoryInterface;
use Magento\Catalog\Api\Data\CategoryInterface;
+use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Category;
use Magento\Catalog\Model\Product;
use Magento\CatalogUrlRewrite\Model\Product\AnchorUrlRewriteGenerator;
@@ -78,6 +79,11 @@ class ProductScopeRewriteGenerator
*/
private $categoryRepository;
+ /**
+ * @var ProductRepositoryInterface
+ */
+ private $productRepository;
+
/**
* @param StoreViewService $storeViewService
* @param StoreManagerInterface $storeManager
@@ -89,6 +95,7 @@ class ProductScopeRewriteGenerator
* @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
* @param CategoryRepositoryInterface|null $categoryRepository
* @param ScopeConfigInterface|null $config
+ * @param ProductRepositoryInterface|null $productRepository
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -101,7 +108,8 @@ public function __construct(
AnchorUrlRewriteGenerator $anchorUrlRewriteGenerator,
MergeDataProviderFactory $mergeDataProviderFactory = null,
CategoryRepositoryInterface $categoryRepository = null,
- ScopeConfigInterface $config = null
+ ScopeConfigInterface $config = null,
+ ProductRepositoryInterface $productRepository = null
) {
$this->storeViewService = $storeViewService;
$this->storeManager = $storeManager;
@@ -117,6 +125,8 @@ public function __construct(
$this->categoryRepository = $categoryRepository ?:
ObjectManager::getInstance()->get(CategoryRepositoryInterface::class);
$this->config = $config ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class);
+ $this->productRepository = $productRepository ?:
+ ObjectManager::getInstance()->get(ProductRepositoryInterface::class);
}
/**
@@ -144,15 +154,21 @@ public function generateForGlobalScope($productCategories, Product $product, $ro
$mergeDataProvider = clone $this->mergeDataProviderPrototype;
foreach ($product->getStoreIds() as $id) {
- if (!$this->isGlobalScope($id) &&
- !$this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore(
+ if (!$this->isGlobalScope($id)) {
+ if (!$this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore(
$id,
$productId,
Product::ENTITY
)) {
- $mergeDataProvider->merge(
- $this->generateForSpecificStoreView($id, $productCategories, $product, $rootCategoryId)
- );
+ $mergeDataProvider->merge(
+ $this->generateForSpecificStoreView($id, $productCategories, $product, $rootCategoryId)
+ );
+ } else {
+ $scopedProduct = $this->productRepository->getById($productId, false, $id);
+ $mergeDataProvider->merge(
+ $this->generateForSpecificStoreView($id, $productCategories, $scopedProduct, $rootCategoryId)
+ );
+ }
}
}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/GetDefaultUrlKey.php b/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/GetDefaultUrlKey.php
index d5e603d9df59b..9b1f14484a466 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/GetDefaultUrlKey.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/GetDefaultUrlKey.php
@@ -13,6 +13,7 @@
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Select;
use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Store\Model\Store;
/**
@@ -53,10 +54,11 @@ public function __construct(
/**
* Retrieve 'url_key' value for default store.
*
- * @param int $categoryId
+ * @param int $id
* @return string|null
+ * @throws LocalizedException
*/
- public function execute(int $categoryId): ?string
+ public function execute(int $id): ?string
{
$metadata = $this->metadataPool->getMetadata(CategoryInterface::class);
$entityTypeId = $this->eavConfig->getEntityType(Category::ENTITY)->getId();
@@ -64,7 +66,7 @@ public function execute(int $categoryId): ?string
$whereConditions = [
'e.entity_type_id = ' . $entityTypeId,
"e.attribute_code = 'url_key'",
- 'c.' . $linkField . ' = ' . $categoryId,
+ 'c.' . $linkField . ' = ' . $id,
'c.store_id = ' . Store::DEFAULT_STORE_ID,
];
$connection = $this->resourceConnection->getConnection();
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
index faec3bb406956..f439c4afe3786 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
@@ -12,6 +12,7 @@
use Magento\Catalog\Model\ProductFactory;
use Magento\Catalog\Model\ResourceModel\Category\Collection as CategoryCollection;
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory;
+use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\CatalogImportExport\Model\Import\Product as ImportProduct;
use Magento\CatalogUrlRewrite\Model\ObjectRegistry;
use Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory;
@@ -35,7 +36,6 @@
use Magento\UrlRewrite\Model\UrlPersistInterface;
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory;
-use RuntimeException;
/**
* @SuppressWarnings(PHPMD.TooManyFields)
@@ -50,21 +50,29 @@ class AfterImportDataObserver implements ObserverInterface
/**
* @var StoreViewService
+ * @deprecated No longer used.
+ * @see nothing
*/
protected $storeViewService;
/**
* @var Product
+ * @deprecated No longer used.
+ * @see nothing
*/
protected $product;
/**
* @var array
+ * @deprecated No longer used.
+ * @see nothing
*/
protected $productsWithStores;
/**
* @var array
+ * @deprecated No longer used.
+ * @see nothing
*/
protected $products = [];
@@ -147,7 +155,7 @@ class AfterImportDataObserver implements ObserverInterface
'url_path',
'name',
'visibility',
- 'save_rewrites_history'
+ 'save_rewrites_history',
];
/**
@@ -174,6 +182,11 @@ class AfterImportDataObserver implements ObserverInterface
*/
private $scopeConfig;
+ /**
+ * @var CollectionFactory
+ */
+ private $productCollectionFactory;
+
/**
* @param ProductFactory $catalogProductFactory
* @param ObjectRegistryFactory $objectRegistryFactory
@@ -186,8 +199,9 @@ class AfterImportDataObserver implements ObserverInterface
* @param MergeDataProviderFactory|null $mergeDataProviderFactory
* @param CategoryCollectionFactory|null $categoryCollectionFactory
* @param ScopeConfigInterface|null $scopeConfig
- * @throws RuntimeException
+ * @param CollectionFactory|null $collectionFactory
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct(
ProductFactory $catalogProductFactory,
@@ -200,17 +214,16 @@ public function __construct(
UrlFinderInterface $urlFinder,
MergeDataProviderFactory $mergeDataProviderFactory = null,
CategoryCollectionFactory $categoryCollectionFactory = null,
- ScopeConfigInterface $scopeConfig = null
+ ScopeConfigInterface $scopeConfig = null,
+ CollectionFactory $collectionFactory = null
) {
$this->urlPersist = $urlPersist;
$this->catalogProductFactory = $catalogProductFactory;
$this->objectRegistryFactory = $objectRegistryFactory;
$this->productUrlPathGenerator = $productUrlPathGenerator;
- $this->storeViewService = $storeViewService;
$this->storeManager = $storeManager;
$this->urlRewriteFactory = $urlRewriteFactory;
$this->urlFinder = $urlFinder;
-
$mergeDataProviderFactory = $mergeDataProviderFactory ?: ObjectManager::getInstance()->get(
MergeDataProviderFactory::class
);
@@ -219,6 +232,8 @@ public function __construct(
ObjectManager::getInstance()->get(CategoryCollectionFactory::class);
$this->scopeConfig = $scopeConfig ?:
ObjectManager::getInstance()->get(ScopeConfigInterface::class);
+ $this->productCollectionFactory = $collectionFactory ?:
+ ObjectManager::getInstance()->get(CollectionFactory::class);
}
/**
@@ -232,26 +247,55 @@ public function __construct(
public function execute(Observer $observer)
{
$this->import = $observer->getEvent()->getAdapter();
- if ($products = $observer->getEvent()->getBunch()) {
- foreach ($products as $product) {
- $this->_populateForUrlGeneration($product);
- }
- $productUrls = $this->generateUrls();
- if ($productUrls) {
- $this->urlPersist->replace($productUrls);
+ $bunch = $observer->getEvent()->getBunch();
+ if (!$bunch) {
+ return;
+ }
+ $products = $this->populateForUrlsGeneration($bunch);
+ $productUrls = $this->generateUrls($products);
+ if ($productUrls) {
+ $this->urlPersist->replace($productUrls);
+ }
+ }
+
+ /**
+ * Create product models from imported data and get url_key from existing products when not in import data.
+ *
+ * @param array[] $bunch
+ * @return Product[]
+ * @throws LocalizedException
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ */
+ private function populateForUrlsGeneration(array $bunch) : array
+ {
+ $products = [];
+ $productIdsMissingUrlKeyByStore = [];
+ foreach ($bunch as $product) {
+ $this->populateForUrlGeneration($product, $products);
+ }
+ foreach ($products as $productsByStore) {
+ foreach ($productsByStore as $storeId => $product) {
+ if (null === $product->getData('url_key')) {
+ $productIdsMissingUrlKeyByStore[$storeId][] = $product->getId();
+ }
}
}
+ foreach ($productIdsMissingUrlKeyByStore as $storeId => $productIds) {
+ $this->getUrlKeyAndNameForProductsByIds($productIds, $storeId, $products);
+ }
+ return $products;
}
/**
* Create product model from imported data for URL rewrite purposes.
*
* @param array $rowData
- * @return AfterImportDataObserver|null
+ * @param Product[] $products
+ * @return void
* @throws LocalizedException
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
- private function _populateForUrlGeneration($rowData)
+ private function populateForUrlGeneration(array $rowData, array &$products)
{
$newSku = $this->import->getNewSku($rowData[ImportProduct::COL_SKU]);
$oldSku = $this->import->getOldSku();
@@ -259,16 +303,13 @@ private function _populateForUrlGeneration($rowData)
return null;
}
$rowData['entity_id'] = $newSku['entity_id'];
-
$product = $this->catalogProductFactory->create();
$product->setId($rowData['entity_id']);
-
foreach ($this->vitalForGenerationFields as $field) {
if (isset($rowData[$field])) {
$product->setData($field, $rowData[$field]);
}
}
-
$this->categoryCache[$rowData['entity_id']] = $this->import->getProductCategories($rowData['sku']);
$this->websiteCache[$rowData['entity_id']] = $this->import->getProductWebsites($rowData['sku']);
foreach ($this->websiteCache[$rowData['entity_id']] as $websiteId) {
@@ -276,16 +317,13 @@ private function _populateForUrlGeneration($rowData)
$this->websitesToStoreIds[$websiteId] = $this->storeManager->getWebsite($websiteId)->getStoreIds();
}
}
-
$this->setStoreToProduct($product, $rowData);
-
if ($this->isGlobalScope($product->getStoreId())) {
- $this->populateGlobalProduct($product);
+ $this->populateGlobalProduct($product, $products);
} else {
$this->storesCache[$product->getStoreId()] = true;
- $this->addProductToImport($product, $product->getStoreId());
+ $this->addProductToImport($product, $product->getStoreId(), $products);
}
- return $this;
}
/**
@@ -320,7 +358,7 @@ private function isNeedToPopulateForUrlGeneration($rowData, $newSku, $oldSku): b
* @param array $rowData
* @return void
*/
- private function setStoreToProduct(Product $product, array $rowData)
+ private function setStoreToProduct(Product $product, array $rowData): void
{
if (!empty($rowData[ImportProduct::COL_STORE])
&& ($storeId = $this->import->getStoreIdByCode($rowData[ImportProduct::COL_STORE]))
@@ -336,58 +374,52 @@ private function setStoreToProduct(Product $product, array $rowData)
*
* @param Product $product
* @param string $storeId
- * @return $this
+ * @param Product[] $products
+ * @return void
*/
- private function addProductToImport($product, $storeId)
+ private function addProductToImport(Product $product, string $storeId, array &$products) : void
{
if ($product->getVisibility() == (string)Visibility::getOptionArray()[Visibility::VISIBILITY_NOT_VISIBLE]) {
- return $this;
- }
- if (!isset($this->products[$product->getId()])) {
- $this->products[$product->getId()] = [];
+ return;
}
- $this->products[$product->getId()][$storeId] = $product;
- return $this;
+ $products[$product->getId()][$storeId] = $product;
}
/**
* Populate global product
*
* @param Product $product
- * @return $this
+ * @param Product[] $products
+ * @return void
*/
- private function populateGlobalProduct($product)
+ private function populateGlobalProduct($product, array &$products) : void
{
foreach ($this->import->getProductWebsites($product->getSku()) as $websiteId) {
foreach ($this->websitesToStoreIds[$websiteId] as $storeId) {
$this->storesCache[$storeId] = true;
if (!$this->isGlobalScope($storeId)) {
- $this->addProductToImport($product, $storeId);
+ $this->addProductToImport($product, $storeId, $products);
}
}
}
- return $this;
}
/**
* Generate product url rewrites
*
+ * @param Product[] $products
* @return UrlRewrite[]
* @throws LocalizedException
*/
- private function generateUrls()
+ private function generateUrls(array $products)
{
$mergeDataProvider = clone $this->mergeDataProviderPrototype;
- $mergeDataProvider->merge($this->canonicalUrlRewriteGenerate());
+ $mergeDataProvider->merge($this->canonicalUrlRewriteGenerate($products));
if ($this->isCategoryRewritesEnabled()) {
- $mergeDataProvider->merge($this->categoriesUrlRewriteGenerate());
+ $mergeDataProvider->merge($this->categoriesUrlRewriteGenerate($products));
}
- $mergeDataProvider->merge($this->currentUrlRewritesRegenerate());
+ $mergeDataProvider->merge($this->currentUrlRewritesRegenerate($products));
$this->productCategories = null;
-
- unset($this->products);
- $this->products = [];
-
return $mergeDataProvider->getData();
}
@@ -405,12 +437,13 @@ private function isGlobalScope($storeId)
/**
* Generate list based on store view
*
+ * @param Product[] $products
* @return UrlRewrite[]
*/
- private function canonicalUrlRewriteGenerate()
+ private function canonicalUrlRewriteGenerate(array $products)
{
$urls = [];
- foreach ($this->products as $productId => $productsByStores) {
+ foreach ($products as $productId => $productsByStores) {
foreach ($productsByStores as $storeId => $product) {
if ($this->productUrlPathGenerator->getUrlPath($product)) {
$urls[] = $this->urlRewriteFactory->create()
@@ -422,20 +455,20 @@ private function canonicalUrlRewriteGenerate()
}
}
}
-
return $urls;
}
/**
* Generate list based on categories.
*
+ * @param Product[] $products
* @return UrlRewrite[]
* @throws LocalizedException
*/
- private function categoriesUrlRewriteGenerate(): array
+ private function categoriesUrlRewriteGenerate(array $products): array
{
$urls = [];
- foreach ($this->products as $productId => $productsByStores) {
+ foreach ($products as $productId => $productsByStores) {
foreach ($productsByStores as $storeId => $product) {
foreach ($this->categoryCache[$productId] as $categoryId) {
$category = $this->getCategoryById($categoryId, $storeId);
@@ -465,18 +498,18 @@ private function categoriesUrlRewriteGenerate(): array
/**
* Generate list based on current rewrites
*
+ * @param Product[] $products
* @return UrlRewrite[]
*/
- private function currentUrlRewritesRegenerate()
+ private function currentUrlRewritesRegenerate(array $products)
{
$currentUrlRewrites = $this->urlFinder->findAllByData(
[
UrlRewrite::STORE_ID => array_keys($this->storesCache),
- UrlRewrite::ENTITY_ID => array_keys($this->products),
+ UrlRewrite::ENTITY_ID => array_keys($products),
UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
]
);
-
$mergeDataProvider = clone $this->mergeDataProviderPrototype;
foreach ($currentUrlRewrites as $currentUrlRewrite) {
$category = $this->retrieveCategoryFromMetadata($currentUrlRewrite);
@@ -484,13 +517,11 @@ private function currentUrlRewritesRegenerate()
continue;
}
$urls = $currentUrlRewrite->getIsAutogenerated()
- ? $this->generateForAutogenerated($currentUrlRewrite, $category)
- : $this->generateForCustom($currentUrlRewrite, $category);
+ ? $this->generateForAutogenerated($currentUrlRewrite, $category, $products)
+ : $this->generateForCustom($currentUrlRewrite, $category, $products);
$mergeDataProvider->merge($urls);
}
$urlRewrites = $mergeDataProvider->getData();
-
- $this->product = null;
$this->productCategories = null;
return $urlRewrites;
}
@@ -499,69 +530,70 @@ private function currentUrlRewritesRegenerate()
* Generate url-rewrite for outogenerated url-rewirte.
*
* @param UrlRewrite $url
- * @param Category $category
+ * @param Category|null $category
+ * @param Product[] $products
* @return array
*/
- private function generateForAutogenerated($url, $category)
+ private function generateForAutogenerated(UrlRewrite $url, ?Category $category, array $products) : array
{
$storeId = $url->getStoreId();
$productId = $url->getEntityId();
- if (isset($this->products[$productId][$storeId])) {
- $product = $this->products[$productId][$storeId];
- if (!$product->getData('save_rewrites_history')) {
- return [];
- }
- $targetPath = $this->productUrlPathGenerator->getUrlPathWithSuffix($product, $storeId, $category);
- if ($url->getRequestPath() === $targetPath) {
- return [];
- }
- return [
- $this->urlRewriteFactory->create()
- ->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE)
- ->setEntityId($productId)
- ->setRequestPath($url->getRequestPath())
- ->setTargetPath($targetPath)
- ->setRedirectType(OptionProvider::PERMANENT)
- ->setStoreId($storeId)
- ->setDescription($url->getDescription())
- ->setIsAutogenerated(0)
- ->setMetadata($url->getMetadata())
- ];
+ if (!isset($products[$productId][$storeId])) {
+ return [];
}
- return [];
+ $product = $products[$productId][$storeId];
+ if (!$product->getData('save_rewrites_history')) {
+ return [];
+ }
+ $targetPath = $this->productUrlPathGenerator->getUrlPathWithSuffix($product, $storeId, $category);
+ if ($url->getRequestPath() === $targetPath) {
+ return [];
+ }
+ return [
+ $this->urlRewriteFactory->create()
+ ->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE)
+ ->setEntityId($productId)
+ ->setRequestPath($url->getRequestPath())
+ ->setTargetPath($targetPath)
+ ->setRedirectType(OptionProvider::PERMANENT)
+ ->setStoreId($storeId)
+ ->setDescription($url->getDescription())
+ ->setIsAutogenerated(0)
+ ->setMetadata($url->getMetadata())
+ ];
}
/**
- * Generate url-rewrite for custom url-rewirte.
+ * Generate url-rewrite for custom url-rewrite.
*
* @param UrlRewrite $url
- * @param Category $category
- * @return array
+ * @param Category|null $category
+ * @param Product[] $products
+ * @return UrlRewrite[]
*/
- private function generateForCustom($url, $category)
+ private function generateForCustom(UrlRewrite $url, ?Category $category, array $products) : array
{
$storeId = $url->getStoreId();
$productId = $url->getEntityId();
- if (isset($this->products[$productId][$storeId])) {
- $product = $this->products[$productId][$storeId];
+ if (isset($products[$productId][$storeId])) {
+ $product = $products[$productId][$storeId];
$targetPath = $url->getRedirectType()
? $this->productUrlPathGenerator->getUrlPathWithSuffix($product, $storeId, $category)
: $url->getTargetPath();
if ($url->getRequestPath() === $targetPath) {
return [];
}
- return [
- $this->urlRewriteFactory->create()
- ->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE)
- ->setEntityId($productId)
- ->setRequestPath($url->getRequestPath())
- ->setTargetPath($targetPath)
- ->setRedirectType($url->getRedirectType())
- ->setStoreId($storeId)
- ->setDescription($url->getDescription())
- ->setIsAutogenerated(0)
- ->setMetadata($url->getMetadata())
- ];
+ $urlRewrite = $this->urlRewriteFactory->create();
+ $urlRewrite->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE);
+ $urlRewrite->setEntityId($productId);
+ $urlRewrite->setRequestPath($url->getRequestPath());
+ $urlRewrite->setTargetPath($targetPath);
+ $urlRewrite->setRedirectType($url->getRedirectType());
+ $urlRewrite->setStoreId($storeId);
+ $urlRewrite->setDescription($url->getDescription());
+ $urlRewrite->setIsAutogenerated(0);
+ $urlRewrite->setMetadata($url->getMetadata());
+ return [$urlRewrite];
}
return [];
}
@@ -572,7 +604,7 @@ private function generateForCustom($url, $category)
* @param UrlRewrite $url
* @return Category|null|bool
*/
- private function retrieveCategoryFromMetadata($url)
+ private function retrieveCategoryFromMetadata(UrlRewrite $url)
{
$metadata = $url->getMetadata();
if (isset($metadata['category_id'])) {
@@ -590,7 +622,7 @@ private function retrieveCategoryFromMetadata($url)
* @return Category|DataObject
* @throws LocalizedException
*/
- private function getCategoryById($categoryId, $storeId)
+ private function getCategoryById(int $categoryId, int $storeId)
{
if (!isset($this->categoriesCache[$categoryId][$storeId])) {
/** @var CategoryCollection $categoryCollection */
@@ -602,7 +634,6 @@ private function getCategoryById($categoryId, $storeId)
->addAttributeToSelect('url_path');
$this->categoriesCache[$categoryId][$storeId] = $categoryCollection->getFirstItem();
}
-
return $this->categoriesCache[$categoryId][$storeId];
}
@@ -611,7 +642,7 @@ private function getCategoryById($categoryId, $storeId)
*
* @return bool
*/
- private function isCategoryRewritesEnabled()
+ private function isCategoryRewritesEnabled() : bool
{
return (bool)$this->scopeConfig->getValue('catalog/seo/generate_category_product_rewrites');
}
@@ -647,4 +678,42 @@ private function getParentCategoriesUrlRewrites(array $categoryIds, int $storeId
}
return $urls;
}
+
+ /**
+ * Get Products' url_key and name by product Ids
+ *
+ * @param int[] $productIds
+ * @param int $storeId
+ * @param array[] $importedProducts
+ * @return void
+ */
+ private function getUrlKeyAndNameForProductsByIds(array $productIds, int $storeId, array $importedProducts): void
+ {
+ $productCollection = $this->productCollectionFactory->create();
+ $productCollection->setStoreId($storeId);
+ $productCollection->addAttributeToSelect('url_key');
+ $productCollection->addAttributeToSelect('name');
+ $productCollection->addFieldToFilter(
+ 'entity_id',
+ ['in' => array_unique($productIds)]
+ );
+ $products = $productCollection->getItems();
+ foreach ($products as $product) {
+ $productId = $product->getId();
+ $importedProduct = $importedProducts[$productId][$storeId];
+ $urlKey = $product->getUrlKey();
+ if (!empty($urlKey)) {
+ $importedProduct->setData('url_key', $urlKey);
+ continue;
+ }
+ $name = $importedProduct->getName();
+ if (empty($name)) {
+ $name = $product->getName();
+ }
+ if (empty($name)) {
+ continue;
+ }
+ $product->formatUrlKey($name);
+ }
+ }
}
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php
index 9593f5cb4fcc7..650abd6b264a6 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryUrlPathAutogeneratorObserver.php
@@ -8,11 +8,14 @@
namespace Magento\CatalogUrlRewrite\Observer;
use Magento\Catalog\Api\CategoryRepositoryInterface;
+use Magento\Catalog\Api\Data\CategoryInterface;
use Magento\Catalog\Model\Category;
use Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider;
use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
use Magento\CatalogUrlRewrite\Model\ResourceModel\Category\GetDefaultUrlKey;
use Magento\CatalogUrlRewrite\Service\V1\StoreViewService;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Exception\LocalizedException;
@@ -22,6 +25,8 @@
/**
* Class for set or update url path.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CategoryUrlPathAutogeneratorObserver implements ObserverInterface
{
@@ -56,6 +61,11 @@ class CategoryUrlPathAutogeneratorObserver implements ObserverInterface
*/
private $getDefaultUrlKey;
+ /**
+ * @var MetadataPool
+ */
+ private $metadataPool;
+
/**
* @param CategoryUrlPathGenerator $categoryUrlPathGenerator
* @param ChildrenCategoriesProvider $childrenCategoriesProvider
@@ -63,6 +73,7 @@ class CategoryUrlPathAutogeneratorObserver implements ObserverInterface
* @param CategoryRepositoryInterface $categoryRepository
* @param CompositeUrlKey $compositeUrlValidator
* @param GetDefaultUrlKey $getDefaultUrlKey
+ * @param MetadataPool|null $metadataPool
*/
public function __construct(
CategoryUrlPathGenerator $categoryUrlPathGenerator,
@@ -70,7 +81,8 @@ public function __construct(
StoreViewService $storeViewService,
CategoryRepositoryInterface $categoryRepository,
CompositeUrlKey $compositeUrlValidator,
- GetDefaultUrlKey $getDefaultUrlKey
+ GetDefaultUrlKey $getDefaultUrlKey,
+ ?MetadataPool $metadataPool = null
) {
$this->categoryUrlPathGenerator = $categoryUrlPathGenerator;
$this->childrenCategoriesProvider = $childrenCategoriesProvider;
@@ -78,6 +90,8 @@ public function __construct(
$this->categoryRepository = $categoryRepository;
$this->compositeUrlValidator = $compositeUrlValidator;
$this->getDefaultUrlKey = $getDefaultUrlKey;
+ $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()
+ ->get(MetadataPool::class);
}
/**
@@ -101,9 +115,14 @@ public function execute(Observer $observer)
$this->updateUrlKey($category, $resultUrlKey);
}
if ($category->hasChildren()) {
- $defaultUrlKey = $this->getDefaultUrlKey->execute((int)$category->getId());
- if ($defaultUrlKey) {
- $this->updateUrlKey($category, $defaultUrlKey);
+ $metadata = $this->metadataPool->getMetadata(CategoryInterface::class);
+ $linkField = $metadata->getLinkField();
+ $id = $category->getData($linkField);
+ if ($id) {
+ $defaultUrlKey = $this->getDefaultUrlKey->execute((int)$id);
+ if ($defaultUrlKey) {
+ $this->updateUrlKey($category, $defaultUrlKey);
+ }
}
}
$category->setUrlKey(null)->setUrlPath(null);
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php
index 512340354172e..64cd683dfca86 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php
@@ -10,6 +10,7 @@
use Magento\Catalog\Model\Product\Visibility;
use Magento\CatalogUrlRewrite\Model\Products\AppendUrlRewritesToProducts;
use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
+use Magento\CatalogUrlRewrite\Service\V1\StoreViewService;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
@@ -46,22 +47,30 @@ class ProductProcessUrlRewriteSavingObserver implements ObserverInterface
*/
private $getStoresList;
+ /**
+ * @var StoreViewService
+ */
+ private $storeViewService;
+
/**
* @param UrlPersistInterface $urlPersist
- * @param AppendUrlRewritesToProducts|null $appendRewrites
+ * @param AppendUrlRewritesToProducts $appendRewrites
* @param ScopeConfigInterface $scopeConfig
* @param GetStoresListByWebsiteIds $getStoresList
+ * @param StoreViewService $storeViewService
*/
public function __construct(
UrlPersistInterface $urlPersist,
AppendUrlRewritesToProducts $appendRewrites,
ScopeConfigInterface $scopeConfig,
- GetStoresListByWebsiteIds $getStoresList
+ GetStoresListByWebsiteIds $getStoresList,
+ StoreViewService $storeViewService
) {
$this->urlPersist = $urlPersist;
$this->appendRewrites = $appendRewrites;
$this->scopeConfig = $scopeConfig;
$this->getStoresList = $getStoresList;
+ $this->storeViewService = $storeViewService;
}
/**
@@ -82,6 +91,23 @@ public function execute(Observer $observer)
$storesToAdd = $this->getStoresList->execute(
array_diff($product->getWebsiteIds(), $oldWebsiteIds)
);
+
+ if ($product->getStoreId() === Store::DEFAULT_STORE_ID
+ && $product->dataHasChangedFor('visibility')
+ && (int) $product->getOrigData('visibility') === Visibility::VISIBILITY_NOT_VISIBLE
+ ) {
+ foreach ($product->getStoreIds() as $storeId) {
+ if (!$this->storeViewService->doesEntityHaveOverriddenVisibilityForStore(
+ $storeId,
+ $product->getId(),
+ Product::ENTITY
+ )
+ ) {
+ $storesToAdd[] = $storeId;
+ }
+ }
+ $storesToAdd = array_unique($storesToAdd);
+ }
$this->appendRewrites->execute([$product], $storesToAdd);
}
}
@@ -102,9 +128,23 @@ private function deleteObsoleteRewrites(Product $product): void
array_diff($oldWebsiteIds, $product->getWebsiteIds())
);
if ((int)$product->getVisibility() === Visibility::VISIBILITY_NOT_VISIBLE) {
- $isGlobalScope = $product->getStoreId() == Store::DEFAULT_STORE_ID;
- $storesToRemove[] = $isGlobalScope ? $product->getStoreIds() : $product->getStoreId();
+ if ($product->getStoreId() === Store::DEFAULT_STORE_ID) {
+ foreach ($product->getStoreIds() as $storeId) {
+ if (!$this->storeViewService->doesEntityHaveOverriddenVisibilityForStore(
+ $storeId,
+ $product->getId(),
+ Product::ENTITY
+ )
+ ) {
+ $storesToRemove[] = $storeId;
+ }
+ }
+ } else {
+ $storesToRemove[] = $product->getStoreId();
+ }
+ $storesToRemove = array_unique($storesToRemove);
}
+ $storesToRemove = array_filter($storesToRemove);
if ($storesToRemove) {
$this->urlPersist->deleteByData(
[
@@ -130,7 +170,6 @@ private function isWebsiteChanged(Product $product)
return array_diff($oldWebsiteIds, $newWebsiteIds) || array_diff($newWebsiteIds, $oldWebsiteIds);
}
-
/**
* Is product rewrites need to be updated
*
diff --git a/app/code/Magento/CatalogUrlRewrite/Service/V1/StoreViewService.php b/app/code/Magento/CatalogUrlRewrite/Service/V1/StoreViewService.php
index b4a74b6795eed..0c515e90358d0 100644
--- a/app/code/Magento/CatalogUrlRewrite/Service/V1/StoreViewService.php
+++ b/app/code/Magento/CatalogUrlRewrite/Service/V1/StoreViewService.php
@@ -11,7 +11,7 @@
use Magento\Catalog\Api\Data\ProductInterface;
/**
- * Store view service
+ * Service that checks scope overridden values
*/
class StoreViewService
{
@@ -70,6 +70,20 @@ public function doesEntityHaveOverriddenUrlPathForStore($storeId, $entityId, $en
return $this->doesEntityHaveOverriddenUrlAttributeForStore($storeId, $entityId, $entityType, 'url_path');
}
+ /**
+ * Check that entity has overridden visibility for specific store
+ *
+ * @param int $storeId
+ * @param int $entityId
+ * @param string $entityType
+ * @throws \InvalidArgumentException
+ * @return bool
+ */
+ public function doesEntityHaveOverriddenVisibilityForStore($storeId, $entityId, $entityType)
+ {
+ return $this->doesEntityHaveOverriddenUrlAttributeForStore($storeId, $entityId, $entityType, 'visibility');
+ }
+
/**
* Check that entity has overridden url attribute for specific store
*
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/StorefrontCategoryUrlRewriteDifferentStoreTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/StorefrontCategoryUrlRewriteDifferentStoreTest.xml
index 3275a136aa118..abb19c3f95509 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/StorefrontCategoryUrlRewriteDifferentStoreTest.xml
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/StorefrontCategoryUrlRewriteDifferentStoreTest.xml
@@ -17,6 +17,7 @@
+
@@ -44,6 +45,7 @@
+
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php
index d9c6adce9661f..e6a99bddcbc15 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php
@@ -8,6 +8,7 @@
namespace Magento\CatalogUrlRewrite\Test\Unit\Model;
use Magento\Catalog\Api\CategoryRepositoryInterface;
+use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Category;
use Magento\Catalog\Model\Product;
use Magento\CatalogUrlRewrite\Model\ObjectRegistry;
@@ -73,6 +74,9 @@ class ProductScopeRewriteGeneratorTest extends TestCase
/** @var CategoryRepositoryInterface|MockObject */
private $categoryRepositoryMock;
+ /** @var ProductRepositoryInterface|MockObject */
+ private $productRepositoryMock;
+
protected function setUp(): void
{
$this->serializer = $this->createMock(Json::class);
@@ -131,6 +135,7 @@ function ($value) {
->getMock();
$this->categoryRepositoryMock = $this->getMockForAbstractClass(CategoryRepositoryInterface::class);
+ $this->productRepositoryMock = $this->getMockForAbstractClass(ProductRepositoryInterface::class);
$this->productScopeGenerator = (new ObjectManager($this))->getObject(
ProductScopeRewriteGenerator::class,
@@ -144,7 +149,8 @@ function ($value) {
'storeManager' => $this->storeManager,
'mergeDataProviderFactory' => $mergeDataProviderFactory,
'config' => $this->configMock,
- 'categoryRepository' => $this->categoryRepositoryMock
+ 'categoryRepository' => $this->categoryRepositoryMock,
+ 'productRepository' =>$this->productRepositoryMock
]
);
$this->categoryMock = $this->getMockBuilder(Category::class)
@@ -161,7 +167,7 @@ public function testGenerationForGlobalScope()
$product->expects($this->any())->method('getStoreId')->willReturn(null);
$product->expects($this->any())->method('getStoreIds')->willReturn([1]);
$this->storeViewService->expects($this->once())->method('doesEntityHaveOverriddenUrlKeyForStore')
- ->willReturn(false);
+ ->willReturn(true);
$this->initObjectRegistryFactory([]);
$canonical = new UrlRewrite([], $this->serializer);
$canonical->setRequestPath('category-1')
@@ -185,6 +191,7 @@ public function testGenerationForGlobalScope()
->setStoreId(4);
$this->anchorUrlRewriteGenerator->expects($this->any())->method('generate')
->willReturn([$anchorCategories]);
+ $this->productRepositoryMock->expects($this->once())->method('getById')->willReturn($product);
$this->assertEquals(
[
@@ -230,19 +237,6 @@ public function testGenerationForSpecificStore()
);
}
- /**
- * Test method
- */
- public function testSkipGenerationForGlobalScope()
- {
- $product = $this->createMock(Product::class);
- $product->expects($this->any())->method('getStoreIds')->willReturn([1, 2]);
- $this->storeViewService->expects($this->exactly(2))->method('doesEntityHaveOverriddenUrlKeyForStore')
- ->willReturn(true);
-
- $this->assertEquals([], $this->productScopeGenerator->generateForGlobalScope([], $product, 1));
- }
-
/**
* @param array $entities
*/
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
index 955403b40e81f..8573e15e4602a 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
@@ -8,9 +8,7 @@
namespace Magento\CatalogUrlRewrite\Test\Unit\Observer;
use Magento\Catalog\Api\ProductRepositoryInterface;
-use Magento\Catalog\Model\Category;
use Magento\Catalog\Model\ProductFactory;
-use Magento\Catalog\Model\ResourceModel\Category\Collection as CategoryCollection;
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory;
use Magento\CatalogImportExport\Model\Import\Product as ImportProduct;
use Magento\CatalogUrlRewrite\Model\ObjectRegistry;
@@ -40,11 +38,6 @@
*/
class AfterImportDataObserverTest extends TestCase
{
- /**
- * @var string
- */
- private $categoryId = 10;
-
/**
* @var UrlPersistInterface|MockObject
*/
@@ -217,7 +210,6 @@ protected function setUp(): void
$this->productRepository = $this->getMockBuilder(ProductRepositoryInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
-
$this->objectRegistryFactory = $this->createMock(ObjectRegistryFactory::class);
$this->productUrlPathGenerator = $this->createMock(
ProductUrlPathGenerator::class
@@ -238,34 +230,28 @@ protected function setUp(): void
)
->disableOriginalConstructor()
->getMockForAbstractClass();
-
$this->urlRewrite = $this
->getMockBuilder(UrlRewrite::class)
->disableOriginalConstructor()
->getMock();
-
$this->product = $this
->getMockBuilder(\Magento\Catalog\Model\Product::class)
->disableOriginalConstructor()
->getMock();
-
$this->objectRegistry = $this
->getMockBuilder(ObjectRegistry::class)
->disableOriginalConstructor()
->getMock();
-
$mergeDataProviderFactory = $this->createPartialMock(
MergeDataProviderFactory::class,
['create']
);
$this->mergeDataProvider = new MergeDataProvider();
$mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider);
-
$this->categoryCollectionFactory = $this->getMockBuilder(CategoryCollectionFactory::class)
->setMethods(['create'])
->disableOriginalConstructor()
->getMock();
-
$this->objectManager = new ObjectManager($this);
$this->import = $this->objectManager->getObject(
AfterImportDataObserver::class,
@@ -288,10 +274,7 @@ protected function setUp(): void
* Test for afterImportData()
* Covers afterImportData() + protected methods used inside
*
- * @covers \Magento\CatalogUrlRewrite\Observer\AfterImportDataObserver::_populateForUrlGeneration
- * @covers \Magento\CatalogUrlRewrite\Observer\AfterImportDataObserver::isGlobalScope
- * @covers \Magento\CatalogUrlRewrite\Observer\AfterImportDataObserver::populateGlobalProduct
- * @covers \Magento\CatalogUrlRewrite\Observer\AfterImportDataObserver::addProductToImport
+ * @covers \Magento\CatalogUrlRewrite\Observer\AfterImportDataObserver
*
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
@@ -331,7 +314,7 @@ public function testAfterImportData()
[$this->products[0][ImportProduct::COL_SKU]],
[$this->products[1][ImportProduct::COL_SKU]]
)->willReturn([]);
- $getProductWebsitesCallsCount = $productsCount*2;
+ $getProductWebsitesCallsCount = $productsCount * 2;
$this->importProduct
->expects($this->exactly($getProductWebsitesCallsCount))
->method('getProductWebsites')
@@ -349,71 +332,45 @@ public function testAfterImportData()
->expects($this->exactly(1))
->method('getStoreIdByCode')
->willReturnMap($map);
- $product = $this->createPartialMock(
- \Magento\Catalog\Model\Product::class,
- [
- 'getId',
- 'setId',
- 'getSku',
- 'setStoreId',
- 'getStoreId',
- ]
- );
- $product
- ->expects($this->exactly($productsCount))
- ->method('setId')
- ->withConsecutive([$newSku[0]['entity_id']], [$newSku[1]['entity_id']]);
- $product
- ->expects($this->any())
- ->method('getId')
- ->willReturnOnConsecutiveCalls(
- $newSku[0]['entity_id'],
- $newSku[0]['entity_id'],
- $newSku[0]['entity_id'],
- $newSku[0]['entity_id'],
- $newSku[1]['entity_id'],
- $newSku[1]['entity_id'],
- $newSku[1]['entity_id']
- );
- $product
- ->expects($this->exactly($productsCount))
- ->method('getSku')
- ->will(
- $this->onConsecutiveCalls(
- $this->products[0]['sku'],
- $this->products[1]['sku']
- )
- );
- $product
- ->expects($this->exactly($productsCount))
- ->method('getStoreId')
- ->will(
- $this->onConsecutiveCalls(
- $this->products[0][ImportProduct::COL_STORE],
- $this->products[1][ImportProduct::COL_STORE]
- )
- );
- $product
- ->expects($this->exactly($productsCount))
- ->method('setStoreId')
- ->withConsecutive(
- [$this->products[0][ImportProduct::COL_STORE]],
- [$this->products[1][ImportProduct::COL_STORE]]
+ $mockProducts = [];
+ foreach ($this->products as $productsKey => $productsValue) {
+ $product = $this->createPartialMock(
+ \Magento\Catalog\Model\Product::class,
+ [
+ 'getId',
+ 'setId',
+ 'getSku',
+ 'setStoreId',
+ 'getStoreId',
+ ]
);
+ $product->expects($this->any())
+ ->method('setId')
+ ->with($newSku[$productsKey]['entity_id']);
+ $product->expects($this->any())
+ ->method('getId')
+ ->willReturn($newSku[$productsKey]['entity_id']);
+ $product->expects($this->any())
+ ->method('getSku')
+ ->willReturn($productsValue['sku']);
+ $product->expects($this->any())->method('getStoreId')
+ ->willReturn($productsValue[ImportProduct::COL_STORE]);
+ $product->expects($this->any())
+ ->method('setStoreId')
+ ->with($productsValue[ImportProduct::COL_STORE]);
+ $mockProducts[] = $product;
+ }
$this->catalogProductFactory
->expects($this->exactly($productsCount))
->method('create')
- ->willReturn($product);
-
+ ->willReturnOnConsecutiveCalls(...$mockProducts);
$this->urlFinder->expects($this->any())->method('findAllByData')->willReturn([]);
-
$this->productUrlPathGenerator->expects($this->any())->method('getUrlPathWithSuffix')
->willReturn('urlPathWithSuffix');
$this->productUrlPathGenerator->expects($this->any())->method('getUrlPath')
->willReturn('urlPath');
$this->productUrlPathGenerator->expects($this->any())->method('getCanonicalUrlPath')
->willReturn('canonicalUrlPath');
-
$this->urlRewrite->expects($this->any())->method('setStoreId')->willReturnSelf();
$this->urlRewrite->expects($this->any())->method('setEntityId')->willReturnSelf();
$this->urlRewrite->expects($this->any())->method('setEntityType')->willReturnSelf();
@@ -423,227 +380,18 @@ public function testAfterImportData()
$this->urlRewrite->expects($this->any())->method('getRequestPath')->willReturn('requestPath');
$this->urlRewrite->expects($this->any())->method('getStoreId')
->willReturnOnConsecutiveCalls(0, 'not global');
-
$this->urlRewriteFactory->expects($this->any())->method('create')->willReturn($this->urlRewrite);
-
$productUrls = [
'requestPath_0' => $this->urlRewrite,
'requestPath_not global' => $this->urlRewrite
];
-
$this->urlPersist
->expects($this->once())
->method('replace')
->with($productUrls);
-
$this->import->execute($this->observer);
}
- /**
- * Cover canonicalUrlRewriteGenerate().
- */
- public function testCanonicalUrlRewriteGenerateWithUrlPath()
- {
- $productId = 'product_id';
- $requestPath = 'simple-product.html';
- $storeId = 10;
- $product = $this
- ->getMockBuilder(\Magento\Catalog\Model\Product::class)
- ->disableOriginalConstructor()
- ->getMock();
- $productsByStores = [$storeId => $product];
- $products = [
- $productId => $productsByStores,
- ];
-
- $targetPath = 'catalog/product/view/id/' . $productId;
- $this->setPropertyValue($this->import, 'products', $products);
-
- $this->productUrlPathGenerator
- ->expects($this->once())
- ->method('getUrlPathWithSuffix')
- ->willReturn($requestPath);
- $this->productUrlPathGenerator
- ->expects($this->once())
- ->method('getUrlPath')
- ->willReturn('urlPath');
- $this->productUrlPathGenerator
- ->expects($this->once())
- ->method('getCanonicalUrlPath')
- ->willReturn($targetPath);
- $this->urlRewrite
- ->expects($this->once())
- ->method('setStoreId')
- ->with($storeId)->willReturnSelf();
- $this->urlRewrite
- ->expects($this->once())
- ->method('setEntityId')
- ->with($productId)->willReturnSelf();
- $this->urlRewrite
- ->expects($this->once())
- ->method('setEntityType')
- ->with(ProductUrlRewriteGenerator::ENTITY_TYPE)->willReturnSelf();
- $this->urlRewrite
- ->expects($this->once())
- ->method('setRequestPath')
- ->with($requestPath)->willReturnSelf();
- $this->urlRewrite
- ->expects($this->once())
- ->method('setTargetPath')
- ->with($targetPath)->willReturnSelf();
- $this->urlRewriteFactory
- ->expects($this->once())
- ->method('create')
- ->willReturn($this->urlRewrite);
-
- $actualResult = $this->invokeMethod($this->import, 'canonicalUrlRewriteGenerate');
- $this->assertEquals(
- [
- $this->urlRewrite,
- ],
- $actualResult
- );
- }
-
- /**
- * Cover canonicalUrlRewriteGenerate().
- */
- public function testCanonicalUrlRewriteGenerateWithEmptyUrlPath()
- {
- $productId = 'product_id';
- $storeId = 10;
- $product = $this
- ->getMockBuilder(\Magento\Catalog\Model\Product::class)
- ->disableOriginalConstructor()
- ->getMock();
- $productsByStores = [$storeId => $product];
- $products = [
- $productId => $productsByStores,
- ];
-
- $this->setPropertyValue($this->import, 'products', $products);
-
- $this->productUrlPathGenerator
- ->expects($this->once())
- ->method('getUrlPath')
- ->willReturn('');
- $this->urlRewriteFactory
- ->expects($this->never())
- ->method('create');
-
- $actualResult = $this->invokeMethod($this->import, 'canonicalUrlRewriteGenerate');
- $this->assertEquals([], $actualResult);
- }
-
- /**
- * Cover categoriesUrlRewriteGenerate().
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- */
- public function testCategoriesUrlRewriteGenerate()
- {
- $urlPathWithCategory = 'category/simple-product.html';
- $storeId = 10;
- $productId = 'product_id';
- $canonicalUrlPathWithCategory = 'canonical-path-with-category';
- $product = $this
- ->getMockBuilder(\Magento\Catalog\Model\Product::class)
- ->disableOriginalConstructor()
- ->getMock();
- $productsByStores = [
- $storeId => $product,
- ];
- $products = [
- $productId => $productsByStores,
- ];
- $categoryCache = [
- $productId => [$this->categoryId],
- ];
-
- $this->setPropertyValue($this->import, 'products', $products);
- $this->setPropertyValue($this->import, 'categoryCache', $categoryCache);
- $this->setPropertyValue($this->import, 'import', $this->importProduct);
-
- $this->productUrlPathGenerator
- ->expects($this->any())
- ->method('getUrlPathWithSuffix')
- ->willReturn($urlPathWithCategory);
- $this->productUrlPathGenerator
- ->expects($this->any())
- ->method('getCanonicalUrlPath')
- ->willReturn($canonicalUrlPathWithCategory);
- $category = $this->createMock(Category::class);
- $category
- ->expects($this->any())
- ->method('getId')
- ->willReturn($this->categoryId);
- $category
- ->expects($this->any())
- ->method('getAnchorsAbove')
- ->willReturn([]);
- $categoryCollection = $this->getMockBuilder(CategoryCollection::class)
- ->disableOriginalConstructor()
- ->getMock();
- $categoryCollection->expects($this->once())
- ->method('addIdFilter')
- ->with([$this->categoryId])
- ->willReturnSelf();
- $categoryCollection->expects($this->once())
- ->method('setStoreId')
- ->with($storeId)
- ->willReturnSelf();
- $categoryCollection->expects($this->exactly(3))
- ->method('addAttributeToSelect')
- ->withConsecutive(
- ['name'],
- ['url_key'],
- ['url_path']
- )->willReturnSelf();
- $categoryCollection->expects($this->once())
- ->method('getFirstItem')
- ->willReturn($category);
-
- $this->categoryCollectionFactory->expects($this->once())
- ->method('create')
- ->willReturn($categoryCollection);
-
- $this->urlRewrite
- ->expects($this->any())
- ->method('setStoreId')
- ->with($storeId)->willReturnSelf();
- $this->urlRewrite
- ->expects($this->any())
- ->method('setEntityId')
- ->with($productId)->willReturnSelf();
- $this->urlRewrite
- ->expects($this->any())
- ->method('setEntityType')
- ->with(ProductUrlRewriteGenerator::ENTITY_TYPE)->willReturnSelf();
- $this->urlRewrite
- ->expects($this->any())
- ->method('setRequestPath')
- ->with($urlPathWithCategory)->willReturnSelf();
- $this->urlRewrite
- ->expects($this->any())
- ->method('setTargetPath')
- ->with($canonicalUrlPathWithCategory)->willReturnSelf();
- $this->urlRewrite
- ->expects($this->any())
- ->method('setMetadata')
- ->with(['category_id' => $this->categoryId])->willReturnSelf();
- $this->urlRewriteFactory
- ->expects($this->any())
- ->method('create')
- ->willReturn($this->urlRewrite);
-
- $actualResult = $this->invokeMethod($this->import, 'categoriesUrlRewriteGenerate');
- $this->assertEquals(
- [
- $this->urlRewrite,
- ],
- $actualResult
- );
- }
-
/**
* @param AfterImportDataObserver $object
* @param string $property
@@ -658,21 +406,6 @@ protected function setPropertyValue($object, $property, $value)
$reflectionProperty->setValue($object, $value);
}
- /**
- * @param AfterImportDataObserver $object
- * @param string $methodName
- * @param array $parameters
- * @return mixed
- */
- protected function invokeMethod($object, $methodName, array $parameters = [])
- {
- $reflection = new \ReflectionClass(get_class($object));
- $method = $reflection->getMethod($methodName);
- $method->setAccessible(true);
-
- return $method->invokeArgs($object, $parameters);
- }
-
/**
* @param mixed $storeId
* @param mixed $productId
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php
index 367336c09f862..8c85f30482ad0 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryUrlPathAutogeneratorObserverTest.php
@@ -7,6 +7,7 @@
namespace Magento\CatalogUrlRewrite\Test\Unit\Observer;
+use Magento\Catalog\Api\Data\CategoryInterface;
use Magento\Catalog\Model\Category;
use Magento\Catalog\Model\ResourceModel\Category as CategoryResource;
use Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider;
@@ -14,6 +15,8 @@
use Magento\CatalogUrlRewrite\Model\ResourceModel\Category\GetDefaultUrlKey;
use Magento\CatalogUrlRewrite\Observer\CategoryUrlPathAutogeneratorObserver;
use Magento\CatalogUrlRewrite\Service\V1\StoreViewService;
+use Magento\Framework\EntityManager\EntityMetadataInterface;
+use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Event\Observer;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
@@ -24,6 +27,8 @@
/**
* Unit tests for \Magento\CatalogUrlRewrite\Observer\CategoryUrlPathAutogeneratorObserver class.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CategoryUrlPathAutogeneratorObserverTest extends TestCase
{
@@ -72,6 +77,16 @@ class CategoryUrlPathAutogeneratorObserverTest extends TestCase
*/
private $getDefaultUrlKey;
+ /**
+ * @var MetadataPool|MockObject
+ */
+ private $metadataPool;
+
+ /**
+ * @var EntityMetadataInterface|MockObject
+ */
+ private $entityMetaDataInterface;
+
/**
* @inheritDoc
*/
@@ -83,17 +98,21 @@ protected function setUp(): void
->disableOriginalConstructor()
->getMock();
$this->categoryResource = $this->createMock(CategoryResource::class);
- $this->category = $this->createPartialMock(
- Category::class,
- [
- 'dataHasChangedFor',
- 'getResource',
- 'getStoreId',
- 'formatUrlKey',
- 'getId',
- 'hasChildren',
- ]
- );
+ $this->category = $this->getMockBuilder(Category::class)
+ ->onlyMethods(
+ [
+ 'dataHasChangedFor',
+ 'getResource',
+ 'getStoreId',
+ 'formatUrlKey',
+ 'hasChildren',
+ 'getData',
+ 'getUrlKey'
+ ]
+ )
+ ->addMethods(['getUrlPath'])
+ ->disableOriginalConstructor()
+ ->getMock();
$this->category->expects($this->any())->method('getResource')->willReturn($this->categoryResource);
$this->observer->expects($this->any())->method('getEvent')->willReturnSelf();
$this->observer->expects($this->any())->method('getCategory')->willReturn($this->category);
@@ -112,6 +131,14 @@ protected function setUp(): void
->onlyMethods(['execute'])
->getMock();
+ $this->metadataPool = $this->getMockBuilder(MetadataPool::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['getMetadata'])
+ ->getMock();
+
+ $this->entityMetaDataInterface = $this->getMockBuilder(EntityMetadataInterface::class)
+ ->getMockForAbstractClass();
+
$this->categoryUrlPathAutogeneratorObserver = (new ObjectManagerHelper($this))->getObject(
CategoryUrlPathAutogeneratorObserver::class,
[
@@ -120,6 +147,7 @@ protected function setUp(): void
'storeViewService' => $this->storeViewService,
'compositeUrlValidator' => $this->compositeUrlValidator,
'getDefaultUrlKey' => $this->getDefaultUrlKey,
+ 'metadataPool' => $this->metadataPool
]
);
}
@@ -134,13 +162,20 @@ public function testShouldFormatUrlKeyAndGenerateUrlPathIfUrlKeyIsNotUsingDefaul
$expectedUrlKey = 'formatted_url_key';
$expectedUrlPath = 'generated_url_path';
$categoryData = ['use_default' => ['url_key' => 0], 'url_key' => 'some_key', 'url_path' => ''];
+ $this->category->expects($this->any())
+ ->method('getUrlKey')
+ ->willReturnOnConsecutiveCalls($categoryData['url_key'], null, $expectedUrlKey);
+ $this->category->expects($this->any())
+ ->method('getUrlPath')
+ ->willReturnOnConsecutiveCalls($categoryData['url_path'], $expectedUrlPath);
$this->category->setData($categoryData);
$this->category->isObjectNew($isObjectNew);
$this->categoryUrlPathGenerator->expects($this->once())->method('getUrlKey')->willReturn($expectedUrlKey);
$this->categoryUrlPathGenerator->expects($this->once())->method('getUrlPath')->willReturn($expectedUrlPath);
$this->assertEquals($categoryData['url_key'], $this->category->getUrlKey());
$this->assertEquals($categoryData['url_path'], $this->category->getUrlPath());
- $this->compositeUrlValidator->expects($this->once())->method('validate')->with('formatted_url_key')->willReturn([]);
+ $this->compositeUrlValidator->expects($this->once())->method('validate')
+ ->with('formatted_url_key')->willReturn([]);
$this->categoryUrlPathAutogeneratorObserver->execute($this->observer);
$this->assertEquals($expectedUrlKey, $this->category->getUrlKey());
$this->assertEquals($expectedUrlPath, $this->category->getUrlPath());
@@ -179,11 +214,32 @@ public function testShouldResetUrlPathAndUrlKeyIfUrlKeyIsUsingDefaultValue(bool
$this->category->expects($this->once())
->method('hasChildren')
->willReturn(false);
+ $this->metadataPool->expects($this->any())
+ ->method('getMetadata')
+ ->with(CategoryInterface::class)
+ ->willReturn($this->entityMetaDataInterface);
+ $this->entityMetaDataInterface->expects($this->any())
+ ->method('getLinkField')
+ ->willReturn('row_id');
+ $this->category->expects($this->any())
+ ->method('getUrlKey')
+ ->willReturn($categoryData['url_key']);
+ $this->category->expects($this->any())
+ ->method('getUrlPath')
+ ->willReturn($categoryData['url_path']);
+ $this->category->expects($this->any())
+ ->method('getData')
+ ->willReturnMap(
+ [
+ ['use_default', null, ['url_key' => 1]],
+ ['row_id', null, null],
+ ]
+ );
$this->assertEquals($categoryData['url_key'], $this->category->getUrlKey());
$this->assertEquals($categoryData['url_path'], $this->category->getUrlPath());
$this->categoryUrlPathAutogeneratorObserver->execute($this->observer);
- $this->assertNull($this->category->getUrlKey());
- $this->assertNull($this->category->getUrlPath());
+ $this->assertNotEmpty($this->category->getUrlKey());
+ $this->assertNotEmpty($this->category->getUrlPath());
}
/**
@@ -201,15 +257,18 @@ public function shouldResetUrlPathAndUrlKeyIfUrlKeyIsUsingDefaultValueDataProvid
/**
* @return void
+ * @throws LocalizedException
*/
public function testShouldUpdateUrlPathForChildrenIfUrlKeyIsUsingDefaultValueForSpecificStore(): void
{
$storeId = 1;
$categoryId = 1;
+ $rowId = 1;
$categoryData = [
'use_default' => ['url_key' => 1],
'url_key' => null,
'url_path' => 'some_path',
+ 'row_id' => 1
];
$this->category->setData($categoryData);
@@ -220,9 +279,24 @@ public function testShouldUpdateUrlPathForChildrenIfUrlKeyIsUsingDefaultValueFor
$this->category->expects($this->once())
->method('hasChildren')
->willReturn(true);
- $this->category->expects($this->exactly(2))
- ->method('getId')
- ->willReturn($categoryId);
+ $this->metadataPool->expects($this->any())
+ ->method('getMetadata')
+ ->with(CategoryInterface::class)
+ ->willReturn($this->entityMetaDataInterface);
+ $this->entityMetaDataInterface->expects($this->any())
+ ->method('getLinkField')
+ ->willReturn('row_id');
+ $this->category->expects($this->any())
+ ->method('getUrlKey')
+ ->willReturn(false);
+ $this->category->expects($this->any())
+ ->method('getData')
+ ->willReturnMap(
+ [
+ ['use_default', null, ['url_key' => 1]],
+ ['row_id', null, $rowId],
+ ]
+ );
$this->getDefaultUrlKey->expects($this->once())
->method('execute')
->with($categoryId)
@@ -262,7 +336,7 @@ public function testShouldUpdateUrlPathForChildrenIfUrlKeyIsUsingDefaultValueFor
->willReturn([$childCategory]);
$this->categoryUrlPathAutogeneratorObserver->execute($this->observer);
- $this->assertNull($this->category->getUrlKey());
+ $this->assertFalse($this->category->getUrlKey());
$this->assertNull($this->category->getUrlPath());
}
@@ -312,7 +386,8 @@ public function testUrlPathAttributeUpdating()
$this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPath')->willReturn($expectedUrlPath);
$this->categoryResource->expects($this->once())->method('saveAttribute')->with($this->category, 'url_path');
$this->category->expects($this->once())->method('dataHasChangedFor')->with('url_path')->willReturn(false);
- $this->compositeUrlValidator->expects($this->once())->method('validate')->with('formatted_url_key')->willReturn([]);
+ $this->compositeUrlValidator->expects($this->once())->method('validate')
+ ->with('formatted_url_key')->willReturn([]);
$this->categoryUrlPathAutogeneratorObserver->execute($this->observer);
}
@@ -368,7 +443,8 @@ public function testChildrenUrlPathAttributeUpdatingForSpecificStore()
$this->childrenCategoriesProvider->expects($this->once())->method('getChildren')->willReturn([$childCategory]);
$childCategory->expects($this->once())->method('setUrlPath')->with('generated_url_path')->willReturnSelf();
$childCategoryResource->expects($this->once())->method('saveAttribute')->with($childCategory, 'url_path');
- $this->compositeUrlValidator->expects($this->once())->method('validate')->with('generated_url_key')->willReturn([]);
+ $this->compositeUrlValidator->expects($this->once())->method('validate')
+ ->with('generated_url_key')->willReturn([]);
$this->categoryUrlPathAutogeneratorObserver->execute($this->observer);
}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/ProductProcessUrlRewriteSavingObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/ProductProcessUrlRewriteSavingObserverTest.php
index 0ceff2aeff5e5..0f74ce0f14d77 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/ProductProcessUrlRewriteSavingObserverTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/ProductProcessUrlRewriteSavingObserverTest.php
@@ -9,13 +9,17 @@
namespace Magento\CatalogUrlRewrite\Test\Unit\Observer;
use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\Visibility;
use Magento\CatalogUrlRewrite\Model\Products\AppendUrlRewritesToProducts;
+use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
use Magento\CatalogUrlRewrite\Observer\ProductProcessUrlRewriteSavingObserver;
+use Magento\CatalogUrlRewrite\Service\V1\StoreViewService;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Event;
use Magento\Framework\Event\Observer;
use Magento\Store\Model\StoreResolver\GetStoresListByWebsiteIds;
use Magento\UrlRewrite\Model\UrlPersistInterface;
+use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -60,6 +64,11 @@ class ProductProcessUrlRewriteSavingObserverTest extends TestCase
*/
private $scopeConfig;
+ /**
+ * @var MockObject|StoreViewService
+ */
+ private $storeViewService;
+
/**
* @inheritdoc
*/
@@ -67,21 +76,8 @@ protected function setUp(): void
{
$this->urlPersist = $this->getMockForAbstractClass(UrlPersistInterface::class);
$this->product = $this->getMockBuilder(Product::class)
- ->addMethods(['getIsChangedWebsites', 'getIsChangedCategories'])
- ->onlyMethods(
- [
- 'getId',
- 'dataHasChangedFor',
- 'getVisibility',
- 'getStoreId',
- 'getWebsiteIds',
- 'getOrigData',
- 'getCategoryCollection',
- ]
- )
->disableOriginalConstructor()
- ->getMock();
- $this->product->expects($this->any())->method('getId')->willReturn(3);
+ ->getMockForAbstractClass();
$this->event = $this->getMockBuilder(Event::class)
->addMethods(['getProduct'])
->disableOriginalConstructor()
@@ -105,11 +101,14 @@ protected function setUp(): void
->disableOriginalConstructor()
->getMock();
+ $this->storeViewService = $this->createMock(StoreViewService::class);
+
$this->model = new ProductProcessUrlRewriteSavingObserver(
$this->urlPersist,
$this->appendRewrites,
$this->scopeConfig,
- $getStoresList
+ $getStoresList,
+ $this->storeViewService
);
}
@@ -117,124 +116,246 @@ protected function setUp(): void
* Data provider
*
* @return array
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function urlKeyDataProvider()
{
return [
'url changed' => [
- 'isChangedUrlKey' => true,
- 'isChangedVisibility' => false,
- 'isChangedWebsites' => false,
- 'isChangedCategories' => false,
- 'visibilityResult' => 4,
- 'expectedReplaceCount' => 1,
- 'websitesWithProduct' => [1],
-
+ 'origData' => [
+ 'entity_id' => 101,
+ 'id' => 101,
+ 'url_key' => 'simple',
+ 'visibility' => Visibility::VISIBILITY_BOTH,
+ 'website_ids' => [1],
+ 'store_id' => 1,
+ 'is_changed_categories' => null,
+ ],
+ 'newData' => [
+ 'url_key' => 'simple1',
+ ],
+ 'expectedExecutionCount' => 1,
],
'no chnages' => [
- 'isChangedUrlKey' => false,
- 'isChangedVisibility' => false,
- 'isChangedWebsites' => false,
- 'isChangedCategories' => false,
- 'visibilityResult' => 4,
- 'expectedReplaceCount' => 0,
- 'websitesWithProduct' => [1],
+ 'origData' => [
+ 'entity_id' => 101,
+ 'id' => 101,
+ 'url_key' => 'simple',
+ 'visibility' => Visibility::VISIBILITY_BOTH,
+ 'website_ids' => [1],
+ 'store_id' => 1,
+ 'is_changed_categories' => null,
+ ],
+ 'newData' => [],
+ 'expectedExecutionCount' => 0,
],
'visibility changed' => [
- 'isChangedUrlKey' => false,
- 'isChangedVisibility' => true,
- 'isChangedWebsites' => false,
- 'isChangedCategories' => false,
- 'visibilityResult' => 4,
- 'expectedReplaceCount' => 1,
- 'websitesWithProduct' => [1],
+ 'origData' => [
+ 'entity_id' => 101,
+ 'id' => 101,
+ 'url_key' => 'simple',
+ 'visibility' => Visibility::VISIBILITY_BOTH,
+ 'website_ids' => [1],
+ 'store_id' => 1,
+ 'is_changed_categories' => null,
+ ],
+ 'newData' => [
+ 'visibility' => Visibility::VISIBILITY_IN_CATALOG,
+ ],
+ 'expectedExecutionCount' => 1,
],
'websites changed' => [
- 'isChangedUrlKey' => false,
- 'isChangedVisibility' => false,
- 'isChangedWebsites' => true,
- 'isChangedCategories' => false,
- 'visibilityResult' => 4,
- 'expectedReplaceCount' => 1,
- 'websitesWithProduct' => [1],
+ 'origData' => [
+ 'entity_id' => 101,
+ 'id' => 101,
+ 'url_key' => 'simple',
+ 'visibility' => Visibility::VISIBILITY_BOTH,
+ 'website_ids' => [1],
+ 'store_id' => 1,
+ 'is_changed_categories' => null,
+ ],
+ 'newData' => [
+ 'website_ids' => [1, 2],
+ ],
+ 'expectedExecutionCount' => 1,
],
'categories changed' => [
- 'isChangedUrlKey' => false,
- 'isChangedVisibility' => false,
- 'isChangedWebsites' => false,
- 'isChangedCategories' => true,
- 'visibilityResult' => 4,
- 'expectedReplaceCount' => 1,
- 'websitesWithProduct' => [1],
+ 'origData' => [
+ 'entity_id' => 101,
+ 'id' => 101,
+ 'url_key' => 'simple',
+ 'visibility' => Visibility::VISIBILITY_BOTH,
+ 'website_ids' => [1],
+ 'store_id' => 1,
+ 'is_changed_categories' => null,
+ ],
+ 'newData' => [
+ 'is_changed_categories' => true,
+ ],
+ 'expectedExecutionCount' => 1,
+ ],
+ 'url changed with visibility - invisible' => [
+ 'origData' => [
+ 'entity_id' => 101,
+ 'id' => 101,
+ 'url_key' => 'simple',
+ 'visibility' => Visibility::VISIBILITY_NOT_VISIBLE,
+ 'website_ids' => [1],
+ 'store_id' => 1,
+ 'is_changed_categories' => null,
+ ],
+ 'newData' => [
+ 'url_key' => 'simple1',
+ ],
+ 'expectedExecutionCount' => 0,
],
- 'url changed invisible' => [
- 'isChangedUrlKey' => true,
- 'isChangedVisibility' => false,
- 'isChangedWebsites' => false,
- 'isChangedCategories' => false,
- 'visibilityResult' => 1,
- 'expectedReplaceCount' => 0,
- 'websitesWithProduct' => [1],
+ 'visibility changed to invisible in global scope - 1' => [
+ 'origData' => [
+ 'entity_id' => 101,
+ 'id' => 101,
+ 'url_key' => 'simple',
+ 'visibility' => Visibility::VISIBILITY_BOTH,
+ 'website_ids' => [1],
+ 'store_id' => 0,
+ 'store_ids' => [1, 2],
+ 'is_changed_categories' => null,
+ ],
+ 'newData' => [
+ 'visibility' => Visibility::VISIBILITY_NOT_VISIBLE,
+ ],
+ 'expectedExecutionCount' => 1,
+ 'expectedStoresToAdd' => [],
+ 'doesEntityHaveOverriddenVisibilityForStore' => [
+ 1 => false,
+ 2 => false,
+ ],
+ 'expectedStoresToRemove' => [1, 2]
+ ],
+ 'visibility changed to invisible in global scope - 2' => [
+ 'origData' => [
+ 'entity_id' => 101,
+ 'id' => 101,
+ 'url_key' => 'simple',
+ 'visibility' => Visibility::VISIBILITY_BOTH,
+ 'website_ids' => [1],
+ 'store_id' => 0,
+ 'store_ids' => [1, 2],
+ 'is_changed_categories' => null,
+ ],
+ 'newData' => [
+ 'visibility' => Visibility::VISIBILITY_NOT_VISIBLE,
+ ],
+ 'expectedExecutionCount' => 1,
+ 'expectedStoresToAdd' => [],
+ 'doesEntityHaveOverriddenVisibilityForStore' => [
+ 1 => false,
+ 2 => true,
+ ],
+ 'expectedStoresToRemove' => [1]
+ ],
+ 'visibility changed from invisible to visible in global scope - 1' => [
+ 'origData' => [
+ 'entity_id' => 101,
+ 'id' => 101,
+ 'url_key' => 'simple',
+ 'visibility' => Visibility::VISIBILITY_NOT_VISIBLE,
+ 'website_ids' => [1],
+ 'store_id' => 0,
+ 'store_ids' => [1, 2],
+ 'is_changed_categories' => null,
+ ],
+ 'newData' => [
+ 'visibility' => Visibility::VISIBILITY_BOTH,
+ ],
+ 'expectedExecutionCount' => 1,
+ 'expectedStoresToAdd' => [1, 2],
+ 'doesEntityHaveOverriddenVisibilityForStore' => [
+ 1 => false,
+ 2 => false,
+ ]
+ ],
+ 'visibility changed from invisible to visible in global scope - 2' => [
+ 'origData' => [
+ 'entity_id' => 101,
+ 'id' => 101,
+ 'url_key' => 'simple',
+ 'visibility' => Visibility::VISIBILITY_NOT_VISIBLE,
+ 'website_ids' => [1],
+ 'store_id' => 0,
+ 'store_ids' => [1, 2],
+ 'is_changed_categories' => null,
+ ],
+ 'newData' => [
+ 'visibility' => Visibility::VISIBILITY_BOTH,
+ ],
+ 'expectedExecutionCount' => 1,
+ 'expectedStoresToAdd' => [1],
+ 'doesEntityHaveOverriddenVisibilityForStore' => [
+ 1 => false,
+ 2 => true,
+ ]
],
];
}
/**
- * @param bool $isChangedUrlKey
- * @param bool $isChangedVisibility
- * @param bool $isChangedWebsites
- * @param bool $isChangedCategories
- * @param bool $visibilityResult
- * @param int $expectedReplaceCount
- * @param array $websitesWithProduct
- *
+ * @param array $origData
+ * @param array $newData
+ * @param int $expectedExecutionCount
+ * @param int $expectedStoresToAdd
+ * @throws \Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException
* @dataProvider urlKeyDataProvider
*/
public function testExecuteUrlKey(
- $isChangedUrlKey,
- $isChangedVisibility,
- $isChangedWebsites,
- $isChangedCategories,
- $visibilityResult,
- $expectedReplaceCount,
- $websitesWithProduct
+ array $origData,
+ array $newData,
+ int $expectedExecutionCount,
+ array $expectedStoresToAdd = [],
+ array $doesEntityHaveOverriddenVisibilityForStore = [],
+ array $expectedStoresToRemove = [],
) {
- $this->product->expects($this->any())->method('getStoreId')->willReturn(12);
+ $this->product->setData($origData);
+ $this->product->setOrigData();
+ $this->product->addData($newData);
- $this->product->expects($this->any())
- ->method('dataHasChangedFor')
+ $this->storeViewService
+ ->method('doesEntityHaveOverriddenVisibilityForStore')
->willReturnMap(
- [
- ['visibility', $isChangedVisibility],
- ['url_key', $isChangedUrlKey],
- ]
+ array_map(
+ function (int $storeId, bool $override) {
+ return [$storeId, $this->product->getId(), Product::ENTITY, $override];
+ },
+ array_keys($doesEntityHaveOverriddenVisibilityForStore),
+ $doesEntityHaveOverriddenVisibilityForStore
+ )
);
-
- $this->product->expects($this->any())
- ->method('getIsChangedWebsites')
- ->willReturn($isChangedWebsites);
-
- $this->product->expects($this->any())
- ->method('getIsChangedCategories')
- ->willReturn($isChangedCategories);
-
- $this->product->expects($this->any())->method('getWebsiteIds')->will(
- $this->returnValue($websitesWithProduct)
- );
-
- $this->product->expects($this->any())
- ->method('getVisibility')
- ->willReturn($visibilityResult);
-
- $this->product->expects($this->any())
- ->method('getOrigData')
- ->willReturn($isChangedWebsites ? [] : $websitesWithProduct);
$this->scopeConfig->expects($this->any())
->method('isSetFlag')
->willReturn(true);
- $this->appendRewrites->expects($this->exactly($expectedReplaceCount))
- ->method('execute');
+ if (!$expectedExecutionCount) {
+ $this->appendRewrites->expects($this->never())
+ ->method('execute');
+ } else {
+ $this->appendRewrites->expects($this->exactly($expectedExecutionCount))
+ ->method('execute')
+ ->with(
+ [$this->product],
+ $expectedStoresToAdd
+ );
+ }
+
+ if ($expectedStoresToRemove) {
+ $this->urlPersist->expects($this->once())
+ ->method('deleteByData')
+ ->with(
+ [
+ UrlRewrite::ENTITY_ID => $this->product->getId(),
+ UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
+ UrlRewrite::STORE_ID => $expectedStoresToRemove,
+ ]
+ );
+ }
$this->model->execute($this->observer);
}
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/CatalogUrlRewrite/etc/config.xml b/app/code/Magento/CatalogUrlRewrite/etc/config.xml
index d05c0b4b7aa59..116982c7da31c 100644
--- a/app/code/Magento/CatalogUrlRewrite/etc/config.xml
+++ b/app/code/Magento/CatalogUrlRewrite/etc/config.xml
@@ -9,7 +9,7 @@
- 1
+ 0
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/Block/Product/ProductsList.php b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php
index 7e6693ce68ef9..d35590efc93b2 100644
--- a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php
+++ b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php
@@ -43,28 +43,27 @@ class ProductsList extends AbstractProduct implements BlockInterface, IdentityIn
/**
* Default value for products count that will be shown
*/
- const DEFAULT_PRODUCTS_COUNT = 10;
+ public const DEFAULT_PRODUCTS_COUNT = 10;
/**
* Name of request parameter for page number value
*
- * @deprecated @see $this->getData('page_var_name')
+ * @deprecated
+ * @see $this->getData('page_var_name')
*/
- const PAGE_VAR_NAME = 'np';
+ public const PAGE_VAR_NAME = 'np';
/**
* Default value for products per page
*/
- const DEFAULT_PRODUCTS_PER_PAGE = 5;
+ public const DEFAULT_PRODUCTS_PER_PAGE = 5;
/**
* Default value whether show pager or not
*/
- const DEFAULT_SHOW_PAGER = false;
+ public const DEFAULT_SHOW_PAGER = false;
/**
- * Instance of pager block
- *
* @var Pager
*/
protected $pager;
@@ -75,15 +74,11 @@ class ProductsList extends AbstractProduct implements BlockInterface, IdentityIn
protected $httpContext;
/**
- * Catalog product visibility
- *
* @var Visibility
*/
protected $catalogProductVisibility;
/**
- * Product collection factory
- *
* @var CollectionFactory
*/
protected $productCollectionFactory;
@@ -223,6 +218,7 @@ public function getCacheKeyInfo()
$this->_storeManager->getStore()->getId(),
$this->_design->getDesignTheme()->getId(),
$this->httpContext->getValue(\Magento\Customer\Model\Context::CONTEXT_GROUP),
+ $this->json->serialize($this->httpContext->getValue('tax_rates')),
(int)$this->getRequest()->getParam($this->getData('page_var_name'), 1),
$this->getProductsPerPage(),
$this->getProductsCount(),
@@ -537,7 +533,8 @@ public function getTitle()
* Get currency of product
*
* @return PriceCurrencyInterface
- * @deprecated 100.2.0
+ * @deprecated
+ * @see Constructor injection
*/
private function getPriceCurrency()
{
diff --git a/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php b/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php
index 41f23d9c6dfbd..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;
/**
@@ -87,9 +87,7 @@ public function loadAttributeOptions()
$productAttributes = array_filter(
$productAttributes,
function ($attribute) {
- return $attribute->getFrontendLabel() &&
- $attribute->getFrontendInput() !== 'text' &&
- $attribute->getAttributeCode() !== ProductInterface::STATUS;
+ return $attribute->getFrontendLabel();
}
);
@@ -203,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/Mftf/Test/StorefrontProductGridUIUpdatesOnDesktopTest.xml b/app/code/Magento/CatalogWidget/Test/Mftf/Test/StorefrontProductGridUIUpdatesOnDesktopTest.xml
index 5590aa1cdcefa..e833c8e72f47a 100644
--- a/app/code/Magento/CatalogWidget/Test/Mftf/Test/StorefrontProductGridUIUpdatesOnDesktopTest.xml
+++ b/app/code/Magento/CatalogWidget/Test/Mftf/Test/StorefrontProductGridUIUpdatesOnDesktopTest.xml
@@ -61,6 +61,7 @@
+
diff --git a/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php b/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php
index 87a76ab801a1f..d698683a0e058 100644
--- a/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php
+++ b/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php
@@ -163,7 +163,14 @@ public function testGetCacheKeyInfo()
$theme->expects($this->once())->method('getId')->willReturn('blank');
$this->design->expects($this->once())->method('getDesignTheme')->willReturn($theme);
- $this->httpContext->expects($this->once())->method('getValue')->willReturn('context_group');
+ $this->httpContext->expects($this->exactly(2))
+ ->method('getValue')
+ ->withConsecutive(
+ [$this->equalTo(\Magento\Customer\Model\Context::CONTEXT_GROUP)],
+ [$this->equalTo('tax_rates')]
+ )
+ ->willReturnOnConsecutiveCalls('context_group', [10]);
+
$this->productsList->setData('conditions', 'some_serialized_conditions');
$this->productsList->setData('page_var_name', 'page_number');
@@ -188,6 +195,7 @@ public function testGetCacheKeyInfo()
1,
'blank',
'context_group',
+ '[10]',
1,
5,
10,
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/Block/Checkout/LayoutProcessor.php b/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php
index 16450ec6ff2c2..e25e087e607ee 100644
--- a/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php
+++ b/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php
@@ -132,9 +132,9 @@ private function convertElementsToSelect($elements, $attributesToConvert)
$elements[$code]['dataType'] = 'select';
$elements[$code]['formElement'] = 'select';
- foreach ($options as $key => $value) {
+ foreach ($options as $value) {
$elements[$code]['options'][] = [
- 'value' => $key,
+ 'value' => $value,
'label' => $value,
];
}
diff --git a/app/code/Magento/Checkout/Controller/Cart/Add.php b/app/code/Magento/Checkout/Controller/Cart/Add.php
index 009505d8e7b2f..ea02c43acc022 100644
--- a/app/code/Magento/Checkout/Controller/Cart/Add.php
+++ b/app/code/Magento/Checkout/Controller/Cart/Add.php
@@ -14,6 +14,7 @@
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Framework\Filter\LocalizedToNormalized;
/**
* Controller for processing add to cart action.
@@ -105,7 +106,7 @@ public function execute()
$params = $this->getRequest()->getParams();
try {
if (isset($params['qty'])) {
- $filter = new \Zend_Filter_LocalizedToNormalized(
+ $filter = new LocalizedToNormalized(
['locale' => $this->_objectManager->get(
\Magento\Framework\Locale\ResolverInterface::class
)->getLocale()]
diff --git a/app/code/Magento/Checkout/Controller/Cart/EstimatePost.php b/app/code/Magento/Checkout/Controller/Cart/EstimatePost.php
index 581b9180e91d8..d247c96f5afb2 100644
--- a/app/code/Magento/Checkout/Controller/Cart/EstimatePost.php
+++ b/app/code/Magento/Checkout/Controller/Cart/EstimatePost.php
@@ -6,9 +6,10 @@
namespace Magento\Checkout\Controller\Cart;
use Magento\Framework;
+use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Checkout\Model\Cart as CustomerCart;
-class EstimatePost extends \Magento\Checkout\Controller\Cart
+class EstimatePost extends \Magento\Checkout\Controller\Cart implements HttpPostActionInterface
{
/**
* @var \Magento\Quote\Api\CartRepositoryInterface
@@ -63,9 +64,7 @@ public function execute()
->setCity($city)
->setPostcode($postcode)
->setRegionId($regionId)
- ->setRegion($region)
- ->setCollectShippingRates(true);
- $this->quoteRepository->save($this->cart->getQuote());
+ ->setRegion($region);
$this->cart->save();
return $this->_goBack();
}
diff --git a/app/code/Magento/Checkout/Controller/Cart/UpdateItemOptions.php b/app/code/Magento/Checkout/Controller/Cart/UpdateItemOptions.php
index 205f0aa8dbd2e..c0a2993887d57 100644
--- a/app/code/Magento/Checkout/Controller/Cart/UpdateItemOptions.php
+++ b/app/code/Magento/Checkout/Controller/Cart/UpdateItemOptions.php
@@ -12,6 +12,7 @@
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\DataObject;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Filter\LocalizedToNormalized;
use Magento\Framework\Locale\ResolverInterface;
use Psr\Log\LoggerInterface;
@@ -37,7 +38,7 @@ public function execute()
}
try {
if (isset($params['qty'])) {
- $inputFilter = new \Zend_Filter_LocalizedToNormalized(
+ $inputFilter = new LocalizedToNormalized(
[
'locale' => $this->_objectManager->get(ResolverInterface::class)->getLocale(),
]
diff --git a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php
index 35dd55f051ea1..58f7cbe0ab104 100644
--- a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php
+++ b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php
@@ -130,6 +130,7 @@ private function updateItemQuantity(Item $item, float $qty)
{
if ($qty > 0) {
$item->clearMessage();
+ $item->setHasError(false);
$item->setQty($qty);
if ($item->getHasError()) {
diff --git a/app/code/Magento/Checkout/Controller/Sidebar/UpdateItemQty.php b/app/code/Magento/Checkout/Controller/Sidebar/UpdateItemQty.php
index 50fb1325171b1..f115ead419c6a 100644
--- a/app/code/Magento/Checkout/Controller/Sidebar/UpdateItemQty.php
+++ b/app/code/Magento/Checkout/Controller/Sidebar/UpdateItemQty.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Checkout\Controller\Sidebar;
use Magento\Checkout\Model\Cart\RequestQuantityProcessor;
@@ -16,6 +18,9 @@
use Psr\Log\LoggerInterface;
use Magento\Framework\App\Action\HttpPostActionInterface;
+/**
+ * Class used to update item quantity.
+ */
class UpdateItemQty extends Action implements HttpPostActionInterface
{
/**
@@ -68,6 +73,10 @@ public function execute()
{
$itemId = (int)$this->getRequest()->getParam('item_id');
$itemQty = (float)$this->getRequest()->getParam('item_qty') * 1;
+
+ if ($itemQty <= 0) {
+ return $this->jsonResponse(__('Invalid Item Quantity Requested.'));
+ }
$itemQty = $this->quantityProcessor->prepareQuantity($itemQty);
try {
diff --git a/app/code/Magento/Checkout/Model/AddressComparator.php b/app/code/Magento/Checkout/Model/AddressComparator.php
new file mode 100644
index 0000000000000..c285f15e4e40e
--- /dev/null
+++ b/app/code/Magento/Checkout/Model/AddressComparator.php
@@ -0,0 +1,79 @@
+serializer = $serializer;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isEqual(?AddressInterface $address1, ?AddressInterface $address2): bool
+ {
+ if ($address1 === null || $address2 === null) {
+ return false;
+ }
+
+ if ($address1->getCustomerAddressId() !== null &&
+ $address2->getCustomerAddressId() !== null
+ ) {
+ return ((int)$address1->getCustomerAddressId() ===
+ (int)$address2->getCustomerAddressId());
+ } else {
+ $addressKeys = array_intersect_key($address1->getData(), $address2->getData());
+ $removeKeys = ['address_type', 'region_code', 'save_in_address_book'];
+ $addressKeys = array_diff_key($addressKeys, array_flip($removeKeys));
+
+ $address1Data = array_intersect_key($address1->getData(), $addressKeys);
+ $address2Data = array_intersect_key($address2->getData(), $addressKeys);
+ $diff = $this->computeArrayDifference($address1Data, $address2Data);
+ return empty($diff);
+ }
+ }
+
+ /**
+ * Computing the difference of two arrays
+ *
+ * @param array $array1
+ * @param array $array2
+ * @return array
+ */
+ private function computeArrayDifference(array $array1, array $array2): array
+ {
+ return array_udiff_assoc(
+ $array1,
+ $array2,
+ function ($el1, $el2) {
+ if (is_object($el1) || is_array($el1)) {
+ $el1 = $this->serializer->serialize($el1);
+ }
+ if (is_object($el2) || is_array($el2)) {
+ $el2 = $this->serializer->serialize($el2);
+ }
+ return strcmp((string)$el1, (string)$el2);
+ }
+ );
+ }
+}
diff --git a/app/code/Magento/Checkout/Model/AddressComparatorInterface.php b/app/code/Magento/Checkout/Model/AddressComparatorInterface.php
new file mode 100644
index 0000000000000..2face53aa8243
--- /dev/null
+++ b/app/code/Magento/Checkout/Model/AddressComparatorInterface.php
@@ -0,0 +1,22 @@
+ 0) {
+ $item->clearMessage();
+ $item->setHasError(false);
$item->setQty($qty);
if ($item->getHasError()) {
@@ -707,6 +709,9 @@ public function getItemsQty()
*/
public function updateItem($itemId, $requestInfo = null, $updatingParams = null)
{
+ $product = null;
+ $productId = null;
+
try {
$item = $this->getQuote()->getItemById($itemId);
if (!$item) {
@@ -757,6 +762,7 @@ public function updateItem($itemId, $requestInfo = null, $updatingParams = null)
* Getter for RequestInfoFilter
*
* @deprecated 100.1.2
+ * @see MAGETWO-60073
* @return \Magento\Checkout\Model\Cart\RequestInfoFilterInterface
*/
private function getRequestInfoFilter()
diff --git a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php
index 9e31c4d77301e..dd4080bcc39dc 100644
--- a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php
+++ b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php
@@ -31,6 +31,7 @@
use Magento\Quote\Model\QuoteIdMaskFactory;
use Magento\Store\Model\ScopeInterface;
use Magento\Ui\Component\Form\Element\Multiline;
+use Magento\Framework\Escaper;
/**
* Default Config Provider for checkout
@@ -191,6 +192,11 @@ class DefaultConfigProvider implements ConfigProviderInterface
*/
private $configPostProcessor;
+ /**
+ * @var Escaper
+ */
+ private $escaper;
+
/**
* @param CheckoutHelper $checkoutHelper
* @param Session $checkoutSession
@@ -222,6 +228,7 @@ class DefaultConfigProvider implements ConfigProviderInterface
* @param AddressMetadataInterface $addressMetadata
* @param AttributeOptionManagementInterface $attributeOptionManager
* @param CustomerAddressDataProvider|null $customerAddressData
+ * @param Escaper|null $escaper
* @codeCoverageIgnore
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -255,7 +262,8 @@ public function __construct(
CaptchaConfigPostProcessorInterface $configPostProcessor,
AddressMetadataInterface $addressMetadata = null,
AttributeOptionManagementInterface $attributeOptionManager = null,
- CustomerAddressDataProvider $customerAddressData = null
+ CustomerAddressDataProvider $customerAddressData = null,
+ Escaper $escaper = null
) {
$this->checkoutHelper = $checkoutHelper;
$this->checkoutSession = $checkoutSession;
@@ -289,6 +297,7 @@ public function __construct(
$this->customerAddressData = $customerAddressData ?:
ObjectManager::getInstance()->get(CustomerAddressDataProvider::class);
$this->configPostProcessor = $configPostProcessor;
+ $this->escaper = $escaper ?? ObjectManager::getInstance()->get(Escaper::class);
}
/**
@@ -343,6 +352,7 @@ public function getConfig()
'shipping/shipping_policy/shipping_policy_content',
ScopeInterface::SCOPE_STORE
);
+ $policyContent = $this->escaper->escapeHtml($policyContent);
$output['shippingPolicy'] = [
'isEnabled' => $this->scopeConfig->isSetFlag(
'shipping/shipping_policy/enable_shipping_policy',
diff --git a/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php b/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php
index 461a12c87aeb3..027394497e82c 100644
--- a/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php
+++ b/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php
@@ -14,11 +14,13 @@
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Quote\Model\Quote;
+use Psr\Log\LoggerInterface as Logger;
/**
* Guest payment information management model.
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
class GuestPaymentInformationManagement implements \Magento\Checkout\Api\GuestPaymentInformationManagementInterface
{
@@ -54,7 +56,7 @@ class GuestPaymentInformationManagement implements \Magento\Checkout\Api\GuestPa
protected $cartRepository;
/**
- * @var \Psr\Log\LoggerInterface
+ * @var Logger
*/
private $logger;
@@ -73,6 +75,11 @@ class GuestPaymentInformationManagement implements \Magento\Checkout\Api\GuestPa
*/
private $saveRateLimitDisabled = false;
+ /**
+ * @var AddressComparatorInterface
+ */
+ private $addressComparator;
+
/**
* @param \Magento\Quote\Api\GuestBillingAddressManagementInterface $billingAddressManagement
* @param \Magento\Quote\Api\GuestPaymentMethodManagementInterface $paymentMethodManagement
@@ -80,8 +87,10 @@ class GuestPaymentInformationManagement implements \Magento\Checkout\Api\GuestPa
* @param \Magento\Checkout\Api\PaymentInformationManagementInterface $paymentInformationManagement
* @param \Magento\Quote\Model\QuoteIdMaskFactory $quoteIdMaskFactory
* @param CartRepositoryInterface $cartRepository
+ * @param Logger $logger
* @param PaymentProcessingRateLimiterInterface|null $paymentsRateLimiter
* @param PaymentSavingRateLimiterInterface|null $savingRateLimiter
+ * @param AddressComparatorInterface|null $addressComparator
* @codeCoverageIgnore
*/
public function __construct(
@@ -91,8 +100,10 @@ public function __construct(
\Magento\Checkout\Api\PaymentInformationManagementInterface $paymentInformationManagement,
\Magento\Quote\Model\QuoteIdMaskFactory $quoteIdMaskFactory,
CartRepositoryInterface $cartRepository,
+ Logger $logger,
?PaymentProcessingRateLimiterInterface $paymentsRateLimiter = null,
- ?PaymentSavingRateLimiterInterface $savingRateLimiter = null
+ ?PaymentSavingRateLimiterInterface $savingRateLimiter = null,
+ ?AddressComparatorInterface $addressComparator = null
) {
$this->billingAddressManagement = $billingAddressManagement;
$this->paymentMethodManagement = $paymentMethodManagement;
@@ -104,6 +115,9 @@ public function __construct(
?? ObjectManager::getInstance()->get(PaymentProcessingRateLimiterInterface::class);
$this->savingRateLimiter = $savingRateLimiter
?? ObjectManager::getInstance()->get(PaymentSavingRateLimiterInterface::class);
+ $this->addressComparator = $addressComparator
+ ?? ObjectManager::getInstance()->get(AddressComparatorInterface::class);
+ $this->logger = $logger;
}
/**
@@ -126,7 +140,7 @@ public function savePaymentInformationAndPlaceOrder(
try {
$orderId = $this->cartManagement->placeOrder($cartId);
} catch (\Magento\Framework\Exception\LocalizedException $e) {
- $this->getLogger()->critical(
+ $this->logger->critical(
'Placing an order with quote_id ' . $cartId . ' is failed: ' . $e->getMessage()
);
throw new CouldNotSaveException(
@@ -134,7 +148,7 @@ public function savePaymentInformationAndPlaceOrder(
$e
);
} catch (\Exception $e) {
- $this->getLogger()->critical($e);
+ $this->logger->critical($e);
throw new CouldNotSaveException(
__('An error occurred on the server. Please try to place the order again.'),
$e
@@ -165,7 +179,10 @@ public function savePaymentInformation(
$quoteIdMask = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id');
/** @var Quote $quote */
$quote = $this->cartRepository->getActive($quoteIdMask->getQuoteId());
-
+ $shippingAddress = $quote->getShippingAddress();
+ if ($this->addressComparator->isEqual($shippingAddress, $billingAddress)) {
+ $shippingAddress->setSameAsBilling(1);
+ }
if ($billingAddress) {
$billingAddress->setEmail($email);
$quote->removeAddress($quote->getBillingAddress()->getId());
@@ -176,7 +193,7 @@ public function savePaymentInformation(
}
$this->limitShippingCarrier($quote);
- if (!(int)$quote->getItemsQty()) {
+ if (!(float)$quote->getItemsQty()) {
throw new CouldNotSaveException(__('Some of the products are disabled.'));
}
@@ -193,20 +210,6 @@ public function getPaymentInformation($cartId)
return $this->paymentInformationManagement->getPaymentInformation($quoteIdMask->getQuoteId());
}
- /**
- * Get logger instance
- *
- * @return \Psr\Log\LoggerInterface
- * @deprecated 100.1.8
- */
- private function getLogger()
- {
- if (!$this->logger) {
- $this->logger = \Magento\Framework\App\ObjectManager::getInstance()->get(\Psr\Log\LoggerInterface::class);
- }
- return $this->logger;
- }
-
/**
* Limits shipping rates request by carrier from shipping address.
*
diff --git a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php
index 1aad8a78bda55..41867d3c83500 100644
--- a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php
+++ b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php
@@ -9,9 +9,13 @@
use Magento\Checkout\Api\Exception\PaymentProcessingRateLimitExceededException;
use Magento\Checkout\Api\PaymentProcessingRateLimiterInterface;
use Magento\Checkout\Api\PaymentSavingRateLimiterInterface;
+use Magento\Customer\Api\AddressRepositoryInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\CouldNotSaveException;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Quote\Api\CartRepositoryInterface;
+use Magento\Quote\Model\Quote;
+use Psr\Log\LoggerInterface;
/**
* Payment information management service.
@@ -23,6 +27,7 @@ class PaymentInformationManagement implements \Magento\Checkout\Api\PaymentInfor
/**
* @var \Magento\Quote\Api\BillingAddressManagementInterface
* @deprecated 100.1.0 This call was substituted to eliminate extra quote::save call
+ * @see not in use anymore
*/
protected $billingAddressManagement;
@@ -46,11 +51,6 @@ class PaymentInformationManagement implements \Magento\Checkout\Api\PaymentInfor
*/
protected $cartTotalsRepository;
- /**
- * @var \Psr\Log\LoggerInterface
- */
- private $logger;
-
/**
* @var CartRepositoryInterface
*/
@@ -71,6 +71,21 @@ class PaymentInformationManagement implements \Magento\Checkout\Api\PaymentInfor
*/
private $saveRateLimiterDisabled = false;
+ /**
+ * @var AddressRepositoryInterface
+ */
+ private $addressRepository;
+
+ /**
+ * @var AddressComparatorInterface
+ */
+ private $addressComparator;
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
/**
* @param \Magento\Quote\Api\BillingAddressManagementInterface $billingAddressManagement
* @param \Magento\Quote\Api\PaymentMethodManagementInterface $paymentMethodManagement
@@ -80,7 +95,11 @@ class PaymentInformationManagement implements \Magento\Checkout\Api\PaymentInfor
* @param PaymentProcessingRateLimiterInterface|null $paymentRateLimiter
* @param PaymentSavingRateLimiterInterface|null $saveRateLimiter
* @param CartRepositoryInterface|null $cartRepository
+ * @param AddressRepositoryInterface|null $addressRepository
+ * @param AddressComparatorInterface|null $addressComparator
+ * @param LoggerInterface|null $logger
* @codeCoverageIgnore
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
\Magento\Quote\Api\BillingAddressManagementInterface $billingAddressManagement,
@@ -90,7 +109,10 @@ public function __construct(
\Magento\Quote\Api\CartTotalRepositoryInterface $cartTotalsRepository,
?PaymentProcessingRateLimiterInterface $paymentRateLimiter = null,
?PaymentSavingRateLimiterInterface $saveRateLimiter = null,
- ?CartRepositoryInterface $cartRepository = null
+ ?CartRepositoryInterface $cartRepository = null,
+ ?AddressRepositoryInterface $addressRepository = null,
+ ?AddressComparatorInterface $addressComparator = null,
+ ?LoggerInterface $logger = null
) {
$this->billingAddressManagement = $billingAddressManagement;
$this->paymentMethodManagement = $paymentMethodManagement;
@@ -103,6 +125,11 @@ public function __construct(
?? ObjectManager::getInstance()->get(PaymentSavingRateLimiterInterface::class);
$this->cartRepository = $cartRepository
?? ObjectManager::getInstance()->get(CartRepositoryInterface::class);
+ $this->addressRepository = $addressRepository
+ ?? ObjectManager::getInstance()->get(AddressRepositoryInterface::class);
+ $this->addressComparator = $addressComparator
+ ?? ObjectManager::getInstance()->get(AddressComparatorInterface::class);
+ $this->logger = $logger ?? ObjectManager::getInstance()->get(LoggerInterface::class);
}
/**
@@ -123,8 +150,8 @@ public function savePaymentInformationAndPlaceOrder(
}
try {
$orderId = $this->cartManagement->placeOrder($cartId);
- } catch (\Magento\Framework\Exception\LocalizedException $e) {
- $this->getLogger()->critical(
+ } catch (LocalizedException $e) {
+ $this->logger->critical(
'Placing an order with quote_id ' . $cartId . ' is failed: ' . $e->getMessage()
);
throw new CouldNotSaveException(
@@ -132,7 +159,7 @@ public function savePaymentInformationAndPlaceOrder(
$e
);
} catch (\Exception $e) {
- $this->getLogger()->critical($e);
+ $this->logger->critical($e);
throw new CouldNotSaveException(
__('A server error stopped your order from being placed. Please try to place your order again.'),
$e
@@ -143,6 +170,8 @@ public function savePaymentInformationAndPlaceOrder(
/**
* @inheritdoc
+ *
+ * @throws LocalizedException
*/
public function savePaymentInformation(
$cartId,
@@ -170,12 +199,8 @@ public function savePaymentInformation(
$quote->removeAddress($quote->getBillingAddress()->getId());
$quote->setBillingAddress($billingAddress);
$quote->setDataChanges(true);
- $shippingAddress = $quote->getShippingAddress();
- if ($shippingAddress && $shippingAddress->getShippingMethod()) {
- $shippingRate = $shippingAddress->getShippingRateByCode($shippingAddress->getShippingMethod());
- if ($shippingRate) {
- $shippingAddress->setLimitCarrier($shippingRate->getCarrier());
- }
+ if ($quote->getShippingAddress()) {
+ $this->processShippingAddress($quote);
}
}
$this->paymentMethodManagement->set($cartId, $paymentMethod);
@@ -195,16 +220,44 @@ public function getPaymentInformation($cartId)
}
/**
- * Get logger instance
+ * Processes shipping address.
*
- * @return \Psr\Log\LoggerInterface
- * @deprecated 100.1.8
+ * @param Quote $quote
+ * @return void
+ * @throws LocalizedException
*/
- private function getLogger()
+ private function processShippingAddress(Quote $quote): void
{
- if (!$this->logger) {
- $this->logger = ObjectManager::getInstance()->get(\Psr\Log\LoggerInterface::class);
+ $shippingAddress = $quote->getShippingAddress();
+ $billingAddress = $quote->getBillingAddress();
+ if ($shippingAddress->getShippingMethod()) {
+ $shippingRate = $shippingAddress->getShippingRateByCode($shippingAddress->getShippingMethod());
+ if ($shippingRate) {
+ $shippingAddress->setLimitCarrier($shippingRate->getCarrier());
+ }
+ }
+ if ($this->addressComparator->isEqual($shippingAddress, $billingAddress)) {
+ $shippingAddress->setSameAsBilling(1);
+ }
+ // Save new address in the customer address book and set it id for billing and shipping quote addresses.
+ if ($shippingAddress->getSameAsBilling() && $shippingAddress->getSaveInAddressBook()) {
+ $shippingAddressData = $shippingAddress->exportCustomerAddress();
+ $customer = $quote->getCustomer();
+ $hasDefaultBilling = (bool)$customer->getDefaultBilling();
+ $hasDefaultShipping = (bool)$customer->getDefaultShipping();
+ if (!$hasDefaultShipping) {
+ //Make provided address as default shipping address
+ $shippingAddressData->setIsDefaultShipping(true);
+ if (!$hasDefaultBilling && !$billingAddress->getSaveInAddressBook()) {
+ $shippingAddressData->setIsDefaultBilling(true);
+ }
+ }
+ $shippingAddressData->setCustomerId($quote->getCustomerId());
+ $this->addressRepository->save($shippingAddressData);
+ $quote->addCustomerAddress($shippingAddressData);
+ $shippingAddress->setCustomerAddressData($shippingAddressData);
+ $shippingAddress->setCustomerAddressId($shippingAddressData->getId());
+ $billingAddress->setCustomerAddressId($shippingAddressData->getId());
}
- return $this->logger;
}
}
diff --git a/app/code/Magento/Checkout/Model/Sidebar.php b/app/code/Magento/Checkout/Model/Sidebar.php
index 17cf40a73490c..b4d1403c131ad 100644
--- a/app/code/Magento/Checkout/Model/Sidebar.php
+++ b/app/code/Magento/Checkout/Model/Sidebar.php
@@ -8,12 +8,14 @@
use Magento\Checkout\Helper\Data as HelperData;
use Magento\Checkout\Model\Cart;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Filter\LocalizedToNormalized;
use Magento\Framework\Locale\ResolverInterface;
use Magento\Quote\Api\Data\CartItemInterface;
use Magento\Quote\Model\Quote\Address\Total;
/**
* @deprecated 100.1.0
+ * @see we don't recommend this approach anymore
*/
class Sidebar
{
@@ -127,7 +129,7 @@ public function updateQuoteItem($itemId, $itemQty)
protected function normalize($itemQty)
{
if ($itemQty) {
- $filter = new \Zend_Filter_LocalizedToNormalized(
+ $filter = new LocalizedToNormalized(
['locale' => $this->resolver->getLocale()]
);
return $filter->filter((string)$itemQty);
diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniCartEmptyActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniCartEmptyActionGroup.xml
index 649421a53040b..2119e5c43f7bb 100644
--- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniCartEmptyActionGroup.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniCartEmptyActionGroup.xml
@@ -13,8 +13,9 @@
Validates that the provided Product Count appears in the Storefront Header next to the Shopping Cart icon. Clicks on the Mini Shopping Cart icon. Validates that the 'No Items' message is present and correct in the Storefront Mini Shopping Cart.
+
-
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CashOnDeliverySpecificCountryActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CashOnDeliverySpecificCountryActionGroup.xml
new file mode 100644
index 0000000000000..01e2a6ed46f42
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CashOnDeliverySpecificCountryActionGroup.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ Enable/Disable Cash On Delivery payment method and allow for specific Country.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillingShippingWithMultipleStreetLinesSectionActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillingShippingWithMultipleStreetLinesSectionActionGroup.xml
new file mode 100644
index 0000000000000..441e3571d0f55
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillingShippingWithMultipleStreetLinesSectionActionGroup.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+ Fills in the provided Customer/Address (Including Region) details on the Storefront Checkout page under the 'Shipping Address' section. Selects the provided Shipping Method. Clicks on Next. Validates that the URL is present and correct.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontGuestCheckoutProceedActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontGuestCheckoutProceedActionGroup.xml
new file mode 100644
index 0000000000000..79c43b03e8f65
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontGuestCheckoutProceedActionGroup.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ Clicks on proceed to Checkout Shipping step. fill the address under payment method
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartMessageSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartMessageSection.xml
index 0c7f200e2b5eb..c9f89e2e0342a 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartMessageSection.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartMessageSection.xml
@@ -13,5 +13,6 @@
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml
index e0b2b7c2e6c13..ca279bfe8e546 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml
@@ -39,5 +39,8 @@
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml
index 365f5fcd14b23..9f6dba3081b7c 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml
@@ -22,6 +22,9 @@
+
+
+
@@ -52,6 +55,7 @@
+
@@ -65,5 +69,8 @@
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml
index 4b8b1745c16df..082eaf38122e2 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml
@@ -23,6 +23,8 @@
+
+
@@ -46,5 +48,8 @@
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml
index 0b1fbd5ca0e5a..3482a45b6fa91 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml
@@ -46,5 +46,7 @@
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductCartGiftOptionSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductCartGiftOptionSection.xml
index 681d5ddb550d3..6fa00bd3dc928 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductCartGiftOptionSection.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductCartGiftOptionSection.xml
@@ -15,5 +15,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
-
+
+
@@ -37,7 +38,6 @@
-
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/AssertSuccessMessageAppearsAfterAddingProductToCartThatContainsOutOfStockProductTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/AssertSuccessMessageAppearsAfterAddingProductToCartThatContainsOutOfStockProductTest.xml
new file mode 100644
index 0000000000000..979976caf78ae
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/AssertSuccessMessageAppearsAfterAddingProductToCartThatContainsOutOfStockProductTest.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/CustomerOrderSimpleProductTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/CustomerOrderSimpleProductTest.xml
index 1cf2aaed81f84..555f7768b1c4a 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/CustomerOrderSimpleProductTest.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/CustomerOrderSimpleProductTest.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DefaultBillingAddressShouldBeCheckedOnPaymentPageTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DefaultBillingAddressShouldBeCheckedOnPaymentPageTest.xml
index d46ebfd63203a..64980c74dc0c2 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/DefaultBillingAddressShouldBeCheckedOnPaymentPageTest.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/DefaultBillingAddressShouldBeCheckedOnPaymentPageTest.xml
@@ -20,6 +20,8 @@
+
+
@@ -31,6 +33,8 @@
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DisplayPriceForShippingRateOnShoppingCartPageWithSpecificTaxDisplaySettingsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DisplayPriceForShippingRateOnShoppingCartPageWithSpecificTaxDisplaySettingsTest.xml
new file mode 100644
index 0000000000000..34f82268b922a
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/DisplayPriceForShippingRateOnShoppingCartPageWithSpecificTaxDisplaySettingsTest.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutCancelEditingBillingAddress.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutCancelEditingBillingAddress.xml
index 6c72d794deeb7..5dbe287d88d3a 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutCancelEditingBillingAddress.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutCancelEditingBillingAddress.xml
@@ -18,10 +18,13 @@
+
160
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontGuestCustomerProductsMerged.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontGuestCustomerProductsMerged.xml
new file mode 100644
index 0000000000000..a9d34db16b506
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontGuestCustomerProductsMerged.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCashOnDeliveryPaymentForSpecificCountryTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCashOnDeliveryPaymentForSpecificCountryTest.xml
new file mode 100644
index 0000000000000..024e1221d95e6
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCashOnDeliveryPaymentForSpecificCountryTest.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCustomerInfoOnOrderPageCreatedByGuestTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCustomerInfoOnOrderPageCreatedByGuestTest.xml
index fa75a280e69f1..e769d9d37286d 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCustomerInfoOnOrderPageCreatedByGuestTest.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCustomerInfoOnOrderPageCreatedByGuestTest.xml
@@ -21,6 +21,7 @@
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest/StorefrontCustomerCheckoutTestWithRestrictedCountriesForPaymentTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest/StorefrontCustomerCheckoutTestWithRestrictedCountriesForPaymentTest.xml
index 5fd34081c7a8b..f12dd6fb34827 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest/StorefrontCustomerCheckoutTestWithRestrictedCountriesForPaymentTest.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest/StorefrontCustomerCheckoutTestWithRestrictedCountriesForPaymentTest.xml
@@ -22,6 +22,7 @@
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutDataPersistTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutDataPersistTest.xml
index a43bbef57d0c8..45eb3443e4205 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutDataPersistTest.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutDataPersistTest.xml
@@ -19,6 +19,7 @@
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutAddNewAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutAddNewAddressTest.xml
new file mode 100644
index 0000000000000..cf21af3daed17
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutAddNewAddressTest.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutWithSidebarDisabledTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutWithSidebarDisabledTest.xml
index 74ad9985e72f2..4a4d7bcabfb03 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutWithSidebarDisabledTest.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutWithSidebarDisabledTest.xml
@@ -18,6 +18,7 @@
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontModalWindowForSignInIsShownIfGuestCheckoutIsDisabledTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontModalWindowForSignInIsShownIfGuestCheckoutIsDisabledTest.xml
new file mode 100644
index 0000000000000..6abc2b92178ea
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontModalWindowForSignInIsShownIfGuestCheckoutIsDisabledTest.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityAcceptLowerThanStockQuantityAfterChangingStockInBackendTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityAcceptLowerThanStockQuantityAfterChangingStockInBackendTest.xml
new file mode 100644
index 0000000000000..4e42963054662
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductQuantityAcceptLowerThanStockQuantityAfterChangingStockInBackendTest.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10.00
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml
index 71e884cc36fb4..87eba009f5e56 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontRefreshPageDuringGuestCheckoutTest.xml
@@ -19,6 +19,7 @@
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml
index 7be123d9cb71f..a17ce096a95f9 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUSCustomerCheckoutWithCouponAndBankTransferPaymentMethodTest.xml
@@ -18,6 +18,7 @@
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $grabProductQtyInMinicart
+ 0
+
+
+
+
+
+
+
+
+ $grabProductQtyInCart
+ 0
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/VerifyStateOptionApplicableForCheckoutFlowTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/VerifyStateOptionApplicableForCheckoutFlowTest.xml
new file mode 100644
index 0000000000000..6016893ed3e28
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/VerifyStateOptionApplicableForCheckoutFlowTest.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Unit/Controller/Sidebar/UpdateItemQtyTest.php b/app/code/Magento/Checkout/Test/Unit/Controller/Sidebar/UpdateItemQtyTest.php
index 1ad9a8220c82d..959cebea17471 100644
--- a/app/code/Magento/Checkout/Test/Unit/Controller/Sidebar/UpdateItemQtyTest.php
+++ b/app/code/Magento/Checkout/Test/Unit/Controller/Sidebar/UpdateItemQtyTest.php
@@ -19,6 +19,9 @@
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
+/**
+ * Class used to execute test cases for update item quantity
+ */
class UpdateItemQtyTest extends TestCase
{
/**
@@ -248,43 +251,32 @@ public function testExecuteWithException(): void
/**
* @return void
*/
- public function testExecuteWithWrongRequestParams(): void
+ public function testExecuteWithInvalidItemQty(): void
{
+ $error = [
+ 'success' => false,
+ 'error_message' => 'Invalid Item Quantity Requested.'
+ ];
+ $jsonResult = json_encode($error);
$this->requestMock
->method('getParam')
- ->withConsecutive(['item_id'], ['item_qty'])
- ->willReturnOnConsecutiveCalls(0, 'error');
-
- $this->sidebarMock->expects($this->once())
- ->method('checkQuoteItem')
- ->with(0)
- ->willThrowException(new LocalizedException(__('Error!')));
+ ->withConsecutive(['item_id', null], ['item_qty', null])
+ ->willReturnOnConsecutiveCalls('1', '{{7+2}}');
$this->sidebarMock->expects($this->once())
->method('getResponseData')
- ->with('Error!')
- ->willReturn(
- [
- 'success' => false,
- 'error_message' => 'Error!'
- ]
- );
+ ->with('Invalid Item Quantity Requested.')
+ ->willReturn($error);
$this->jsonHelperMock->expects($this->once())
->method('jsonEncode')
- ->with(
- [
- 'success' => false,
- 'error_message' => 'Error!'
- ]
- )
- ->willReturn('json encoded');
+ ->with($error)
+ ->willReturn($jsonResult);
$this->responseMock->expects($this->once())
->method('representJson')
- ->with('json encoded')
- ->willReturn('json represented');
+ ->willReturn($jsonResult);
- $this->assertEquals('json represented', $this->updateItemQty->execute());
+ $this->assertEquals($jsonResult, $this->updateItemQty->execute());
}
}
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/Checkout/etc/di.xml b/app/code/Magento/Checkout/etc/di.xml
index 9a23e03438591..280944dc4090c 100644
--- a/app/code/Magento/Checkout/etc/di.xml
+++ b/app/code/Magento/Checkout/etc/di.xml
@@ -28,6 +28,7 @@
+
diff --git a/app/code/Magento/Checkout/i18n/en_US.csv b/app/code/Magento/Checkout/i18n/en_US.csv
index 01f3bb9a426d5..aa3cf0748cb0c 100644
--- a/app/code/Magento/Checkout/i18n/en_US.csv
+++ b/app/code/Magento/Checkout/i18n/en_US.csv
@@ -186,6 +186,7 @@ Payment,Payment
"Show Cross-sell Items in the Shopping Cart","Show Cross-sell Items in the Shopping Cart"
"You added %1 to your shopping cart .","You added %1 to your shopping cart ."
"The shipping method is missing. Select the shipping method and try again.","The shipping method is missing. Select the shipping method and try again."
+"Invalid Item Quantity Requested.","Invalid Item Quantity Requested."
Are you sure you want to leave the page?,Are you sure you want to leave the page?
Changes you made to the cart will not be saved.,Changes you made to the cart will not be saved.
Leave,Leave
diff --git a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml
index 78b3b4bbdd6e8..b20b4d02706f3 100644
--- a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml
+++ b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml
@@ -95,6 +95,7 @@
- #opc-new-shipping-address
-
- popup
+ - new-shipping-address-modal
- true
- true
- Shipping Address
diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml
index 66f0917fb90c3..9a8d1abd7260d 100644
--- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml
+++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml
@@ -104,6 +104,7 @@ $canApplyMsrp = $helper->isShowBeforeOrderConfirm($product) && $helper->isMinima
data-cart-item-id="= $block->escapeHtmlAttr($_item->getSku()) ?>"
value="= $block->escapeHtmlAttr($block->getQty()) ?>"
type="number"
+ min="0"
size="4"
step="any"
title="= $block->escapeHtmlAttr(__('Qty')) ?>"
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js b/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js
index 48e79f1d00833..0d4faed0c7384 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js
@@ -154,7 +154,12 @@ define([
onError: function (response) {
var that = this,
elm,
+ responseData = [];
+
+ try {
responseData = JSON.parse(response['error_message']);
+ } catch (error) {
+ }
if (response['error_message']) {
try {
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/cart/totals-processor/default.js b/app/code/Magento/Checkout/view/frontend/web/js/model/cart/totals-processor/default.js
index d8fea6ea329e8..2039217fc4a11 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/model/cart/totals-processor/default.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/model/cart/totals-processor/default.js
@@ -77,16 +77,6 @@ define([
* @param {Object} address
*/
estimateTotals: function (address) {
- var data = {
- shippingMethodCode: null,
- shippingCarrierCode: null
- };
-
- if (quote.shippingMethod() && quote.shippingMethod()['method_code']) {
- data.shippingMethodCode = quote.shippingMethod()['method_code'];
- data.shippingCarrierCode = quote.shippingMethod()['carrier_code'];
- }
-
return loadFromServer(address);
}
};
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js b/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js
index eb4b45d83817c..3748212da918e 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js
@@ -40,7 +40,7 @@ define([
regionCode: addressData.region ? addressData.region['region_code'] : null,
region: addressData.region ? addressData.region.region : null,
customerId: addressData['customer_id'] || addressData.customerId,
- street: addressData.street ? _.compact(addressData.street) : addressData.street,
+ street: addressData.street,
company: addressData.company,
telephone: addressData.telephone,
fax: addressData.fax,
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js b/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js
index 36403d5e22595..6e72e38b05e8e 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js
@@ -18,7 +18,7 @@ define(
'use strict';
return function (serviceUrl, payload, messageContainer) {
- var headers = {};
+ var headers = {}, redirectURL = '';
fullScreenLoader.startLoader();
_.each(hooks.requestModifiers, function (modifier) {
@@ -30,6 +30,13 @@ define(
).fail(
function (response) {
errorProcessor.process(response, messageContainer);
+ redirectURL = response.getResponseHeader('errorRedirectAction');
+
+ if (redirectURL) {
+ setTimeout(function () {
+ errorProcessor.redirectTo(redirectURL);
+ }, 3000);
+ }
}
).done(
function (response) {
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js
index 6412fd726ed60..68c2548d6565f 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js
@@ -42,7 +42,7 @@ function (
'use strict';
var lastSelectedBillingAddress = null,
- addressUpadated = false,
+ addressUpdated = false,
addressEdited = false,
countryData = customerData.get('directory-data'),
addressOptions = addressList().filter(function (address) {
@@ -125,8 +125,7 @@ function (
useShippingAddress: function () {
if (this.isAddressSameAsShipping()) {
selectBillingAddress(quote.shippingAddress());
-
- this.updateAddresses();
+ this.updateAddresses(true);
this.isAddressDetailsVisible(true);
} else {
lastSelectedBillingAddress = quote.billingAddress();
@@ -144,7 +143,7 @@ function (
updateAddress: function () {
var addressData, newBillingAddress;
- addressUpadated = true;
+ addressUpdated = true;
if (this.selectedAddress() && !this.isAddressFormVisible()) {
selectBillingAddress(this.selectedAddress());
@@ -171,15 +170,14 @@ function (
checkoutData.setNewCustomerBillingAddress(addressData);
}
}
- setBillingAddressAction(globalMessageList);
- this.updateAddresses();
+ this.updateAddresses(true);
},
/**
* Edit address action
*/
editAddress: function () {
- addressUpadated = false;
+ addressUpdated = false;
addressEdited = true;
lastSelectedBillingAddress = quote.billingAddress();
quote.billingAddress(null);
@@ -190,7 +188,7 @@ function (
* Cancel address edit action
*/
cancelAddressEdit: function () {
- addressUpadated = true;
+ addressUpdated = true;
this.restoreBillingAddress();
if (quote.billingAddress()) {
@@ -215,7 +213,7 @@ function (
* Check if Billing Address Changes should be canceled
*/
needCancelBillingAddressChanges: function () {
- if (addressEdited && !addressUpadated) {
+ if (addressEdited && !addressUpdated) {
this.cancelAddressEdit();
}
},
@@ -244,11 +242,15 @@ function (
/**
* Trigger action to update shipping and billing addresses
+ *
+ * @param {Boolean} force
*/
- updateAddresses: function () {
- if (window.checkoutConfig.reloadOnBillingAddress ||
- !window.checkoutConfig.displayBillingOnPaymentMethod
- ) {
+ updateAddresses: function (force) {
+ force = !(typeof force === 'undefined' || force !== true);
+
+ if (force
+ || window.checkoutConfig.reloadOnBillingAddress
+ || !window.checkoutConfig.displayBillingOnPaymentMethod) {
setBillingAddressAction(globalMessageList);
}
},
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/checkout/placeOrderCaptcha.js b/app/code/Magento/Checkout/view/frontend/web/js/view/checkout/placeOrderCaptcha.js
index d0e27ad8e0abb..0dc0e0e9eadba 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/view/checkout/placeOrderCaptcha.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/view/checkout/placeOrderCaptcha.js
@@ -20,7 +20,6 @@ function (defaultCaptcha, captchaList, _, placeOrderHooks) {
this._super();
currentCaptcha = captchaList.getCaptchaByFormId(this.formId);
-
if (currentCaptcha != null) {
currentCaptcha.setIsVisible(true);
this.setCurrentCaptcha(currentCaptcha);
@@ -29,9 +28,11 @@ function (defaultCaptcha, captchaList, _, placeOrderHooks) {
headers['X-Captcha'] = self.captchaValue()();
}
});
- placeOrderHooks.afterRequestListeners.push(function () {
- self.refresh();
- });
+ if (self.isRequired()) {
+ placeOrderHooks.afterRequestListeners.push(function () {
+ self.refresh();
+ });
+ }
}
}
});
diff --git a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html
index 15bc99587972e..9cdd84f5915c0 100644
--- a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html
+++ b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html
@@ -11,7 +11,7 @@
-
+
,
diff --git a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html
index b74896972b28c..96e16b0c3f836 100644
--- a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html
+++ b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html
@@ -81,8 +81,9 @@
'data-cart-item-id': product_sku
}, value: qty"
type="number"
+ min="0"
size="4"
- class="item-qty cart-item-qty"/>
+ class="item-qty cart-item-qty">
-
+
-
+
-
diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html
index 0602c3f39c897..80a2e63cdb255 100644
--- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html
+++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html
@@ -10,7 +10,7 @@
-
+
,
diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html
index 1ffaad7a839b2..1c4ab02b9b03d 100644
--- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html
+++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html
@@ -10,7 +10,7 @@
-
+
,
diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateDisabledTextTermEntityTest.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateDisabledTextTermEntityTest.xml
index 3c0c171fdfe9e..e74235dba19db 100644
--- a/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateDisabledTextTermEntityTest.xml
+++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateDisabledTextTermEntityTest.xml
@@ -20,8 +20,8 @@
+
-
diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateEnabledTextTermOnMultishippingEntityTest.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateEnabledTextTermOnMultishippingEntityTest.xml
index c60ef95c8edce..3eb1e9dd02c9d 100644
--- a/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateEnabledTextTermOnMultishippingEntityTest.xml
+++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminCreateEnabledTextTermOnMultishippingEntityTest.xml
@@ -20,6 +20,8 @@
+
+
@@ -30,6 +32,8 @@
+
+
diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminDeleteActiveTextTermEntityTest.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminDeleteActiveTextTermEntityTest.xml
index 1e17dcc675573..175d5eb621501 100644
--- a/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminDeleteActiveTextTermEntityTest.xml
+++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminDeleteActiveTextTermEntityTest.xml
@@ -20,6 +20,7 @@
+
@@ -30,6 +31,8 @@
+
+
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/Block/Page.php b/app/code/Magento/Cms/Block/Page.php
index 5407940a7ffdb..fad2207ab18e4 100644
--- a/app/code/Magento/Cms/Block/Page.php
+++ b/app/code/Magento/Cms/Block/Page.php
@@ -135,7 +135,7 @@ protected function _addBreadcrumbs(\Magento\Cms\Model\Page $page)
'web/default/cms_no_route',
ScopeInterface::SCOPE_STORE
);
- $noRouteDelimiterPosition = strrpos($noRouteIdentifier, '|');
+ $noRouteDelimiterPosition = $noRouteIdentifier === null ? false : strrpos($noRouteIdentifier, '|');
if ($noRouteDelimiterPosition) {
$noRouteIdentifier = substr($noRouteIdentifier, 0, $noRouteDelimiterPosition);
}
diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/PostDataProcessor.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/PostDataProcessor.php
index d104b11b2dd5c..63394cae1d280 100644
--- a/app/code/Magento/Cms/Controller/Adminhtml/Page/PostDataProcessor.php
+++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/PostDataProcessor.php
@@ -11,6 +11,7 @@
use Magento\Framework\Config\Dom\ValidationException;
use Magento\Framework\Config\Dom\ValidationSchemaException;
use Magento\Cms\Model\Page\CustomLayout\CustomLayoutValidator;
+use Magento\Framework\Filter\FilterInput;
/**
* Controller helper for user input.
@@ -81,15 +82,16 @@ public function filter($data)
}
}
- return (new \Zend_Filter_Input($filterRules, [], $data))->getUnescaped();
+ return (new FilterInput($filterRules, [], $data))->getUnescaped();
}
/**
* Validate post data
*
* @param array $data
- * @return bool Return FALSE if some item is invalid
+ * @return bool Return FALSE if some item is invalid
* @deprecated 103.0.2
+ * @see no alternatives
*/
public function validate($data)
{
@@ -159,9 +161,7 @@ private function validateData($data, $layoutXmlValidator)
if (!$this->customLayoutValidator->validate($data)) {
return false;
}
- } catch (ValidationException $e) {
- return false;
- } catch (ValidationSchemaException $e) {
+ } catch (ValidationException | ValidationSchemaException $e) {
return false;
} catch (\Exception $e) {
$this->messageManager->addExceptionMessage($e);
diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCreateNewWidgetsOfCatalogCategoryLinkActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCreateNewWidgetsOfCatalogCategoryLinkActionGroup.xml
new file mode 100644
index 0000000000000..d53edc0ac7fd0
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCreateNewWidgetsOfCatalogCategoryLinkActionGroup.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+ Open create new Widgets of Catalog Category Link Type.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCreateNewWidgetsOfCatalogProductLinkActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCreateNewWidgetsOfCatalogProductLinkActionGroup.xml
new file mode 100644
index 0000000000000..4f5c1f998bcba
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCreateNewWidgetsOfCatalogProductLinkActionGroup.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+ Open create new Widgets.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminUpdateWidgetsOfCatalogCategoryLinkActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminUpdateWidgetsOfCatalogCategoryLinkActionGroup.xml
new file mode 100644
index 0000000000000..2e8353df9e46f
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminUpdateWidgetsOfCatalogCategoryLinkActionGroup.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ Update Widgets.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminUpdateWidgetsOfCatalogProductLinkActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminUpdateWidgetsOfCatalogProductLinkActionGroup.xml
new file mode 100644
index 0000000000000..84f01e3c3f88d
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminUpdateWidgetsOfCatalogProductLinkActionGroup.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ Update Widgets.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertStorefrontCategoryDetailPageNameAndUrlActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertStorefrontCategoryDetailPageNameAndUrlActionGroup.xml
new file mode 100644
index 0000000000000..a15efe506c99d
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertStorefrontCategoryDetailPageNameAndUrlActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ Validates that the Category name and Url are correct.
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Cms/Test/Mftf/Data/AdminMenuData.xml
index 3e227df56c909..a1224332aceab 100644
--- a/app/code/Magento/Cms/Test/Mftf/Data/AdminMenuData.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Data/AdminMenuData.xml
@@ -18,6 +18,13 @@
Pages
magento-cms-cms-page
+
+
+ Widgets
+ Widgets
+ magento-widget-cms-widget-instance
+
+
Blocks
Blocks
diff --git a/app/code/Magento/Cms/Test/Mftf/Page/CmsNewWidgetsPage.xml b/app/code/Magento/Cms/Test/Mftf/Page/CmsNewWidgetsPage.xml
new file mode 100644
index 0000000000000..ac4b1ae2d3631
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/Page/CmsNewWidgetsPage.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontHeaderSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/StorefrontHeaderSection.xml
index 7b47c6b49db7b..21472ddff5df3 100644
--- a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontHeaderSection.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Section/StorefrontHeaderSection.xml
@@ -9,6 +9,8 @@
diff --git a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/CmsNewWidgetOptionsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/CmsNewWidgetOptionsSection.xml
new file mode 100644
index 0000000000000..bf9f9f67d5c92
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/CmsNewWidgetOptionsSection.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/CmsNewWidgetStoreforntPropertiesSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/CmsNewWidgetStoreforntPropertiesSection.xml
new file mode 100644
index 0000000000000..6d8f8de9190ab
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/CmsNewWidgetStoreforntPropertiesSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/CmsNewWidgetUpdateLayoutSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/CmsNewWidgetUpdateLayoutSection.xml
new file mode 100644
index 0000000000000..5f311ec89eaae
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/CmsNewWidgetUpdateLayoutSection.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Section/AdminCatalogSearchConfigurationSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/CmsNewWidgetsSettingSection.xml
similarity index 51%
rename from app/code/Magento/Elasticsearch6/Test/Mftf/Section/AdminCatalogSearchConfigurationSection.xml
rename to app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/CmsNewWidgetsSettingSection.xml
index 339cb736445b6..954de11fd141f 100644
--- a/app/code/Magento/Elasticsearch6/Test/Mftf/Section/AdminCatalogSearchConfigurationSection.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection/CmsNewWidgetsSettingSection.xml
@@ -8,8 +8,10 @@
-
diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddUpdateDeleteWidgetOfTypeCatalogCategoryLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddUpdateDeleteWidgetOfTypeCatalogCategoryLinkTypeTest.xml
new file mode 100644
index 0000000000000..a63b3abc9f498
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddUpdateDeleteWidgetOfTypeCatalogCategoryLinkTypeTest.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddUpdateDeleteWidgetOfTypeCatalogProductLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddUpdateDeleteWidgetOfTypeCatalogProductLinkTypeTest.xml
new file mode 100644
index 0000000000000..7001acdea89b3
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddUpdateDeleteWidgetOfTypeCatalogProductLinkTypeTest.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/Observer/ProcessUrlRewriteSavingObserver.php b/app/code/Magento/CmsUrlRewrite/Observer/ProcessUrlRewriteSavingObserver.php
index 39ece93bc83b8..fc576ffb38e26 100644
--- a/app/code/Magento/CmsUrlRewrite/Observer/ProcessUrlRewriteSavingObserver.php
+++ b/app/code/Magento/CmsUrlRewrite/Observer/ProcessUrlRewriteSavingObserver.php
@@ -44,7 +44,10 @@ public function execute(EventObserver $observer)
/** @var $cmsPage \Magento\Cms\Model\Page */
$cmsPage = $observer->getEvent()->getObject();
- if ($cmsPage->dataHasChangedFor('identifier') || $cmsPage->dataHasChangedFor('store_id')) {
+ if ($cmsPage->dataHasChangedFor('identifier')
+ || $cmsPage->dataHasChangedFor('store_id')
+ || $cmsPage->getData('rewrites_update_force')
+ ) {
$urls = $this->cmsPageUrlRewriteGenerator->generate($cmsPage);
$this->urlPersist->deleteByData([
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/Controller/Adminhtml/System/Config/Save.php b/app/code/Magento/Config/Controller/Adminhtml/System/Config/Save.php
index 91bcb632cf73b..91f93e02dc651 100644
--- a/app/code/Magento/Config/Controller/Adminhtml/System/Config/Save.php
+++ b/app/code/Magento/Config/Controller/Adminhtml/System/Config/Save.php
@@ -8,6 +8,7 @@
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Config\Controller\Adminhtml\System\AbstractConfig;
+use Magento\Framework\Exception\LocalizedException;
/**
* System Configuration Save Controller
@@ -221,6 +222,20 @@ public function execute()
];
$configData = $this->filterNodes($configData);
+ $groups = $this->getRequest()->getParam('groups');
+
+ if (isset($groups['country']['fields'])) {
+ if (isset($groups['country']['fields']['eu_countries'])) {
+ $countries = $groups['country']['fields']['eu_countries'];
+ if (empty($countries['value']) &&
+ !isset($countries['inherit'])) {
+ throw new LocalizedException(
+ __('Something went wrong while saving this configuration.')
+ );
+ }
+ }
+ }
+
/** @var \Magento\Config\Model\Config $configModel */
$configModel = $this->_configFactory->create(['data' => $configData]);
$configModel->save();
diff --git a/app/code/Magento/Config/Controller/Adminhtml/System/ConfigSectionChecker.php b/app/code/Magento/Config/Controller/Adminhtml/System/ConfigSectionChecker.php
index b656498e97dba..2c876ecec4323 100644
--- a/app/code/Magento/Config/Controller/Adminhtml/System/ConfigSectionChecker.php
+++ b/app/code/Magento/Config/Controller/Adminhtml/System/ConfigSectionChecker.php
@@ -6,6 +6,7 @@
namespace Magento\Config\Controller\Adminhtml\System;
+use Laminas\Permissions\Acl\Exception\InvalidArgumentException;
use Magento\Framework\Exception\NotFoundException;
/**
@@ -41,11 +42,14 @@ public function isSectionAllowed($sectionId)
{
try {
if (false == $this->_configStructure->getElement($sectionId)->isAllowed()) {
+ // phpcs:ignore Magento2.Exceptions.DirectThrow
throw new \Exception('');
}
return true;
- } catch (\Zend_Acl_Exception $e) {
+ } catch (InvalidArgumentException $e) {
+ // phpcs:ignore Magento2.Exceptions.ThrowCatch
throw new NotFoundException(__('Page not found.'));
+ // phpcs:ignore Magento2.Exceptions.ThrowCatch
} catch (\Exception $e) {
return false;
}
diff --git a/app/code/Magento/Config/Model/Config/Backend/Admin/Custom.php b/app/code/Magento/Config/Model/Config/Backend/Admin/Custom.php
index 09ff3cdffc392..86eb8f1a9ba6d 100644
--- a/app/code/Magento/Config/Model/Config/Backend/Admin/Custom.php
+++ b/app/code/Magento/Config/Model/Config/Backend/Admin/Custom.php
@@ -17,46 +17,46 @@
*/
class Custom extends \Magento\Framework\App\Config\Value
{
- const CONFIG_SCOPE = 'stores';
+ public const CONFIG_SCOPE = 'stores';
- const CONFIG_SCOPE_ID = 0;
+ public const CONFIG_SCOPE_ID = 0;
- const XML_PATH_UNSECURE_BASE_URL = 'web/unsecure/base_url';
- const XML_PATH_SECURE_BASE_URL = 'web/secure/base_url';
- const XML_PATH_UNSECURE_BASE_LINK_URL = 'web/unsecure/base_link_url';
- const XML_PATH_SECURE_BASE_LINK_URL = 'web/secure/base_link_url';
- const XML_PATH_CURRENCY_OPTIONS_BASE = 'currency/options/base';
- const XML_PATH_ADMIN_SECURITY_USEFORMKEY = 'admin/security/use_form_key';
- const XML_PATH_MAINTENANCE_MODE = 'maintenance_mode';
- const XML_PATH_WEB_COOKIE_COOKIE_LIFETIME = 'web/cookie/cookie_lifetime';
+ public const XML_PATH_UNSECURE_BASE_URL = 'web/unsecure/base_url';
+ public const XML_PATH_SECURE_BASE_URL = 'web/secure/base_url';
+ public const XML_PATH_UNSECURE_BASE_LINK_URL = 'web/unsecure/base_link_url';
+ public const XML_PATH_SECURE_BASE_LINK_URL = 'web/secure/base_link_url';
+ public const XML_PATH_CURRENCY_OPTIONS_BASE = 'currency/options/base';
+ public const XML_PATH_ADMIN_SECURITY_USEFORMKEY = 'admin/security/use_form_key';
+ public const XML_PATH_MAINTENANCE_MODE = 'maintenance_mode';
+ public const XML_PATH_WEB_COOKIE_COOKIE_LIFETIME = 'web/cookie/cookie_lifetime';
/**
* @deprecated Misspelled constant - use XML_PATH_WEB_COOKIE_COOKIE_PATH instead
*/
- const XML_PATH_WEB_COOKIE_COOKE_PATH = 'web/cookie/cookie_path';
- const XML_PATH_WEB_COOKIE_COOKIE_PATH = 'web/cookie/cookie_path';
- const XML_PATH_WEB_COOKIE_COOKIE_DOMAIN = 'web/cookie/cookie_domain';
- const XML_PATH_WEB_COOKIE_HTTPONLY = 'web/cookie/cookie_httponly';
- const XML_PATH_WEB_COOKIE_RESTRICTION = 'web/cookie/cookie_restriction';
- const XML_PATH_GENERAL_LOCALE_TIMEZONE = 'general/locale/timezone';
- const XML_PATH_GENERAL_LOCALE_CODE = 'general/locale/code';
- const XML_PATH_GENERAL_COUNTRY_DEFAULT = 'general/country/default';
- const XML_PATH_SYSTEM_BACKUP_ENABLED = 'system/backup/enabled';
- const XML_PATH_DEV_JS_MERGE_FILES = 'dev/js/merge_files';
- const XML_PATH_DEV_JS_MINIFY_FILES = 'dev/js/minify_files';
- const XML_PATH_DEV_CSS_MERGE_CSS_FILES = 'dev/css/merge_css_files';
- const XML_PATH_DEV_CSS_MINIFY_FILES = 'dev/css/minify_files';
- const XML_PATH_DEV_IMAGE_DEFAULT_ADAPTER = 'dev/image/default_adapter';
- const XML_PATH_WEB_SESSION_USE_FRONTEND_SID = 'web/session/use_frontend_sid';
- const XML_PATH_WEB_SESSION_USE_HTTP_X_FORWARDED_FOR = 'web/session/use_http_x_forwarded_for';
- const XML_PATH_WEB_SESSION_USE_HTTP_VIA = 'web/session/use_http_via';
- const XML_PATH_WEB_SESSION_USE_REMOTE_ADDR = 'web/session/use_remote_addr';
- const XML_PATH_WEB_SESSION_USE_HTTP_USER_AGENT = 'web/session/use_http_user_agent';
- const XML_PATH_CATALOG_FRONTEND_FLAT_CATALOG_CATEGORY = 'catalog/frontend/flat_catalog_category';
- const XML_PATH_CATALOG_FRONTEND_FLAT_CATALOG_PRODUCT = 'catalog/frontend/flat_catalog_product';
- const XML_PATH_TAX_WEEE_ENABLE = 'tax/weee/enable';
- const XML_PATH_CATALOG_SEARCH_ENGINE = 'catalog/search/engine';
- const XML_PATH_CARRIERS = 'carriers';
- const XML_PATH_PAYMENT = 'payment';
+ public const XML_PATH_WEB_COOKIE_COOKE_PATH = 'web/cookie/cookie_path';
+ public const XML_PATH_WEB_COOKIE_COOKIE_PATH = 'web/cookie/cookie_path';
+ public const XML_PATH_WEB_COOKIE_COOKIE_DOMAIN = 'web/cookie/cookie_domain';
+ public const XML_PATH_WEB_COOKIE_HTTPONLY = 'web/cookie/cookie_httponly';
+ public const XML_PATH_WEB_COOKIE_RESTRICTION = 'web/cookie/cookie_restriction';
+ public const XML_PATH_GENERAL_LOCALE_TIMEZONE = 'general/locale/timezone';
+ public const XML_PATH_GENERAL_LOCALE_CODE = 'general/locale/code';
+ public const XML_PATH_GENERAL_COUNTRY_DEFAULT = 'general/country/default';
+ public const XML_PATH_SYSTEM_BACKUP_ENABLED = 'system/backup/enabled';
+ public const XML_PATH_DEV_JS_MERGE_FILES = 'dev/js/merge_files';
+ public const XML_PATH_DEV_JS_MINIFY_FILES = 'dev/js/minify_files';
+ public const XML_PATH_DEV_CSS_MERGE_CSS_FILES = 'dev/css/merge_css_files';
+ public const XML_PATH_DEV_CSS_MINIFY_FILES = 'dev/css/minify_files';
+ public const XML_PATH_DEV_IMAGE_DEFAULT_ADAPTER = 'dev/image/default_adapter';
+ public const XML_PATH_WEB_SESSION_USE_FRONTEND_SID = 'web/session/use_frontend_sid';
+ public const XML_PATH_WEB_SESSION_USE_HTTP_X_FORWARDED_FOR = 'web/session/use_http_x_forwarded_for';
+ public const XML_PATH_WEB_SESSION_USE_HTTP_VIA = 'web/session/use_http_via';
+ public const XML_PATH_WEB_SESSION_USE_REMOTE_ADDR = 'web/session/use_remote_addr';
+ public const XML_PATH_WEB_SESSION_USE_HTTP_USER_AGENT = 'web/session/use_http_user_agent';
+ public const XML_PATH_CATALOG_FRONTEND_FLAT_CATALOG_CATEGORY = 'catalog/frontend/flat_catalog_category';
+ public const XML_PATH_CATALOG_FRONTEND_FLAT_CATALOG_PRODUCT = 'catalog/frontend/flat_catalog_product';
+ public const XML_PATH_TAX_WEEE_ENABLE = 'tax/weee/enable';
+ public const XML_PATH_CATALOG_SEARCH_ENGINE = 'catalog/search/engine';
+ public const XML_PATH_CARRIERS = 'carriers';
+ public const XML_PATH_PAYMENT = 'payment';
/**
* @var \Magento\Framework\App\Config\Storage\WriterInterface
@@ -119,18 +119,16 @@ public function afterSave()
}
if ($useCustomUrl == 1) {
- $this->_configWriter->save(
+ $paths = [
self::XML_PATH_SECURE_BASE_URL,
- $value,
- self::CONFIG_SCOPE,
- self::CONFIG_SCOPE_ID
- );
- $this->_configWriter->save(
self::XML_PATH_UNSECURE_BASE_URL,
- $value,
- self::CONFIG_SCOPE,
- self::CONFIG_SCOPE_ID
- );
+ self::XML_PATH_SECURE_BASE_LINK_URL,
+ self::XML_PATH_UNSECURE_BASE_LINK_URL,
+ ];
+
+ foreach ($paths as $path) {
+ $this->_configWriter->save($path, $value, self::CONFIG_SCOPE, self::CONFIG_SCOPE_ID);
+ }
}
return parent::afterSave();
diff --git a/app/code/Magento/Config/Model/Config/Backend/Admin/Usecustom.php b/app/code/Magento/Config/Model/Config/Backend/Admin/Usecustom.php
index f5d568f2f36be..423c5cdf26d4b 100644
--- a/app/code/Magento/Config/Model/Config/Backend/Admin/Usecustom.php
+++ b/app/code/Magento/Config/Model/Config/Backend/Admin/Usecustom.php
@@ -78,16 +78,16 @@ public function afterSave()
$value = $this->getValue();
if (!$value) {
- $this->_configWriter->delete(
+ $paths = [
Custom::XML_PATH_SECURE_BASE_URL,
- Custom::CONFIG_SCOPE,
- Custom::CONFIG_SCOPE_ID
- );
- $this->_configWriter->delete(
Custom::XML_PATH_UNSECURE_BASE_URL,
- Custom::CONFIG_SCOPE,
- Custom::CONFIG_SCOPE_ID
- );
+ Custom::XML_PATH_SECURE_BASE_LINK_URL,
+ Custom::XML_PATH_UNSECURE_BASE_LINK_URL,
+ ];
+
+ foreach ($paths as $path) {
+ $this->_configWriter->delete($path, Custom::CONFIG_SCOPE, Custom::CONFIG_SCOPE_ID);
+ }
}
return parent::afterSave();
diff --git a/app/code/Magento/Config/Model/Config/Backend/Currency/Cron.php b/app/code/Magento/Config/Model/Config/Backend/Currency/Cron.php
index f29fa0611efa4..1b9bd78c83942 100644
--- a/app/code/Magento/Config/Model/Config/Backend/Currency/Cron.php
+++ b/app/code/Magento/Config/Model/Config/Backend/Currency/Cron.php
@@ -9,6 +9,8 @@
*/
namespace Magento\Config\Model\Config\Backend\Currency;
+use Magento\Framework\Exception\LocalizedException;
+
/**
* Cron job configuration for currency
*
@@ -17,8 +19,7 @@
*/
class Cron extends \Magento\Framework\App\Config\Value
{
- const CRON_STRING_PATH = 'crontab/default/jobs/currency_rates_update/schedule/cron_expr';
-
+ public const CRON_STRING_PATH = 'crontab/default/jobs/currency_rates_update/schedule/cron_expr';
/**
* @var \Magento\Framework\App\Config\ValueFactory
*/
@@ -52,13 +53,28 @@ public function __construct(
* After save handler
*
* @return $this
- * @throws \Exception
+ * @throws LocalizedException
*/
public function afterSave()
{
$time = $this->getData('groups/import/fields/time/value');
- $frequency = $this->getData('groups/import/fields/frequency/value');
-
+ if (empty($time)) {
+ $time = explode(
+ ',',
+ $this->_config->getValue(
+ 'currency/import/time',
+ $this->getScope(),
+ $this->getScopeId()
+ ) ?: '0,0,0'
+ );
+ $frequency = $this->_config->getValue(
+ 'currency/import/frequency',
+ $this->getScope(),
+ $this->getScopeId()
+ );
+ } else {
+ $frequency = $this->getData('groups/import/fields/frequency/value');
+ }
$frequencyWeekly = \Magento\Cron\Model\Config\Source\Frequency::CRON_WEEKLY;
$frequencyMonthly = \Magento\Cron\Model\Config\Source\Frequency::CRON_MONTHLY;
@@ -78,7 +94,7 @@ public function afterSave()
$configValue->load(self::CRON_STRING_PATH, 'path');
$configValue->setValue($cronExprString)->setPath(self::CRON_STRING_PATH)->save();
} catch (\Exception $e) {
- throw new \Exception(__('We can\'t save the Cron expression.'));
+ throw new LocalizedException(__('We can\'t save the Cron expression.'));
}
return parent::afterSave();
}
diff --git a/app/code/Magento/Config/Model/Config/Backend/Email/Address.php b/app/code/Magento/Config/Model/Config/Backend/Email/Address.php
index 2d12884e7bc70..20f1f5fc93fbf 100644
--- a/app/code/Magento/Config/Model/Config/Backend/Email/Address.php
+++ b/app/code/Magento/Config/Model/Config/Backend/Email/Address.php
@@ -9,22 +9,28 @@
*/
namespace Magento\Config\Model\Config\Backend\Email;
+use Magento\Framework\App\Config\Value;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Validator\EmailAddress;
+use Magento\Framework\Validator\ValidateException;
+use Magento\Framework\Validator\ValidatorChain;
/**
* @api
* @since 100.0.2
*/
-class Address extends \Magento\Framework\App\Config\Value
+class Address extends Value
{
/**
+ * Processing object before save data
+ *
* @return $this
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException|ValidateException
*/
public function beforeSave()
{
$value = $this->getValue();
- if (!\Zend_Validate::is($value, \Magento\Framework\Validator\EmailAddress::class)) {
+ if (!ValidatorChain::is($value, EmailAddress::class)) {
throw new LocalizedException(
__('The "%1" email address is incorrect. Verify the email address and try again.', $value)
);
diff --git a/app/code/Magento/Config/Model/Config/Structure/Element/Dependency/Field.php b/app/code/Magento/Config/Model/Config/Structure/Element/Dependency/Field.php
index 5214529bf2f04..22705dff72de5 100644
--- a/app/code/Magento/Config/Model/Config/Structure/Element/Dependency/Field.php
+++ b/app/code/Magento/Config/Model/Config/Structure/Element/Dependency/Field.php
@@ -5,15 +5,13 @@
*/
namespace Magento\Config\Model\Config\Structure\Element\Dependency;
-/**
- * @api
- * @since 100.0.2
- */
-
/**
* Class Field
*
* Fields are used to describe possible values for a type/interface.
+ *
+ * @api
+ * @since 100.0.2
*/
class Field
{
diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminConfigureSalesGiftOptionsActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminConfigureSalesGiftOptionsActionGroup.xml
new file mode 100755
index 0000000000000..add30d345d784
--- /dev/null
+++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminConfigureSalesGiftOptionsActionGroup.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+ Sets Allow Gift Message For a product
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminSetDefaultWebsiteActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminSetDefaultWebsiteActionGroup.xml
new file mode 100644
index 0000000000000..b7e5042577931
--- /dev/null
+++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminSetDefaultWebsiteActionGroup.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+ Sets the Website as default.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/ChooseElasticSearchAsSearchEngineActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/ChooseElasticSearchAsSearchEngineActionGroup.xml
deleted file mode 100644
index ad1cf897b4601..0000000000000
--- a/app/code/Magento/Config/Test/Mftf/ActionGroup/ChooseElasticSearchAsSearchEngineActionGroup.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
- Goes to the 'Configuration' page for 'Catalog'. Sets the 'Search Engine' to 'elasticsearch5'. Clicks on the Save button. PLEASE NOTE: The value is Hardcoded.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/EuropeanCountriesOptionActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/EuropeanCountriesOptionActionGroup.xml
new file mode 100644
index 0000000000000..b913301702a02
--- /dev/null
+++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/EuropeanCountriesOptionActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ Validate european country option value
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Config/Test/Mftf/Section/AdminSalesConfigSection.xml b/app/code/Magento/Config/Test/Mftf/Section/AdminSalesConfigSection.xml
old mode 100644
new mode 100755
index 1c2e2603566bf..d7e47214decbc
--- a/app/code/Magento/Config/Test/Mftf/Section/AdminSalesConfigSection.xml
+++ b/app/code/Magento/Config/Test/Mftf/Section/AdminSalesConfigSection.xml
@@ -13,5 +13,16 @@
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
+
diff --git a/app/code/Magento/Config/Test/Mftf/Section/CountriesFormSection.xml b/app/code/Magento/Config/Test/Mftf/Section/CountriesFormSection.xml
new file mode 100644
index 0000000000000..7a18a4de00fc1
--- /dev/null
+++ b/app/code/Magento/Config/Test/Mftf/Section/CountriesFormSection.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Config/Test/Mftf/Test/ValidateEuropeanCountriesOptionValue.xml b/app/code/Magento/Config/Test/Mftf/Test/ValidateEuropeanCountriesOptionValue.xml
new file mode 100644
index 0000000000000..72a23f7e811c0
--- /dev/null
+++ b/app/code/Magento/Config/Test/Mftf/Test/ValidateEuropeanCountriesOptionValue.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/XsdTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/XsdTest.php
index 68e22c539f526..41b7ca7a67915 100644
--- a/app/code/Magento/Config/Test/Unit/Model/Config/XsdTest.php
+++ b/app/code/Magento/Config/Test/Unit/Model/Config/XsdTest.php
@@ -42,7 +42,10 @@ protected function setUp(): void
public function testSchemaCorrectlyIdentifiesInvalidXml($xmlString, $expectedError)
{
$actualError = $this->_xsdValidator->validate($this->_xsdSchema, $xmlString);
- $this->assertEquals($expectedError, $actualError);
+ $this->assertEquals(false, empty($actualError));
+ foreach ($expectedError as $error) {
+ $this->assertContains($error, $actualError);
+ }
}
public function testSchemaCorrectlyIdentifiesValidXml()
diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/_files/invalidSystemXmlArray.php b/app/code/Magento/Config/Test/Unit/Model/Config/_files/invalidSystemXmlArray.php
index acd8c580caf2e..dce63cc449c0a 100644
--- a/app/code/Magento/Config/Test/Unit/Model/Config/_files/invalidSystemXmlArray.php
+++ b/app/code/Magento/Config/Test/Unit/Model/Config/_files/invalidSystemXmlArray.php
@@ -65,8 +65,7 @@
"Element 'config_path': [facet 'minLength'] The value has a length of '2'; this underruns " .
"the allowed minimum length of '5'.\nLine: 1\n",
"Element 'config_path': [facet 'pattern'] The value 'co' is not " .
- "accepted by the pattern '[a-zA-Z0-9_\\\\]+/[a-zA-Z0-9_\\\\]+/[a-zA-Z0-9_\\\\]+'.\nLine: 1\n",
- "Element 'config_path': 'co' is " . "not a valid value of the atomic type 'typeConfigPath'.\nLine: 1\n"
+ "accepted by the pattern '[a-zA-Z0-9_\\\\]+/[a-zA-Z0-9_\\\\]+/[a-zA-Z0-9_\\\\]+'.\nLine: 1\n"
],
],
'if_module_enabled_with_invalid_type' => [
@@ -78,8 +77,7 @@
"Element 'if_module_enabled': [facet 'minLength'] The value has a length of '3'; this underruns the " .
"allowed minimum length of '5'.\nLine: 1\n",
"Element 'if_module_enabled': [facet 'pattern'] The value 'Som' is not " .
- "accepted by the pattern '[A-Z]+[a-zA-Z0-9]{1,}[_\\\\][A-Z]+[A-Z0-9a-z]{1,}'.\nLine: 1\n",
- "Element 'if_module_enabled': 'Som' " . "is not a valid value of the atomic type 'typeModule'.\nLine: 1\n"
+ "accepted by the pattern '[A-Z]+[a-zA-Z0-9]{1,}[_\\\\][A-Z]+[A-Z0-9a-z]{1,}'.\nLine: 1\n"
],
],
'id_minimum length' => [
@@ -89,22 +87,11 @@
[
"Element 'section', attribute 'id': [facet 'minLength'] The value 's' has a length of '1'; this " .
"underruns the allowed minimum length of '2'.\nLine: 1\n",
- "Element 'section', attribute 'id': 's' is not a valid value " . "of the atomic type 'typeId'.\nLine: 1\n",
- "Element 'section', attribute 'id': Warning: No precomputed " .
- "value available, the value was either invalid or something strange happend.\nLine: 1\n",
"Element 'field', attribute " .
"'id': [facet 'minLength'] The value 'f' has a length of '1'; this underruns the allowed minimum length " .
"of '2'.\nLine: 1\n",
- "Element 'field', attribute 'id': 'f' is not a valid value of the atomic type 'typeId'.\nLine: 1\n",
- "Element" .
- " 'field', attribute 'id': " .
- "Warning: No precomputed value available, the value was either invalid or something" .
- " strange happend.\nLine: 1\n",
"Element 'tab', attribute 'id': [facet 'minLength'] The value 'h' has a length of '1'; " .
- "this underruns the allowed minimum length of '2'.\nLine: 1\n",
- "Element 'tab', attribute 'id': 'h' is not a valid value" . " of the atomic type 'typeId'.\nLine: 1\n",
- "Element 'tab', attribute 'id': Warning: No precomputed value available, " .
- "the value was either invalid or something strange happend.\nLine: 1\n"
+ "this underruns the allowed minimum length of '2'.\nLine: 1\n"
],
],
'source_model_with_invalid_type' => [
@@ -114,8 +101,7 @@
'Label_One ',
[
"Element 'source_model': [facet 'minLength'] The value has a length of '4'; this underruns the allowed " .
- "minimum length of '5'.\nLine: 1\n",
- "Element 'source_model': 'Sour' is not a valid value of the atomic" . " type 'typeModel'.\nLine: 1\n"
+ "minimum length of '5'.\nLine: 1\n"
],
],
'base_url_with_invalid_type' => [
@@ -126,8 +112,7 @@
"Element 'resource': [facet 'minLength'] The value has a length of '4'; this underruns the allowed " .
"minimum length of '8'.\nLine: 1\n",
"Element 'resource': [facet 'pattern'] The value 'One:' is not accepted by the " .
- "pattern '([A-Z]+[a-zA-Z0-9]{1,}){1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n",
- "Element 'resource': 'One:' is not " . "a valid value of the atomic type 'typeAclResourceId'.\nLine: 1\n"
+ "pattern '([A-Z]+[a-zA-Z0-9]{1,}){1,}_[A-Z]+[A-Z0-9a-z]{1,}::[A-Za-z_0-9]{1,}'.\nLine: 1\n"
],
],
'advanced_with_invalid_type' => [
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/Model/Plugin/ProductRepositorySave.php b/app/code/Magento/ConfigurableProduct/Model/Plugin/ProductRepositorySave.php
index dc4ad39752e4f..f14fb67c3f6a9 100644
--- a/app/code/Magento/ConfigurableProduct/Model/Plugin/ProductRepositorySave.php
+++ b/app/code/Magento/ConfigurableProduct/Model/Plugin/ProductRepositorySave.php
@@ -49,7 +49,7 @@ public function __construct(
* @param ProductRepositoryInterface $subject
* @param ProductInterface $product
* @param bool $saveOptions
- * @return array
+ * @return void
* @throws InputException
* @throws NoSuchEntityException
*
@@ -59,34 +59,23 @@ public function beforeSave(
ProductRepositoryInterface $subject,
ProductInterface $product,
$saveOptions = false
- ): array {
- $result[] = $product;
- if ($product->getTypeId() !== Configurable::TYPE_CODE) {
- return $result;
- }
-
+ ): void {
$extensionAttributes = $product->getExtensionAttributes();
- if ($extensionAttributes === null) {
- return $result;
- }
-
- $configurableLinks = (array) $extensionAttributes->getConfigurableProductLinks();
- $configurableOptions = (array) $extensionAttributes->getConfigurableProductOptions();
+ if ($extensionAttributes !== null && $product->getTypeId() === Configurable::TYPE_CODE) {
+ $configurableLinks = (array) $extensionAttributes->getConfigurableProductLinks();
+ $configurableOptions = (array) $extensionAttributes->getConfigurableProductOptions();
- if (empty($configurableLinks) && empty($configurableOptions)) {
- return $result;
- }
-
- $attributeCodes = [];
- /** @var OptionInterface $configurableOption */
- foreach ($configurableOptions as $configurableOption) {
- $eavAttribute = $this->productAttributeRepository->get($configurableOption->getAttributeId());
- $attributeCode = $eavAttribute->getAttributeCode();
- $attributeCodes[] = $attributeCode;
+ if (!empty($configurableLinks) || !empty($configurableOptions)) {
+ $attributeCodes = [];
+ /** @var OptionInterface $configurableOption */
+ foreach ($configurableOptions as $configurableOption) {
+ $eavAttribute = $this->productAttributeRepository->get($configurableOption->getAttributeId());
+ $attributeCode = $eavAttribute->getAttributeCode();
+ $attributeCodes[] = $attributeCode;
+ }
+ $this->validateProductLinks($attributeCodes, $configurableLinks);
+ }
}
- $this->validateProductLinks($attributeCodes, $configurableLinks);
-
- return $result;
}
/**
diff --git a/app/code/Magento/ConfigurableProduct/Test/Fixture/AddProductToCart.php b/app/code/Magento/ConfigurableProduct/Test/Fixture/AddProductToCart.php
new file mode 100644
index 0000000000000..8b0d9d9af6555
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Fixture/AddProductToCart.php
@@ -0,0 +1,76 @@
+productRepository = $productRepository;
+ $this->productType = $productType;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @param array $data Parameters
+ *
+ * $data = [
+ * 'cart_id' => (int) Cart ID. Required.
+ * 'product_id' => (int) Product ID. Required.
+ * 'child_product_id' => (int) Child Product ID. Required.
+ * 'qty' => (int) Quantity. Optional. Default: 1.
+ * ]
+ *
+ */
+ public function apply(array $data = []): ?DataObject
+ {
+ $configurableProduct = $this->productRepository->getById($data['product_id']);
+ $childProduct = $this->productRepository->getById($data['child_product_id']);
+ $buyRequest = [
+ 'super_attribute' => [],
+ 'qty' => $data['qty'] ?? 1,
+ ];
+ foreach ($this->productType->getUsedProductAttributes($configurableProduct) as $attr) {
+ $buyRequest['super_attribute'][$attr->getId()] = $childProduct->getData($attr->getAttributeCode());
+ }
+ return parent::apply(
+ [
+ 'cart_id' => $data['cart_id'],
+ 'product_id' => $data['product_id'],
+ 'buy_request' => $buyRequest
+ ]
+ );
+ }
+}
diff --git a/app/code/Magento/ConfigurableProduct/Test/Fixture/Attribute.php b/app/code/Magento/ConfigurableProduct/Test/Fixture/Attribute.php
index 94766e0affd92..3e6454ff44b3c 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Fixture/Attribute.php
+++ b/app/code/Magento/ConfigurableProduct/Test/Fixture/Attribute.php
@@ -23,6 +23,7 @@ class Attribute extends \Magento\Catalog\Test\Fixture\Attribute
'sort_order' => 1,
]
],
+ 'scope' => 'global',
];
/**
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminAddOptionsToAttributeWithDefaultLayeredNavigationActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminAddOptionsToAttributeWithDefaultLayeredNavigationActionGroup.xml
index c48f22a3656d5..5a0130f5317a4 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminAddOptionsToAttributeWithDefaultLayeredNavigationActionGroup.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminAddOptionsToAttributeWithDefaultLayeredNavigationActionGroup.xml
@@ -19,6 +19,8 @@
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminGotoSelectValueAttributePageActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminGotoSelectValueAttributePageActionGroup.xml
index 969a41e27d459..f4c51e5834db0 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminGotoSelectValueAttributePageActionGroup.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminGotoSelectValueAttributePageActionGroup.xml
@@ -17,6 +17,8 @@
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminSetProductQuantityToEachSkusConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminSetProductQuantityToEachSkusConfigurableProductActionGroup.xml
new file mode 100644
index 0000000000000..c5050827a94b1
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminSetProductQuantityToEachSkusConfigurableProductActionGroup.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ Set quantity to all child skus for configurable product. Save a configurable product and confirm.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/CreateConfigurableProductActionGroupWithDefaultColorAttributeActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/CreateConfigurableProductActionGroupWithDefaultColorAttributeActionGroup.xml
new file mode 100644
index 0000000000000..df16f3c196a7e
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/CreateConfigurableProductActionGroupWithDefaultColorAttributeActionGroup.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+ Goes to the Admin Product grid page. Creates a Configurable Product using the default Product Options(Color)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/CreateNewAttributeActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/CreateNewAttributeActionGroup.xml
index 9925aba09fb82..e9908264584f7 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/CreateNewAttributeActionGroup.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/CreateNewAttributeActionGroup.xml
@@ -49,9 +49,10 @@
+
-
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/DeleteCreatedAttributeActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/DeleteCreatedAttributeActionGroup.xml
index ff8cfecf83ab9..5e6c1d06a87c0 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/DeleteCreatedAttributeActionGroup.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/DeleteCreatedAttributeActionGroup.xml
@@ -26,7 +26,7 @@
-
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml
index 0018f5996c9bc..44e045c64a88d 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml
@@ -12,7 +12,7 @@
Shoes
60
100
- design
+ design
red
red123
blue
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml
index 13760f74297e7..d2873e79a8b89 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml
@@ -65,4 +65,14 @@
10
1
+
+
+ Black
+ sku-black
+ simple
+ 2
+ 1
+ 10
+ 1
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml
index 92e2450ef4f3d..3c5581d496b9b 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml
@@ -60,5 +60,6 @@
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/CatalogProductsSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/CatalogProductsSection.xml
index fb71d6cbda2a8..bcb092abb9b86 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/CatalogProductsSection.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/CatalogProductsSection.xml
@@ -14,7 +14,8 @@
-
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/ConfigurableProductSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/ConfigurableProductSection.xml
index 8099f30941f7d..087e748f404c6 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/ConfigurableProductSection.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/ConfigurableProductSection.xml
@@ -12,7 +12,8 @@
-
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminApplyTierPriceForConfigurableProdTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminApplyTierPriceForConfigurableProdTest.xml
new file mode 100644
index 0000000000000..80adfebb57f7a
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminApplyTierPriceForConfigurableProdTest.xml
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDisplayAssociatedProductPriceTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDisplayAssociatedProductPriceTest.xml
new file mode 100644
index 0000000000000..6093e39e899c6
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDisplayAssociatedProductPriceTest.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductwithanOutofStockItemInShoppingCartTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductwithanOutofStockItemInShoppingCartTest.xml
new file mode 100644
index 0000000000000..c8bc4541015d7
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductwithanOutofStockItemInShoppingCartTest.xml
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/CustomerReorderConfigurableProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/CustomerReorderConfigurableProductTest.xml
new file mode 100644
index 0000000000000..c620023ae102f
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/CustomerReorderConfigurableProductTest.xml
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontAdvanceCatalogSearchConfigurableBySkuWithHyphenTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontAdvanceCatalogSearchConfigurableBySkuWithHyphenTest.xml
index b7ab7323904c5..9829c11e4a058 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontAdvanceCatalogSearchConfigurableBySkuWithHyphenTest.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontAdvanceCatalogSearchConfigurableBySkuWithHyphenTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml
index 4927d05c110e6..0a54083eb8bd5 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/ProductRepositorySaveTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/ProductRepositorySaveTest.php
index 07b4a1faf3db4..9ec362fc76e67 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/ProductRepositorySaveTest.php
+++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/ProductRepositorySaveTest.php
@@ -113,16 +113,13 @@ protected function setUp(): void
*/
public function testBeforeSaveWhenProductIsSimple(): void
{
- $this->product->expects(static::once())
+ $this->product->expects(static::atMost(1))
->method('getTypeId')
->willReturn('simple');
- $this->product->expects(static::never())
+ $this->product->expects(static::once())
->method('getExtensionAttributes');
- $this->assertEquals(
- $this->product,
- $this->plugin->beforeSave($this->productRepository, $this->product)[0]
- );
+ $this->assertNull($this->plugin->beforeSave($this->productRepository, $this->product));
}
/**
@@ -150,52 +147,7 @@ public function testBeforeSaveWithoutOptions(): void
$this->productAttributeRepository->expects(static::never())
->method('get');
- $this->assertEquals(
- $this->product,
- $this->plugin->beforeSave($this->productRepository, $this->product)[0]
- );
- }
-
- /**
- * Test saving a configurable product with same set of attribute values
- *
- * @return void
- */
- public function testBeforeSaveWithLinks(): void
- {
- $this->expectException(InputException::class);
- $this->expectExceptionMessage('Products "5" and "4" have the same set of attribute values.');
- $links = [4, 5];
- $this->product->expects(static::once())
- ->method('getTypeId')
- ->willReturn(Configurable::TYPE_CODE);
-
- $this->product->expects(static::once())
- ->method('getExtensionAttributes')
- ->willReturn($this->extensionAttributes);
- $this->extensionAttributes->expects(static::once())
- ->method('getConfigurableProductOptions')
- ->willReturn(null);
- $this->extensionAttributes->expects(static::once())
- ->method('getConfigurableProductLinks')
- ->willReturn($links);
-
- $this->productAttributeRepository->expects(static::never())
- ->method('get');
-
- $product = $this->getMockBuilder(Product::class)
- ->disableOriginalConstructor()
- ->setMethods(['getData'])
- ->getMock();
-
- $this->productRepository->expects(static::exactly(2))
- ->method('getById')
- ->willReturn($product);
-
- $product->expects(static::never())
- ->method('getData');
-
- $this->plugin->beforeSave($this->productRepository, $this->product);
+ $this->assertNull($this->plugin->beforeSave($this->productRepository, $this->product));
}
/**
diff --git a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php
index 7815408321ed4..f01a8877d6311 100644
--- a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php
+++ b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php
@@ -13,12 +13,13 @@
use Magento\CatalogInventory\Api\StockRegistryInterface;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable as ConfigurableType;
use Magento\ConfigurableProduct\Model\Product\Type\VariationMatrix;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Currency\Exception\CurrencyException;
+use Magento\Framework\Escaper;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Json\Helper\Data as JsonHelper;
use Magento\Framework\Locale\CurrencyInterface;
use Magento\Framework\UrlInterface;
-use Magento\Framework\App\ObjectManager;
-use Magento\Framework\Escaper;
/**
* Associated products helper
@@ -232,7 +233,7 @@ public function getConfigurableAttributesData()
* Prepare variations
*
* @return void
- * @throws \Zend_Currency_Exception
+ * @throws CurrencyException
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* phpcs:disable Generic.Metrics.NestingLevel.TooHigh
*/
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/ui_component/configurable_associated_product_listing.xml b/app/code/Magento/ConfigurableProduct/view/adminhtml/ui_component/configurable_associated_product_listing.xml
index c23141d44a2ec..4aea7909fb746 100644
--- a/app/code/Magento/ConfigurableProduct/view/adminhtml/ui_component/configurable_associated_product_listing.xml
+++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/ui_component/configurable_associated_product_listing.xml
@@ -21,6 +21,9 @@
false
+
+ - POST
+
diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js
index 814b5de71a8f7..40d6d480f45ef 100644
--- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js
+++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js
@@ -412,7 +412,7 @@ define([
product = {
'id': row.productId,
'product_link': row.productUrl,
- 'name': $(' ').text(row.name).html(),
+ 'name': row.name,
'sku': row.sku,
'status': row.status,
'price': row.price,
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/ConfigurableProduct/view/frontend/web/js/options-updater.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/options-updater.js
index 558a1fdf31085..74e1514fad6f6 100644
--- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/options-updater.js
+++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/options-updater.js
@@ -1,7 +1,8 @@
define([
'jquery',
'underscore',
- 'Magento_Customer/js/customer-data'
+ 'Magento_Customer/js/customer-data',
+ 'domReady!'
], function ($, _, customerData) {
'use strict';
diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php
index bc67046dee8d3..795c38d7e6117 100644
--- a/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php
+++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php
@@ -153,6 +153,7 @@ private function fetch(ContextInterface $context) : array
$childCollection = $this->childCollectionFactory->create();
$childCollection->setProductFilter($product);
$childCollection->addWebsiteFilter($context->getExtensionAttributes()->getStore()->getWebsiteId());
+ $childCollection->setFlag('product_children', true);
$this->collectionProcessor->process(
$childCollection,
$this->searchCriteriaBuilder->create(),
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/ConfigurableProductGraphQl/etc/graphql/di.xml b/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml
index 0d307c1fe1948..eb36b1323939c 100644
--- a/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml
+++ b/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml
@@ -85,4 +85,12 @@
+
+
+
+ Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Deferred\ChildProduct
+
+
+
diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls
index 126fd44e024fc..adef21a2094e2 100644
--- a/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls
@@ -12,7 +12,7 @@ type ConfigurableProduct implements ProductInterface, RoutableInterface, Physica
type ConfigurableVariant @doc(description: "Contains all the simple product variants of a configurable product.") {
attributes: [ConfigurableAttributeOption] @resolver(class: "Magento\\ConfigurableProductGraphQl\\Model\\Resolver\\Variant\\Attributes") @doc(description: "An array of configurable attribute options.")
- product: SimpleProduct @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product") @doc(description: "An array of linked simple products.")
+ product: SimpleProduct @resolver(class: "Magento\\ConfigurableProductGraphQl\\Model\\Resolver\\Variant\\Product") @doc(description: "An array of linked simple products.")
}
type ConfigurableAttributeOption @doc(description: "Contains details about a configurable product attribute option.") {
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 @@
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/Console/Command/CronCommand.php b/app/code/Magento/Cron/Console/Command/CronCommand.php
index 142a9a397eb5f..0a9fd4c195f0a 100644
--- a/app/code/Magento/Cron/Console/Command/CronCommand.php
+++ b/app/code/Magento/Cron/Console/Command/CronCommand.php
@@ -6,32 +6,35 @@
namespace Magento\Cron\Console\Command;
-use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\Console\Input\InputOption;
-use Magento\Framework\App\ObjectManager;
-use Magento\Framework\App\ObjectManagerFactory;
-use Magento\Store\Model\Store;
-use Magento\Store\Model\StoreManager;
use Magento\Cron\Observer\ProcessCronQueueObserver;
+use Magento\Framework\App\Cron;
use Magento\Framework\App\DeploymentConfig;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\App\ObjectManagerFactory;
use Magento\Framework\Console\Cli;
+use Magento\Framework\Exception\FileSystemException;
+use Magento\Framework\Exception\RuntimeException;
use Magento\Framework\Shell\ComplexParameter;
+use Magento\Store\Model\Store;
+use Magento\Store\Model\StoreManager;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
/**
* Command for executing cron jobs
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CronCommand extends Command
{
/**
* Name of input option
*/
- const INPUT_KEY_GROUP = 'group';
+ public const INPUT_KEY_GROUP = 'group';
/**
- * Object manager factory
- *
* @var ObjectManagerFactory
*/
private $objectManagerFactory;
@@ -45,7 +48,7 @@ class CronCommand extends Command
/**
* @param ObjectManagerFactory $objectManagerFactory
- * @param DeploymentConfig $deploymentConfig Application deployment configuration
+ * @param DeploymentConfig|null $deploymentConfig Application deployment configuration
*/
public function __construct(
ObjectManagerFactory $objectManagerFactory,
@@ -59,7 +62,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function configure()
{
@@ -86,14 +89,20 @@ protected function configure()
/**
* Runs cron jobs if cron is not disabled in Magento configurations
*
- * {@inheritdoc}
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ *
+ * @return int
+ * @throws FileSystemException
+ * @throws RuntimeException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (!$this->deploymentConfig->get('cron/enabled', 1)) {
$output->writeln('' . 'Cron is disabled. Jobs were not run.' . ' ');
- return;
+ return Cli::RETURN_SUCCESS;
}
+ // phpcs:ignore Magento2.Security.Superglobal
$omParams = $_SERVER;
$omParams[StoreManager::PARAM_RUN_CODE] = 'admin';
$omParams[Store::CUSTOM_ENTRY_POINT_PARAM] = true;
@@ -112,9 +121,11 @@ protected function execute(InputInterface $input, OutputInterface $output)
$params[ProcessCronQueueObserver::STANDALONE_PROCESS_STARTED] = $bootstrapOptionValue;
}
}
- /** @var \Magento\Framework\App\Cron $cronObserver */
- $cronObserver = $objectManager->create(\Magento\Framework\App\Cron::class, ['parameters' => $params]);
+ /** @var Cron $cronObserver */
+ $cronObserver = $objectManager->create(Cron::class, ['parameters' => $params]);
$cronObserver->launch();
$output->writeln('' . 'Ran jobs by schedule.' . ' ');
+
+ return Cli::RETURN_SUCCESS;
}
}
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/Model/System/Currencysymbol.php b/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php
index 400aa56bc68e9..f3b3c46ca7ff9 100644
--- a/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php
+++ b/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php
@@ -27,14 +27,14 @@ class Currencysymbol
protected $_symbolsData = [];
/**
- * Store id
+ * Current store id
*
* @var string|null
*/
protected $_storeId;
/**
- * Website id
+ * Current website id
*
* @var string|null
*/
@@ -55,19 +55,19 @@ class Currencysymbol
/**
* Config path to custom currency symbol value
*/
- const XML_PATH_CUSTOM_CURRENCY_SYMBOL = 'currency/options/customsymbol';
+ public const XML_PATH_CUSTOM_CURRENCY_SYMBOL = 'currency/options/customsymbol';
- const XML_PATH_ALLOWED_CURRENCIES = \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_ALLOW;
+ public const XML_PATH_ALLOWED_CURRENCIES = \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_ALLOW;
/*
* Separator used in config in allowed currencies list
*/
- const ALLOWED_CURRENCIES_CONFIG_SEPARATOR = ',';
+ public const ALLOWED_CURRENCIES_CONFIG_SEPARATOR = ',';
/**
* Config currency section
*/
- const CONFIG_SECTION = 'currency';
+ public const CONFIG_SECTION = 'currency';
/**
* Core event manager proxy
@@ -174,11 +174,11 @@ public function getCurrencySymbolsData()
if (isset($currentSymbols[$code]) && !empty($currentSymbols[$code])) {
$this->_symbolsData[$code]['displaySymbol'] = $currentSymbols[$code];
+ $this->_symbolsData[$code]['inherited'] = false;
} else {
$this->_symbolsData[$code]['displaySymbol'] = $this->_symbolsData[$code]['parentSymbol'];
+ $this->_symbolsData[$code]['inherited'] = true;
}
- $this->_symbolsData[$code]['inherited'] =
- ($this->_symbolsData[$code]['parentSymbol'] == $this->_symbolsData[$code]['displaySymbol']);
}
return $this->_symbolsData;
@@ -193,8 +193,8 @@ public function getCurrencySymbolsData()
public function setCurrencySymbolsData($symbols = [])
{
if (!$this->_storeManager->isSingleStoreMode()) {
- foreach ($this->getCurrencySymbolsData() as $code => $values) {
- if (isset($symbols[$code]) && ($symbols[$code] == $values['parentSymbol'] || empty($symbols[$code]))) {
+ foreach (array_keys($this->getCurrencySymbolsData()) as $code) {
+ if (isset($symbols[$code]) && empty($symbols[$code])) {
unset($symbols[$code]);
}
}
diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Data/AdminElasticSearch6MessagesData.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Data/AdminSaveCurrencySymbolMessageData.xml
similarity index 72%
rename from app/code/Magento/Elasticsearch6/Test/Mftf/Data/AdminElasticSearch6MessagesData.xml
rename to app/code/Magento/CurrencySymbol/Test/Mftf/Data/AdminSaveCurrencySymbolMessageData.xml
index 3fa0ef9fd1c28..76f69a04f484d 100644
--- a/app/code/Magento/Elasticsearch6/Test/Mftf/Data/AdminElasticSearch6MessagesData.xml
+++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Data/AdminSaveCurrencySymbolMessageData.xml
@@ -8,7 +8,7 @@
-
- Successful! Test again?
+
+ You applied the custom currency symbols.
diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Data/CurrencyRatesConfigData.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Data/CurrencyRatesConfigData.xml
index 9b95f4017df08..d2aeef87112c4 100644
--- a/app/code/Magento/CurrencySymbol/Test/Mftf/Data/CurrencyRatesConfigData.xml
+++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Data/CurrencyRatesConfigData.xml
@@ -152,4 +152,22 @@
websites
base
+
+ currency/options/base
+ IDR
+ websites
+ base
+
+
+ currency/options/default
+ IDR
+ websites
+ base
+
+
+ currency/options/allow
+ IDR
+ websites
+ base
+
diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Section/AdminCurrencySymbolsGridSection.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Section/AdminCurrencySymbolsGridSection.xml
index bf087d2ed8f4d..f401c0967a32f 100644
--- a/app/code/Magento/CurrencySymbol/Test/Mftf/Section/AdminCurrencySymbolsGridSection.xml
+++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Section/AdminCurrencySymbolsGridSection.xml
@@ -10,5 +10,7 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayWhenChooseThreeAllowedCurrenciesTest.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayWhenChooseThreeAllowedCurrenciesTest.xml
index 2b476682e5650..be32919c79c83 100644
--- a/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayWhenChooseThreeAllowedCurrenciesTest.xml
+++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayWhenChooseThreeAllowedCurrenciesTest.xml
@@ -66,8 +66,12 @@
-
-
+
+
+
+
+
+
grabRates
diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayedInOneLineTest.xml b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayedInOneLineTest.xml
index 1b1af9acdf1a7..d3cf2c124c85c 100644
--- a/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayedInOneLineTest.xml
+++ b/app/code/Magento/CurrencySymbol/Test/Mftf/Test/AdminOrderRateDisplayedInOneLineTest.xml
@@ -78,7 +78,9 @@
-
+
+
+
grabRate
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/Controller/Account/Confirmation.php b/app/code/Magento/Customer/Controller/Account/Confirmation.php
index 59def8640328c..db4f49549116d 100644
--- a/app/code/Magento/Customer/Controller/Account/Confirmation.php
+++ b/app/code/Magento/Customer/Controller/Account/Confirmation.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Customer\Controller\Account;
use Magento\Customer\Api\AccountManagementInterface;
@@ -13,22 +15,26 @@
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Controller\Result\Redirect;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\State\InvalidTransitionException;
+use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;
use Magento\Store\Model\StoreManagerInterface;
/**
- * Class Confirmation. Send confirmation link to specified email
+ * Send confirmation link to specified email
*/
class Confirmation extends AbstractAccount implements HttpGetActionInterface, HttpPostActionInterface
{
/**
- * @var \Magento\Store\Model\StoreManagerInterface
+ * @var StoreManagerInterface
*/
protected $storeManager;
/**
- * @var \Magento\Customer\Api\AccountManagementInterface
+ * @var AccountManagementInterface
*/
protected $customerAccountManagement;
@@ -53,7 +59,7 @@ class Confirmation extends AbstractAccount implements HttpGetActionInterface, Ht
* @param PageFactory $resultPageFactory
* @param StoreManagerInterface $storeManager
* @param AccountManagementInterface $customerAccountManagement
- * @param Url $customerUrl
+ * @param Url|null $customerUrl
*/
public function __construct(
Context $context,
@@ -74,48 +80,52 @@ public function __construct(
/**
* Send confirmation link to specified email
*
- * @return \Magento\Framework\Controller\Result\Redirect|\Magento\Framework\View\Result\Page
+ * @return Redirect|Page
+ * @throws LocalizedException
*/
public function execute()
{
if ($this->session->isLoggedIn()) {
- /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */
- $resultRedirect = $this->resultRedirectFactory->create();
- $resultRedirect->setPath('*/*/');
- return $resultRedirect;
+ return $this->getRedirect('*/*/');
}
- // try to confirm by email
$email = $this->getRequest()->getPost('email');
- if ($email) {
- /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */
- $resultRedirect = $this->resultRedirectFactory->create();
+ if ($email) {
try {
$this->customerAccountManagement->resendConfirmation(
$email,
$this->storeManager->getStore()->getWebsiteId()
);
$this->messageManager->addSuccessMessage(__('Please check your email for confirmation key.'));
+ return $this->getRedirect('*/*/index', ['_secure' => true]);
} catch (InvalidTransitionException $e) {
$this->messageManager->addSuccessMessage(__('This email does not require confirmation.'));
- } catch (\Exception $e) {
- $this->messageManager->addExceptionMessage($e, __('Wrong email.'));
- $resultRedirect->setPath('*/*/*', ['email' => $email, '_secure' => true]);
- return $resultRedirect;
+ return $this->getRedirect('*/*/index', ['_secure' => true]);
+ } catch (NoSuchEntityException $e) {
+ $this->messageManager->addErrorMessage(__('Wrong email.'));
}
- $this->session->setUsername($email);
- $resultRedirect->setPath('*/*/index', ['_secure' => true]);
- return $resultRedirect;
}
- /** @var \Magento\Framework\View\Result\Page $resultPage */
$resultPage = $this->resultPageFactory->create();
- $resultPage->getLayout()->getBlock('accountConfirmation')->setEmail(
- $this->getRequest()->getParam('email', $email)
- )->setLoginUrl(
- $this->customerUrl->getLoginUrl()
- );
+ $resultPage->getLayout()->getBlock('accountConfirmation')
+ ->setEmail($email)
+ ->setLoginUrl($this->customerUrl->getLoginUrl());
return $resultPage;
}
+
+ /**
+ * Returns redirect object
+ *
+ * @param string $path
+ * @param array $params
+ * @return Redirect
+ */
+ private function getRedirect(string $path, array $params = []): Redirect
+ {
+ $resultRedirect = $this->resultRedirectFactory->create();
+ $resultRedirect->setPath($path, $params);
+
+ return $resultRedirect;
+ }
}
diff --git a/app/code/Magento/Customer/Controller/Account/ForgotPasswordPost.php b/app/code/Magento/Customer/Controller/Account/ForgotPasswordPost.php
index 3c7fca99184d0..c439d4649987f 100644
--- a/app/code/Magento/Customer/Controller/Account/ForgotPasswordPost.php
+++ b/app/code/Magento/Customer/Controller/Account/ForgotPasswordPost.php
@@ -1,6 +1,5 @@
resultRedirectFactory->create();
$email = (string)$this->getRequest()->getPost('email');
if ($email) {
- if (!\Zend_Validate::is($email, \Magento\Framework\Validator\EmailAddress::class)) {
+ if (!ValidatorChain::is($email, EmailAddress::class)) {
$this->session->setForgottenEmail($email);
$this->messageManager->addErrorMessage(
__('The email address is incorrect. Verify the email address and try again.')
@@ -78,6 +80,7 @@ public function execute()
$email,
AccountManagement::EMAIL_RESET
);
+ // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch
} catch (NoSuchEntityException $exception) {
// Do nothing, we don't want anyone to use this action to determine which email accounts are registered.
} catch (SecurityViolationException $exception) {
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
index 47e0ee03e135f..31971795d25d7 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
@@ -46,6 +46,7 @@
use Magento\Framework\View\Result\PageFactory;
use Magento\Newsletter\Model\SubscriberFactory;
use Magento\Newsletter\Model\SubscriptionManagerInterface;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Save customer action.
@@ -69,6 +70,11 @@ class Save extends \Magento\Customer\Controller\Adminhtml\Index implements HttpP
*/
private $addressRegistry;
+ /**
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
+
/**
* Constructor
*
@@ -99,6 +105,7 @@ class Save extends \Magento\Customer\Controller\Adminhtml\Index implements HttpP
* @param JsonFactory $resultJsonFactory
* @param SubscriptionManagerInterface $subscriptionManager
* @param AddressRegistry|null $addressRegistry
+ * @param StoreManagerInterface|null $storeManager
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -128,7 +135,8 @@ public function __construct(
ForwardFactory $resultForwardFactory,
JsonFactory $resultJsonFactory,
SubscriptionManagerInterface $subscriptionManager,
- AddressRegistry $addressRegistry = null
+ AddressRegistry $addressRegistry = null,
+ ?StoreManagerInterface $storeManager = null
) {
parent::__construct(
$context,
@@ -159,6 +167,7 @@ public function __construct(
);
$this->subscriptionManager = $subscriptionManager;
$this->addressRegistry = $addressRegistry ?: ObjectManager::getInstance()->get(AddressRegistry::class);
+ $this->storeManager = $storeManager ?? ObjectManager::getInstance()->get(StoreManagerInterface::class);
}
/**
@@ -249,6 +258,7 @@ protected function _extractData(
* @param array $extractedCustomerData
* @return array
* @deprecated 102.0.1 must be removed because addresses are save separately for now
+ * @see \Magento\Customer\Controller\Adminhtml\Address\Save
*/
protected function saveDefaultFlags(array $addressIdList, array &$extractedCustomerData)
{
@@ -291,6 +301,7 @@ protected function saveDefaultFlags(array $addressIdList, array &$extractedCusto
* @param array $extractedCustomerData
* @return array
* @deprecated 102.0.1 addresses are saved separately for now
+ * @see \Magento\Customer\Controller\Adminhtml\Address\Save
*/
protected function _extractCustomerAddressData(array &$extractedCustomerData)
{
@@ -359,6 +370,13 @@ public function execute()
}
}
+ $storeId = $customer->getStoreId();
+ if (empty($storeId)) {
+ $website = $this->storeManager->getWebsite($customer->getWebsiteId());
+ $storeId = current($website->getStoreIds());
+ }
+ $this->storeManager->setCurrentStore($storeId);
+
// Save customer
if ($customerId) {
$this->_customerRepository->save($customer);
@@ -465,6 +483,7 @@ private function updateSubscriptions(CustomerInterface $customer): void
*
* @return EmailNotificationInterface
* @deprecated 100.1.0
+ * @see no alternative
*/
private function getEmailNotification()
{
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Validate.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Validate.php
index d91bc7424bffe..41ca037e62686 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Validate.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Validate.php
@@ -5,22 +5,128 @@
*/
namespace Magento\Customer\Controller\Adminhtml\Index;
+use Magento\Customer\Api\AccountManagementInterface;
+use Magento\Customer\Api\AddressRepositoryInterface;
+use Magento\Customer\Api\CustomerRepositoryInterface;
+use Magento\Customer\Api\Data\AddressInterfaceFactory;
+use Magento\Customer\Api\Data\CustomerInterfaceFactory;
+use Magento\Customer\Model\Address\Mapper;
+use Magento\Framework\Api\DataObjectHelper;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Customer\Api\Data\CustomerInterface;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\DataObjectFactory as ObjectFactory;
use Magento\Framework\Message\Error;
use Magento\Customer\Controller\Adminhtml\Index as CustomerAction;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Class for validation of customer
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Validate extends CustomerAction implements HttpPostActionInterface, HttpGetActionInterface
{
+ /**
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
+
+ /**
+ * @param \Magento\Backend\App\Action\Context $context
+ * @param \Magento\Framework\Registry $coreRegistry
+ * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory
+ * @param \Magento\Customer\Model\CustomerFactory $customerFactory
+ * @param \Magento\Customer\Model\AddressFactory $addressFactory
+ * @param \Magento\Customer\Model\Metadata\FormFactory $formFactory
+ * @param \Magento\Newsletter\Model\SubscriberFactory $subscriberFactory
+ * @param \Magento\Customer\Helper\View $viewHelper
+ * @param \Magento\Framework\Math\Random $random
+ * @param CustomerRepositoryInterface $customerRepository
+ * @param \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter
+ * @param Mapper $addressMapper
+ * @param AccountManagementInterface $customerAccountManagement
+ * @param AddressRepositoryInterface $addressRepository
+ * @param CustomerInterfaceFactory $customerDataFactory
+ * @param AddressInterfaceFactory $addressDataFactory
+ * @param \Magento\Customer\Model\Customer\Mapper $customerMapper
+ * @param \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor
+ * @param DataObjectHelper $dataObjectHelper
+ * @param ObjectFactory $objectFactory
+ * @param \Magento\Framework\View\LayoutFactory $layoutFactory
+ * @param \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory
+ * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
+ * @param \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory
+ * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
+ * @param StoreManagerInterface|null $storeManager
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
+ */
+ public function __construct(
+ \Magento\Backend\App\Action\Context $context,
+ \Magento\Framework\Registry $coreRegistry,
+ \Magento\Framework\App\Response\Http\FileFactory $fileFactory,
+ \Magento\Customer\Model\CustomerFactory $customerFactory,
+ \Magento\Customer\Model\AddressFactory $addressFactory,
+ \Magento\Customer\Model\Metadata\FormFactory $formFactory,
+ \Magento\Newsletter\Model\SubscriberFactory $subscriberFactory,
+ \Magento\Customer\Helper\View $viewHelper,
+ \Magento\Framework\Math\Random $random,
+ CustomerRepositoryInterface $customerRepository,
+ \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter,
+ Mapper $addressMapper,
+ AccountManagementInterface $customerAccountManagement,
+ AddressRepositoryInterface $addressRepository,
+ CustomerInterfaceFactory $customerDataFactory,
+ AddressInterfaceFactory $addressDataFactory,
+ \Magento\Customer\Model\Customer\Mapper $customerMapper,
+ \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor,
+ DataObjectHelper $dataObjectHelper,
+ ObjectFactory $objectFactory,
+ \Magento\Framework\View\LayoutFactory $layoutFactory,
+ \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory,
+ \Magento\Framework\View\Result\PageFactory $resultPageFactory,
+ \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory,
+ \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
+ ?StoreManagerInterface $storeManager = null
+ ) {
+ parent::__construct(
+ $context,
+ $coreRegistry,
+ $fileFactory,
+ $customerFactory,
+ $addressFactory,
+ $formFactory,
+ $subscriberFactory,
+ $viewHelper,
+ $random,
+ $customerRepository,
+ $extensibleDataObjectConverter,
+ $addressMapper,
+ $customerAccountManagement,
+ $addressRepository,
+ $customerDataFactory,
+ $addressDataFactory,
+ $customerMapper,
+ $dataObjectProcessor,
+ $dataObjectHelper,
+ $objectFactory,
+ $layoutFactory,
+ $resultLayoutFactory,
+ $resultPageFactory,
+ $resultForwardFactory,
+ $resultJsonFactory
+ );
+
+ $this->storeManager = $storeManager ?? ObjectManager::getInstance()->get(StoreManagerInterface::class);
+ }
+
/**
* Customer validation
*
* @param \Magento\Framework\DataObject $response
* @return CustomerInterface|null
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function _validateCustomer($response)
{
@@ -55,6 +161,11 @@ protected function _validateCustomer($response)
$entity_id = $submittedData['entity_id'];
$customer->setId($entity_id);
}
+ if (isset($data['website_id']) && is_numeric($data['website_id'])) {
+ $website = $this->storeManager->getWebsite($data['website_id']);
+ $storeId = current($website->getStoreIds());
+ $this->storeManager->setCurrentStore($storeId);
+ }
$errors = $this->customerAccountManagement->validate($customer)->getMessages();
} catch (\Magento\Framework\Validator\Exception $exception) {
/* @var $error Error */
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/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php
index 139774a774f88..d5689fd2b8c08 100644
--- a/app/code/Magento/Customer/Model/AccountManagement.php
+++ b/app/code/Magento/Customer/Model/AccountManagement.php
@@ -726,6 +726,9 @@ public function resetPassword($email, $resetToken, $newPassword)
$customerSecure->setRpToken(null);
$customerSecure->setRpTokenCreatedAt(null);
$customerSecure->setPasswordHash($this->createPasswordHash($newPassword));
+ $customerSecure->setFailuresNum(0);
+ $customerSecure->setFirstFailure(null);
+ $customerSecure->setLockExpires(null);
$this->sessionCleaner->clearFor((int)$customer->getId());
$this->customerRepository->save($customer);
@@ -1216,6 +1219,7 @@ public function isReadonly($customerId)
* @return $this
* @throws LocalizedException
* @deprecated 100.1.0
+ * @see MAGETWO-71174
*/
protected function sendNewAccountEmail(
$customer,
@@ -1259,6 +1263,7 @@ protected function sendNewAccountEmail(
* @throws LocalizedException
* @throws NoSuchEntityException
* @deprecated 100.1.0
+ * @see MAGETWO-71174
*/
protected function sendPasswordResetNotificationEmail($customer)
{
@@ -1272,6 +1277,7 @@ protected function sendPasswordResetNotificationEmail($customer)
* @param int|string|null $defaultStoreId
* @return int
* @deprecated 100.1.0
+ * @see MAGETWO-71174
* @throws LocalizedException
*/
protected function getWebsiteStoreId($customer, $defaultStoreId = null)
@@ -1289,6 +1295,7 @@ protected function getWebsiteStoreId($customer, $defaultStoreId = null)
*
* @return array
* @deprecated 100.1.0
+ * @see MAGETWO-71174
*/
protected function getTemplateTypes()
{
@@ -1322,6 +1329,7 @@ protected function getTemplateTypes()
* @return $this
* @throws MailException
* @deprecated 100.1.0
+ * @see MAGETWO-71174
*/
protected function sendEmailTemplate(
$customer,
@@ -1476,6 +1484,7 @@ public function changeResetPasswordLinkToken(CustomerInterface $customer, string
* @throws LocalizedException
* @throws NoSuchEntityException
* @deprecated 100.1.0
+ * @see MAGETWO-71174
*/
public function sendPasswordReminderEmail($customer)
{
@@ -1505,6 +1514,7 @@ public function sendPasswordReminderEmail($customer)
* @throws LocalizedException
* @throws NoSuchEntityException
* @deprecated 100.1.0
+ * @see MAGETWO-71174
*/
public function sendPasswordResetConfirmationEmail($customer)
{
@@ -1550,6 +1560,7 @@ protected function getAddressById(CustomerInterface $customer, $addressId)
* @return Data\CustomerSecure
* @throws NoSuchEntityException
* @deprecated 100.1.0
+ * @see MAGETWO-71174
*/
protected function getFullCustomerObject($customer)
{
@@ -1595,6 +1606,7 @@ private function disableAddressValidation($customer)
*
* @return EmailNotificationInterface
* @deprecated 100.1.0
+ * @see MAGETWO-71174
*/
private function getEmailNotification()
{
diff --git a/app/code/Magento/Customer/Model/Address/AbstractAddress.php b/app/code/Magento/Customer/Model/Address/AbstractAddress.php
index bdf29d6e93b75..f710ef6846fd6 100644
--- a/app/code/Magento/Customer/Model/Address/AbstractAddress.php
+++ b/app/code/Magento/Customer/Model/Address/AbstractAddress.php
@@ -120,6 +120,11 @@ class AbstractAddress extends AbstractExtensibleModel implements AddressModelInt
/** @var CompositeValidator */
private $compositeValidator;
+ /**
+ * @var array
+ */
+ private array $regionIdCountry = [];
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -192,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');
@@ -201,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;
}
@@ -398,7 +403,13 @@ public function getRegionCode()
$region = $this->getData('region');
if (!$regionId && is_numeric($region)) {
- if ($this->getRegionModel($region)->getCountryId() == $this->getCountryId()) {
+ $regionId = $this->getRegionIdByCode(
+ (string)$region,
+ (string)$this->getCountryId()
+ );
+ if ($regionId) {
+ $this->setData('region_code', $region);
+ } elseif ($this->getRegionModel($region)->getCountryId() == $this->getCountryId()) {
$this->setData('region_code', $this->getRegionModel($region)->getCode());
}
} elseif ($regionId) {
@@ -419,20 +430,53 @@ public function getRegionCode()
public function getRegionId()
{
$regionId = $this->getData('region_id');
+ if ($regionId) {
+ return $regionId;
+ }
+
$region = $this->getData('region');
- if (!$regionId) {
- if (is_numeric($region)) {
- $this->setData('region_id', $region);
+ if (is_numeric($region)) {
+ $regionId = $this->getRegionIdByCode(
+ (string)$region,
+ (string)$this->getCountryId()
+ );
+ if ($regionId) {
+ $this->setData('region_id', $regionId);
$this->unsRegion();
} else {
- $regionModel = $this->_createRegionInstance()->loadByCode(
- $this->getRegionCode(),
- $this->getCountryId()
- );
- $this->setData('region_id', $regionModel->getId());
+ $this->setData('region_id', $region);
}
+ } else {
+ $regionId = $this->getRegionIdByCode(
+ (string)$this->getRegionCode(),
+ (string)$this->getCountryId()
+ );
+ $this->setData('region_id', $regionId);
}
- return $this->getData('region_id');
+
+ return $regionId;
+ }
+
+ /**
+ * Returns region id.
+ *
+ * @param string $regionCode
+ * @param string $countryId
+ * @return int|null
+ */
+ private function getRegionIdByCode(string $regionCode, string $countryId): ?int
+ {
+ $key = $countryId . '_' . $regionCode;
+ if (!array_key_exists($key, $this->regionIdCountry)) {
+ $regionModel = $this->_createRegionInstance()->loadByCode(
+ $regionCode,
+ $countryId
+ );
+
+ $this->regionIdCountry[$key] = $regionModel->getId() ? (int)$regionModel->getId() : null;
+ }
+
+ return $this->regionIdCountry[$key];
}
/**
diff --git a/app/code/Magento/Customer/Model/Address/Validator/Country.php b/app/code/Magento/Customer/Model/Address/Validator/Country.php
index e0eb1cbadd862..88047b6a53cc6 100644
--- a/app/code/Magento/Customer/Model/Address/Validator/Country.php
+++ b/app/code/Magento/Customer/Model/Address/Validator/Country.php
@@ -11,6 +11,9 @@
use Magento\Directory\Model\AllowedCountries;
use Magento\Framework\Escaper;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Validator\NotEmpty;
+use Magento\Framework\Validator\ValidateException;
+use Magento\Framework\Validator\ValidatorChain;
use Magento\Store\Model\ScopeInterface;
/**
@@ -73,7 +76,7 @@ private function validateCountry(AbstractAddress $address)
{
$countryId = $address->getCountryId();
$errors = [];
- if (!\Zend_Validate::is($countryId, 'NotEmpty')) {
+ if (!ValidatorChain::is($countryId, NotEmpty::class)) {
$errors[] = __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'countryId']);
} elseif (!in_array($countryId, $this->getWebsiteAllowedCountries($address), true)) {
//Checking if such country exists.
@@ -91,7 +94,7 @@ private function validateCountry(AbstractAddress $address)
*
* @param AbstractAddress $address
* @return array
- * @throws \Zend_Validate_Exception
+ * @throws ValidateException
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
private function validateRegion(AbstractAddress $address)
@@ -104,11 +107,11 @@ private function validateRegion(AbstractAddress $address)
$regionId = (string)$address->getRegionId();
$allowedRegions = $regionCollection->getAllIds();
$isRegionRequired = $this->directoryData->isRegionRequired($countryId);
- if ($isRegionRequired && empty($allowedRegions) && !\Zend_Validate::is($region, 'NotEmpty')) {
+ if ($isRegionRequired && empty($allowedRegions) && !ValidatorChain::is($region, NotEmpty::class)) {
//If region is required for country and country doesn't provide regions list
//region must be provided.
$errors[] = __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'region']);
- } elseif ($allowedRegions && !\Zend_Validate::is($regionId, 'NotEmpty') && $isRegionRequired) {
+ } elseif ($allowedRegions && !ValidatorChain::is($regionId, NotEmpty::class) && $isRegionRequired) {
//If country actually has regions and requires you to
//select one then it must be selected.
$errors[] = __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'regionId']);
diff --git a/app/code/Magento/Customer/Model/Address/Validator/General.php b/app/code/Magento/Customer/Model/Address/Validator/General.php
index 4888cd227db48..23c6d687328f3 100644
--- a/app/code/Magento/Customer/Model/Address/Validator/General.php
+++ b/app/code/Magento/Customer/Model/Address/Validator/General.php
@@ -8,6 +8,10 @@
use Magento\Customer\Api\AddressMetadataInterface;
use Magento\Customer\Model\Address\AbstractAddress;
use Magento\Customer\Model\Address\ValidatorInterface;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Validator\NotEmpty;
+use Magento\Framework\Validator\ValidateException;
+use Magento\Framework\Validator\ValidatorChain;
/**
* Address general fields validator.
@@ -54,24 +58,24 @@ public function validate(AbstractAddress $address)
*
* @param AbstractAddress $address
* @return array
- * @throws \Zend_Validate_Exception
+ * @throws ValidateException
*/
private function checkRequiredFields(AbstractAddress $address)
{
$errors = [];
- if (!\Zend_Validate::is($address->getFirstname(), 'NotEmpty')) {
+ if (!ValidatorChain::is($address->getFirstname(), NotEmpty::class)) {
$errors[] = __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'firstname']);
}
- if (!\Zend_Validate::is($address->getLastname(), 'NotEmpty')) {
+ if (!ValidatorChain::is($address->getLastname(), NotEmpty::class)) {
$errors[] = __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'lastname']);
}
- if (!\Zend_Validate::is($address->getStreetLine(1), 'NotEmpty')) {
+ if (!ValidatorChain::is($address->getStreetLine(1), NotEmpty::class)) {
$errors[] = __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'street']);
}
- if (!\Zend_Validate::is($address->getCity(), 'NotEmpty')) {
+ if (!ValidatorChain::is($address->getCity(), NotEmpty::class)) {
$errors[] = __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'city']);
}
@@ -83,34 +87,33 @@ private function checkRequiredFields(AbstractAddress $address)
*
* @param AbstractAddress $address
* @return array
- * @throws \Magento\Framework\Exception\LocalizedException
- * @throws \Zend_Validate_Exception
+ * @throws LocalizedException|ValidateException
*/
private function checkOptionalFields(AbstractAddress $address)
{
$this->reloadAddressAttributes($address);
$errors = [];
if ($this->isTelephoneRequired()
- && !\Zend_Validate::is($address->getTelephone(), 'NotEmpty')
+ && !ValidatorChain::is($address->getTelephone(), NotEmpty::class)
) {
$errors[] = __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'telephone']);
}
if ($this->isFaxRequired()
- && !\Zend_Validate::is($address->getFax(), 'NotEmpty')
+ && !ValidatorChain::is($address->getFax(), NotEmpty::class)
) {
$errors[] = __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'fax']);
}
if ($this->isCompanyRequired()
- && !\Zend_Validate::is($address->getCompany(), 'NotEmpty')
+ && !ValidatorChain::is($address->getCompany(), NotEmpty::class)
) {
$errors[] = __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'company']);
}
$havingOptionalZip = $this->directoryData->getCountriesWithOptionalZip();
if (!in_array($address->getCountryId(), $havingOptionalZip)
- && !\Zend_Validate::is($address->getPostcode(), 'NotEmpty')
+ && !ValidatorChain::is($address->getPostcode(), NotEmpty::class)
) {
$errors[] = __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'postcode']);
}
@@ -122,7 +125,7 @@ private function checkOptionalFields(AbstractAddress $address)
* Check if company field required in configuration.
*
* @return bool
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException
*/
private function isCompanyRequired()
{
@@ -133,7 +136,7 @@ private function isCompanyRequired()
* Check if telephone field required in configuration.
*
* @return bool
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException
*/
private function isTelephoneRequired()
{
@@ -144,7 +147,7 @@ private function isTelephoneRequired()
* Check if fax field required in configuration.
*
* @return bool
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException
*/
private function isFaxRequired()
{
diff --git a/app/code/Magento/Customer/Model/AttributeMetadataResolver.php b/app/code/Magento/Customer/Model/AttributeMetadataResolver.php
index 27c5f77674577..38ded6be996c0 100644
--- a/app/code/Magento/Customer/Model/AttributeMetadataResolver.php
+++ b/app/code/Magento/Customer/Model/AttributeMetadataResolver.php
@@ -37,6 +37,7 @@ class AttributeMetadataResolver
'notice' => 'note',
'default' => 'default_value',
'size' => 'multiline_count',
+ 'attributeId' => 'attribute_id',
];
/**
@@ -80,6 +81,11 @@ class AttributeMetadataResolver
*/
private $groupManagement;
+ /**
+ * @var AttributeWebsiteRequired|null
+ */
+ private ?AttributeWebsiteRequired $attributeWebsiteRequired;
+
/**
* @param CountryWithWebsites $countryWithWebsiteSource
* @param EavValidationRules $eavValidationRules
@@ -87,6 +93,7 @@ class AttributeMetadataResolver
* @param ContextInterface $context
* @param ShareConfig $shareConfig
* @param GroupManagement|null $groupManagement
+ * @param AttributeWebsiteRequired|null $attributeWebsiteRequired
*/
public function __construct(
CountryWithWebsites $countryWithWebsiteSource,
@@ -94,7 +101,8 @@ public function __construct(
FileUploaderDataResolver $fileUploaderDataResolver,
ContextInterface $context,
ShareConfig $shareConfig,
- ?GroupManagement $groupManagement = null
+ ?GroupManagement $groupManagement = null,
+ ?AttributeWebsiteRequired $attributeWebsiteRequired = null
) {
$this->countryWithWebsiteSource = $countryWithWebsiteSource;
$this->eavValidationRules = $eavValidationRules;
@@ -102,6 +110,8 @@ public function __construct(
$this->context = $context;
$this->shareConfig = $shareConfig;
$this->groupManagement = $groupManagement ?? ObjectManager::getInstance()->get(GroupManagement::class);
+ $this->attributeWebsiteRequired = $attributeWebsiteRequired ??
+ ObjectManager::getInstance()->get(AttributeWebsiteRequired::class);
}
/**
@@ -215,7 +225,7 @@ private function modifyGroupAttributeMeta(AttributeInterface $attribute): void
{
if ($attribute->getAttributeCode() === 'group_id') {
$defaultGroup = $this->groupManagement->getDefaultGroup();
- $defaultGroupId = !empty($defaultGroup) ? $defaultGroup->getId() : null;
+ $defaultGroupId = $defaultGroup->getId();
$attribute->setDataUsingMethod(self::$metaProperties['default'], $defaultGroupId);
}
}
@@ -238,5 +248,53 @@ public function processWebsiteMeta(&$meta): void
'field' => 'website_ids'
];
}
+
+ if (isset($meta[CustomerInterface::WEBSITE_ID])) {
+ $this->processWebsiteIsRequired($meta);
+ }
+ }
+
+ /**
+ * Adds attribute 'required' validation according to the scope.
+ *
+ * @param array $meta
+ * @return void
+ */
+ private function processWebsiteIsRequired(&$meta): void
+ {
+ $attributeIds = array_values(
+ array_map(
+ function ($attribute) {
+ return $attribute['arguments']['data']['config']['attributeId'];
+ },
+ array_filter(
+ $meta,
+ function ($attribute) {
+ return isset($attribute['arguments']['data']['config']['attributeId']);
+ }
+ )
+ )
+ );
+ $websiteIds = array_values(
+ array_map(
+ function ($option) {
+ return (int)$option['value'];
+ },
+ $meta[CustomerInterface::WEBSITE_ID]['arguments']['data']['config']['options']
+ )
+ );
+
+ $websiteRequired = $this->attributeWebsiteRequired->get($attributeIds, $websiteIds);
+ array_walk(
+ $meta,
+ function (&$attribute) use ($websiteRequired) {
+ $id = $attribute['arguments']['data']['config']['attributeId'];
+ unset($attribute['arguments']['data']['config']['attributeId']);
+ if (!empty($websiteRequired[$id])) {
+ $attribute['arguments']['data']['config']
+ ['validation']['required-entry-website'] = $websiteRequired[$id];
+ }
+ }
+ );
}
}
diff --git a/app/code/Magento/Customer/Model/AttributeWebsiteRequired.php b/app/code/Magento/Customer/Model/AttributeWebsiteRequired.php
new file mode 100644
index 0000000000000..9e63bdb2fd98a
--- /dev/null
+++ b/app/code/Magento/Customer/Model/AttributeWebsiteRequired.php
@@ -0,0 +1,72 @@
+attribute = $attribute;
+ }
+
+ /**
+ * Returns the attributes value 'is_required' for all websites.
+ *
+ * @param array $attributeIds
+ * @param array $websiteIds
+ * @return array
+ */
+ public function get(array $attributeIds, array $websiteIds): array
+ {
+ $defaultScope = 0;
+ $connection = $this->attribute->getConnection();
+ $selects[] = $connection->select()->from(
+ [$this->attribute->getTable('customer_eav_attribute_website')],
+ ['attribute_id', 'website_id', 'is_required']
+ )->where('attribute_id IN (?) AND is_required IS NOT NULL', $attributeIds);
+
+ $selects[] = $connection->select()->from(
+ [$this->attribute->getTable('eav_attribute')],
+ ['attribute_id', 'website_id' => new \Zend_Db_Expr($defaultScope), 'is_required']
+ )->where('attribute_id IN (?) AND is_required IS NOT NULL', $attributeIds);
+
+ $unionSelect = new UnionExpression($selects, Select::SQL_UNION_ALL);
+ $data = $connection->fetchAll($unionSelect);
+ $isRequired = [];
+ foreach ($data as $row) {
+ $isRequired[$row['website_id']][$row['attribute_id']] = (bool)$row['is_required'];
+ }
+
+ $result = [];
+ foreach ($attributeIds as $attributeId) {
+ foreach ($websiteIds as $websiteId) {
+ if (isset($isRequired[$websiteId][$attributeId])) {
+ if ($isRequired[$websiteId][$attributeId]) {
+ $result[$attributeId][] = $websiteId;
+ }
+ } elseif ($isRequired[$defaultScope][$attributeId]) {
+ $result[$attributeId][] = $websiteId;
+ }
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/app/code/Magento/Customer/Model/Config/Backend/Show/Customer.php b/app/code/Magento/Customer/Model/Config/Backend/Show/Customer.php
index eefbfc4b68d70..f4418c2832855 100644
--- a/app/code/Magento/Customer/Model/Config/Backend/Show/Customer.php
+++ b/app/code/Magento/Customer/Model/Config/Backend/Show/Customer.php
@@ -15,6 +15,8 @@
*/
class Customer extends \Magento\Framework\App\Config\Value
{
+ public const XML_PATH_CUSTOMER_ADDRESS_SHOW_COMPANY = 'customer/address/company_show';
+
/**
* @var \Magento\Eav\Model\Config
*/
diff --git a/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php b/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php
index a3617ac4e4e79..7153df5071b2f 100644
--- a/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php
+++ b/app/code/Magento/Customer/Model/Customer/DataProviderWithDefaultAddresses.php
@@ -6,6 +6,7 @@
*/
namespace Magento\Customer\Model\Customer;
+use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Model\Address;
use Magento\Customer\Model\Customer;
use Magento\Customer\Model\CustomerFactory;
@@ -19,11 +20,14 @@
use Magento\Framework\Session\SessionManagerInterface;
use Magento\Customer\Model\FileUploaderDataResolver;
use Magento\Customer\Model\AttributeMetadataResolver;
+use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Ui\Component\Form\Element\Multiline;
use Magento\Ui\DataProvider\AbstractDataProvider;
/**
* Refactored version of Magento\Customer\Model\Customer\DataProvider with eliminated usage of addresses collection.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class DataProviderWithDefaultAddresses extends AbstractDataProvider
{
@@ -74,6 +78,16 @@ class DataProviderWithDefaultAddresses extends AbstractDataProvider
*/
private $customerFactory;
+ /**
+ * @var ContextInterface
+ */
+ private $context;
+
+ /**
+ * @var CustomerRepositoryInterface
+ */
+ private $customerRepository;
+
/**
* @param string $name
* @param string $primaryFieldName
@@ -87,7 +101,9 @@ class DataProviderWithDefaultAddresses extends AbstractDataProvider
* @param bool $allowToShowHiddenAttributes
* @param array $meta
* @param array $data
- * @param CustomerFactory $customerFactory
+ * @param CustomerFactory|null $customerFactory
+ * @param ContextInterface|null $context
+ * @param CustomerRepositoryInterface|null $customerRepository
* @throws LocalizedException
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -104,7 +120,9 @@ public function __construct(
$allowToShowHiddenAttributes = true,
array $meta = [],
array $data = [],
- CustomerFactory $customerFactory = null
+ CustomerFactory $customerFactory = null,
+ ?ContextInterface $context = null,
+ CustomerRepositoryInterface $customerRepository = null
) {
parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
$this->collection = $customerCollectionFactory->create();
@@ -114,10 +132,13 @@ public function __construct(
$this->countryFactory = $countryFactory;
$this->fileUploaderDataResolver = $fileUploaderDataResolver;
$this->attributeMetadataResolver = $attributeMetadataResolver;
+ $this->context = $context ?? ObjectManager::getInstance()->get(ContextInterface::class);
+ $this->customerFactory = $customerFactory ?: ObjectManager::getInstance()->get(CustomerFactory::class);
+ $this->customerRepository = $customerRepository ??
+ ObjectManager::getInstance()->get(CustomerRepositoryInterface::class);
$this->meta['customer']['children'] = $this->getAttributesMeta(
$eavConfig->getEntityType('customer')
);
- $this->customerFactory = $customerFactory ?: ObjectManager::getInstance()->get(CustomerFactory::class);
}
/**
@@ -142,7 +163,6 @@ public function getData(): array
array_flip(self::$forbiddenCustomerFields)
);
$this->prepareCustomAttributeValue($result['customer']);
- unset($result['address']);
$result['default_billing_address'] = $this->prepareDefaultAddress(
$customer->getDefaultBillingAddress()
@@ -221,6 +241,11 @@ private function getAttributesMeta(Type $entityType): array
{
$meta = [];
$attributes = $entityType->getAttributeCollection();
+ $customerId = $this->context->getRequestParam('id');
+ if ($customerId) {
+ $customer = $this->customerRepository->getById($customerId);
+ $attributes->setWebsite($customer->getWebsiteId());
+ }
/* @var AbstractAttribute $attribute */
foreach ($attributes as $attribute) {
$meta[$attribute->getAttributeCode()] = $this->attributeMetadataResolver->getAttributesMeta(
diff --git a/app/code/Magento/Customer/Model/FileUploaderDataResolver.php b/app/code/Magento/Customer/Model/FileUploaderDataResolver.php
index b9e9125724894..c0810e6adb32b 100644
--- a/app/code/Magento/Customer/Model/FileUploaderDataResolver.php
+++ b/app/code/Magento/Customer/Model/FileUploaderDataResolver.php
@@ -100,7 +100,7 @@ private function getFileUploaderData(
[
'file' => $file,
'size' => null !== $stat ? $stat['size'] : 0,
- 'url' => $viewUrl ?? '',
+ 'url' => $viewUrl,
// phpcs:ignore Magento2.Functions.DiscouragedFunction
'name' => basename($file),
'type' => $fileProcessor->getMimeType($file),
@@ -164,6 +164,7 @@ function (&$value) {
'required' => $this->getMetadataValue($config, 'required'),
'visible' => $this->getMetadataValue($config, 'visible'),
'validation' => $this->getMetadataValue($config, 'validation'),
+ 'attributeId' => $this->getMetadataValue($config, 'attributeId'),
];
}
}
diff --git a/app/code/Magento/Customer/Model/Metadata/Form.php b/app/code/Magento/Customer/Model/Metadata/Form.php
index 81ded6dec071a..976f5925a6f04 100644
--- a/app/code/Magento/Customer/Model/Metadata/Form.php
+++ b/app/code/Magento/Customer/Model/Metadata/Form.php
@@ -19,13 +19,13 @@ class Form
/**#@+
* Values for ignoreInvisible parameter in constructor
*/
- const IGNORE_INVISIBLE = true;
+ public const IGNORE_INVISIBLE = true;
- const DONT_IGNORE_INVISIBLE = false;
+ public const DONT_IGNORE_INVISIBLE = false;
- /**#@-*/
-
- /**#@-*/
+ /**
+ * @var CustomerMetadataInterface
+ */
protected $_customerMetadataService;
/**
@@ -64,8 +64,6 @@ class Form
protected $_isAjax = false;
/**
- * Attribute values
- *
* @var array
*/
protected $_attributeValues = [];
@@ -365,7 +363,7 @@ public function validateData(array $data)
if (!$validator->isValid(false)) {
$messages = [];
foreach ($validator->getMessages() as $errorMessages) {
- $messages[] = (array)$errorMessages;
+ $messages[] = array_values((array)$errorMessages);
}
return array_merge([], ...$messages);
}
diff --git a/app/code/Magento/Customer/Model/Metadata/Form/AbstractData.php b/app/code/Magento/Customer/Model/Metadata/Form/AbstractData.php
index f3c77b6514a5c..2f7b18705fc54 100644
--- a/app/code/Magento/Customer/Model/Metadata/Form/AbstractData.php
+++ b/app/code/Magento/Customer/Model/Metadata/Form/AbstractData.php
@@ -1,15 +1,18 @@
setMessage(__('"%1" invalid type entered.', $label), \Zend_Validate_Alnum::INVALID);
+ $validator = new Alnum($allowWhiteSpace);
+ $validator->setMessage(__('"%1" invalid type entered.', $label), Alnum::INVALID);
$validator->setMessage(
__('"%1" contains non-alphabetic or non-numeric characters.', $label),
- \Zend_Validate_Alnum::NOT_ALNUM
+ Alnum::NOT_ALNUM
);
- $validator->setMessage(__('"%1" is an empty string.', $label), \Zend_Validate_Alnum::STRING_EMPTY);
+ $validator->setMessage(__('"%1" is an empty string.', $label), Alnum::STRING_EMPTY);
if (!$validator->isValid($value)) {
return $validator->getMessages();
}
break;
case 'numeric':
- $validator = new \Zend_Validate_Digits();
- $validator->setMessage(__('"%1" invalid type entered.', $label), \Zend_Validate_Digits::INVALID);
+ $validator = new Digits();
+ $validator->setMessage(__('"%1" invalid type entered.', $label), Digits::INVALID);
$validator->setMessage(
__('"%1" contains non-numeric characters.', $label),
- \Zend_Validate_Digits::NOT_DIGITS
+ Digits::NOT_DIGITS
);
$validator->setMessage(
__('"%1" is an empty string.', $label),
- \Zend_Validate_Digits::STRING_EMPTY
+ Digits::STRING_EMPTY
);
if (!$validator->isValid($value)) {
return $validator->getMessages();
}
break;
case 'alpha':
- $validator = new \Zend_Validate_Alpha(true);
- $validator->setMessage(__('"%1" invalid type entered.', $label), \Zend_Validate_Alpha::INVALID);
+ $validator = new Alpha(true);
+ $validator->setMessage(__('"%1" invalid type entered.', $label), Alpha::INVALID);
$validator->setMessage(
__('"%1" contains non-alphabetic characters.', $label),
- \Zend_Validate_Alpha::NOT_ALPHA
+ Alpha::NOT_ALPHA
);
- $validator->setMessage(__('"%1" is an empty string.', $label), \Zend_Validate_Alpha::STRING_EMPTY);
+ $validator->setMessage(__('"%1" is an empty string.', $label), Alpha::STRING_EMPTY);
if (!$validator->isValid($value)) {
return $validator->getMessages();
}
@@ -364,74 +365,75 @@ protected function _validateInputRule($value)
__("'%value%' appears to be a DNS hostname but cannot match TLD against known list")
*/
$validator = new EmailAddress();
+ $validator->setOptions(['hostnameValidator' => new Hostname()]);
$validator->setMessage(
__('"%1" invalid type entered.', $label),
- \Zend_Validate_EmailAddress::INVALID
+ EmailAddress::INVALID
);
$validator->setMessage(
__('"%1" is not a valid email address.', $label),
- \Zend_Validate_EmailAddress::INVALID_FORMAT
+ EmailAddress::INVALID_FORMAT
);
$validator->setMessage(
__('"%1" is not a valid hostname.', $label),
- \Zend_Validate_EmailAddress::INVALID_HOSTNAME
+ EmailAddress::INVALID_HOSTNAME
);
$validator->setMessage(
__('"%1" is not a valid hostname.', $label),
- \Zend_Validate_EmailAddress::INVALID_MX_RECORD
+ EmailAddress::INVALID_MX_RECORD
);
$validator->setMessage(
__('"%1" is not a valid hostname.', $label),
- \Zend_Validate_EmailAddress::INVALID_MX_RECORD
+ EmailAddress::INVALID_MX_RECORD
);
$validator->setMessage(
__('"%1" is not a valid email address.', $label),
- \Zend_Validate_EmailAddress::DOT_ATOM
+ EmailAddress::DOT_ATOM
);
$validator->setMessage(
__('"%1" is not a valid email address.', $label),
- \Zend_Validate_EmailAddress::QUOTED_STRING
+ EmailAddress::QUOTED_STRING
);
$validator->setMessage(
__('"%1" is not a valid email address.', $label),
- \Zend_Validate_EmailAddress::INVALID_LOCAL_PART
+ EmailAddress::INVALID_LOCAL_PART
);
$validator->setMessage(
__('"%1" uses too many characters.', $label),
- \Zend_Validate_EmailAddress::LENGTH_EXCEEDED
+ EmailAddress::LENGTH_EXCEEDED
);
$validator->setMessage(
__("'%value%' looks like an IP address, which is not an acceptable format."),
- \Zend_Validate_Hostname::IP_ADDRESS_NOT_ALLOWED
+ Hostname::IP_ADDRESS_NOT_ALLOWED
);
$validator->setMessage(
__("'%value%' looks like a DNS hostname but contains a dash in an invalid position."),
- \Zend_Validate_Hostname::INVALID_DASH
+ Hostname::INVALID_DASH
);
$validator->setMessage(
__(
"'%value%' looks like a DNS hostname but we cannot match it against "
. "the hostname schema for TLD '%tld%'."
),
- \Zend_Validate_Hostname::INVALID_HOSTNAME_SCHEMA
+ Hostname::INVALID_HOSTNAME_SCHEMA
);
$validator->setMessage(
__("'%value%' looks like a DNS hostname but cannot extract TLD part."),
- \Zend_Validate_Hostname::UNDECIPHERABLE_TLD
+ Hostname::UNDECIPHERABLE_TLD
);
$validator->setMessage(
__("'%value%' does not look like a valid local network name."),
- \Zend_Validate_Hostname::INVALID_LOCAL_NAME
+ Hostname::INVALID_LOCAL_NAME
);
$validator->setMessage(
__("'%value%' looks like a local network name, which is not an acceptable format."),
- \Zend_Validate_Hostname::LOCAL_NAME_NOT_ALLOWED
+ Hostname::LOCAL_NAME_NOT_ALLOWED
);
$validator->setMessage(
__(
"'%value%' appears to be a DNS hostname, but the given punycode notation cannot be decoded."
),
- \Zend_Validate_Hostname::CANNOT_DECODE_PUNYCODE
+ Hostname::CANNOT_DECODE_PUNYCODE
);
if (!$validator->isValid($value)) {
return array_unique($validator->getMessages());
@@ -443,18 +445,18 @@ protected function _validateInputRule($value)
if ($parsedUrl === false || empty($parsedUrl['scheme']) || empty($parsedUrl['host'])) {
return [__('"%1" is not a valid URL.', $label)];
}
- $validator = new \Zend_Validate_Hostname();
+ $validator = new Hostname();
if (!$validator->isValid($parsedUrl['host'])) {
return [__('"%1" is not a valid URL.', $label)];
}
break;
case 'date':
- $validator = new \Zend_Validate_Date(\Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT);
- $validator->setMessage(__('"%1" invalid type entered.', $label), \Zend_Validate_Date::INVALID);
- $validator->setMessage(__('"%1" is not a valid date.', $label), \Zend_Validate_Date::INVALID_DATE);
+ $validator = new Date();
+ $validator->setMessage(__('"%1" invalid type entered.', $label), Date::INVALID);
+ $validator->setMessage(__('"%1" is not a valid date.', $label), Date::INVALID_DATE);
$validator->setMessage(
__('"%1" does not fit the entered date format.', $label),
- \Zend_Validate_Date::FALSEFORMAT
+ Date::FALSEFORMAT
);
if (!$validator->isValid($value)) {
return array_unique($validator->getMessages());
diff --git a/app/code/Magento/Customer/Model/Metadata/Form/Postcode.php b/app/code/Magento/Customer/Model/Metadata/Form/Postcode.php
index 6f751558d29c9..8ee5379a83628 100644
--- a/app/code/Magento/Customer/Model/Metadata/Form/Postcode.php
+++ b/app/code/Magento/Customer/Model/Metadata/Form/Postcode.php
@@ -6,8 +6,8 @@
namespace Magento\Customer\Model\Metadata\Form;
use Magento\Customer\Api\Data\AttributeMetadataInterface;
-use Magento\Customer\Model\Metadata\ElementFactory;
use Magento\Directory\Helper\Data as DirectoryHelper;
+use Magento\Framework\Stdlib\StringUtils;
use Magento\Framework\Locale\ResolverInterface;
use Psr\Log\LoggerInterface as PsrLogger;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface as MagentoTimezone;
@@ -15,12 +15,12 @@
/**
* Customer Address Postal/Zip Code Attribute Data Model
*/
-class Postcode extends AbstractData
+class Postcode extends Text
{
/**
* @var DirectoryHelper
*/
- protected $directoryHelper;
+ protected DirectoryHelper $directoryHelper;
/**
* @param MagentoTimezone $localeDate
@@ -31,6 +31,7 @@ class Postcode extends AbstractData
* @param string $entityTypeCode
* @param bool $isAjax
* @param DirectoryHelper $directoryHelper
+ * @param StringUtils|null $stringHelper
*/
public function __construct(
MagentoTimezone $localeDate,
@@ -40,9 +41,11 @@ public function __construct(
$value,
$entityTypeCode,
$isAjax,
- DirectoryHelper $directoryHelper
+ DirectoryHelper $directoryHelper,
+ StringUtils $stringHelper = null
) {
$this->directoryHelper = $directoryHelper;
+ $stringHelper = $stringHelper ?? \Magento\Framework\App\ObjectManager::getInstance()->get(StringUtils::class);
parent::__construct(
$localeDate,
$logger,
@@ -50,12 +53,14 @@ public function __construct(
$localeResolver,
$value,
$entityTypeCode,
- $isAjax
+ $isAjax,
+ $stringHelper
);
}
/**
* Validate postal/zip code
+ *
* Return true and skip validation if country zip code is optional
*
* @param array|null|string $value
@@ -75,41 +80,17 @@ public function validateValue($value)
if (empty($value) && $value !== '0') {
$errors[] = __('"%1" is a required value.', $label);
}
+
+ $errors = $this->validateLength($value, $attribute, $errors);
+
+ $result = $this->_validateInputRule($value);
+ if ($result !== true) {
+ $errors = array_merge($errors, $result);
+ }
+
if (count($errors) == 0) {
return true;
}
return $errors;
}
-
- /**
- * {@inheritdoc}
- */
- public function extractValue(\Magento\Framework\App\RequestInterface $request)
- {
- return $this->_applyInputFilter($this->_getRequestValue($request));
- }
-
- /**
- * {@inheritdoc}
- */
- public function compactValue($value)
- {
- return $value;
- }
-
- /**
- * {@inheritdoc}
- */
- public function restoreValue($value)
- {
- return $this->compactValue($value);
- }
-
- /**
- * {@inheritdoc}
- */
- public function outputValue($format = ElementFactory::OUTPUT_FORMAT_TEXT)
- {
- return $this->_applyOutputFilter($this->_value);
- }
}
diff --git a/app/code/Magento/Customer/Model/Metadata/Form/Text.php b/app/code/Magento/Customer/Model/Metadata/Form/Text.php
index 7f5fe35c614d2..75ed5a2415179 100644
--- a/app/code/Magento/Customer/Model/Metadata/Form/Text.php
+++ b/app/code/Magento/Customer/Model/Metadata/Form/Text.php
@@ -119,7 +119,7 @@ public function outputValue($format = \Magento\Customer\Model\Metadata\ElementFa
* @param array $errors
* @return array
*/
- private function validateLength($value, AttributeMetadataInterface $attribute, array $errors): array
+ protected function validateLength($value, AttributeMetadataInterface $attribute, array $errors): array
{
// validate length
$label = __($attribute->getStoreLabel());
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/Model/Plugin/UpdateCustomer.php b/app/code/Magento/Customer/Model/Plugin/UpdateCustomer.php
index 7cf5000a859a7..c6207896fc55d 100644
--- a/app/code/Magento/Customer/Model/Plugin/UpdateCustomer.php
+++ b/app/code/Magento/Customer/Model/Plugin/UpdateCustomer.php
@@ -11,6 +11,8 @@
use Magento\Framework\Webapi\Rest\Request as RestRequest;
use Magento\Customer\Api\Data\CustomerInterface;
use Magento\Customer\Api\CustomerRepositoryInterface;
+use Magento\Authorization\Model\UserContextInterface;
+use Magento\Framework\App\ObjectManager;
/**
* Update customer by id from request param
@@ -22,12 +24,19 @@ class UpdateCustomer
*/
private $request;
+ /**
+ * @var UserContextInterface
+ */
+ private $userContext;
+
/**
* @param RestRequest $request
+ * @param UserContextInterface|null $userContext
*/
- public function __construct(RestRequest $request)
+ public function __construct(RestRequest $request, ?UserContextInterface $userContext = null)
{
$this->request = $request;
+ $this->userContext = $userContext ?? ObjectManager::getInstance()->get(UserContextInterface::class);
}
/**
@@ -43,10 +52,14 @@ public function beforeSave(
CustomerInterface $customer,
?string $passwordHash = null
): array {
- $customerId = $this->request->getParam('customerId');
+ $customerSessionId = $this->userContext->getUserType() === $this->userContext::USER_TYPE_CUSTOMER ?
+ (int)$this->userContext->getUserId() : 0;
+ $customerId = (int)$this->request->getParam('customerId');
$bodyParams = $this->request->getBodyParams();
if (!isset($bodyParams['customer']['Id']) && $customerId) {
- $customer = $this->getUpdatedCustomer($customerRepository->getById($customerId), $customer);
+ if ($customerId === $customerSessionId || $customerSessionId === 0) {
+ $customer = $this->getUpdatedCustomer($customerRepository->getById($customerId), $customer);
+ }
}
return [$customer, $passwordHash];
diff --git a/app/code/Magento/Customer/Model/ResourceModel/AddressRepository.php b/app/code/Magento/Customer/Model/ResourceModel/AddressRepository.php
index 48828d58fca04..6d4986793f8bf 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/AddressRepository.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/AddressRepository.php
@@ -1,7 +1,5 @@
updateData($address);
}
- $addressModel->setStoreId($customerModel->getStoreId());
+ if ($customerModel->getSharingConfig() !== null &&
+ $customerModel->getSharingConfig()->isWebsiteScope()
+ ) {
+ $addressModel->setStoreId($customerModel->getStoreId());
+ }
$errors = $addressModel->validate();
if ($errors !== true) {
@@ -213,6 +211,7 @@ public function getList(SearchCriteriaInterface $searchCriteria)
* @param FilterGroup $filterGroup
* @param Collection $collection
* @return void
+ * @see we don't recommend this approach anymore
* @throws \Magento\Framework\Exception\InputException
*/
protected function addFilterGroupToCollection(FilterGroup $filterGroup, Collection $collection)
@@ -269,13 +268,14 @@ public function deleteById($addressId)
* Retrieve collection processor
*
* @deprecated 101.0.0
+ * @see we don't recommend this approach anymore
* @return CollectionProcessorInterface
*/
private function getCollectionProcessor()
{
if (!$this->collectionProcessor) {
$this->collectionProcessor = \Magento\Framework\App\ObjectManager::getInstance()->get(
- 'Magento\Eav\Model\Api\SearchCriteria\CollectionProcessor'
+ CollectionProcessorInterface::class
);
}
return $this->collectionProcessor;
diff --git a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php
index d7895587c534d..292f41e241e0d 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php
@@ -204,7 +204,7 @@ public function save(CustomerInterface $customer, $passwordHash = null)
$prevCustomerData = $prevCustomerDataArr = null;
if ($customer->getId()) {
$prevCustomerData = $this->getById($customer->getId());
- $prevCustomerDataArr = $prevCustomerData->__toArray();
+ $prevCustomerDataArr = $this->prepareCustomerData($prevCustomerData->__toArray());
}
/** @var $customer \Magento\Customer\Model\Data\Customer */
$customerArr = $customer->__toArray();
@@ -485,6 +485,7 @@ public function deleteById($customerId)
* Helper function that adds a FilterGroup to the collection.
*
* @deprecated 101.0.0
+ * @see no alternative
* @param FilterGroup $filterGroup
* @param Collection $collection
* @return void
@@ -528,4 +529,21 @@ private function setCustomerGroupId($customerModel, $customerArr, $prevCustomerD
$customerModel->setGroupId($prevCustomerDataArr['group_id']);
}
}
+
+ /**
+ * Prepare customer data.
+ *
+ * @param array $customerData
+ * @return array
+ */
+ private function prepareCustomerData(array $customerData): array
+ {
+ if (isset($customerData[CustomerInterface::CUSTOM_ATTRIBUTES])) {
+ foreach ($customerData[CustomerInterface::CUSTOM_ATTRIBUTES] as $attribute) {
+ $customerData[$attribute['attribute_code']] = $attribute['value'];
+ }
+ unset($customerData[CustomerInterface::CUSTOM_ATTRIBUTES]);
+ }
+ return $customerData;
+ }
}
diff --git a/app/code/Magento/Customer/Model/ResourceModel/Group/GetCustomerGroupCodesByIds.php b/app/code/Magento/Customer/Model/ResourceModel/Group/GetCustomerGroupCodesByIds.php
new file mode 100644
index 0000000000000..05abdd552b705
--- /dev/null
+++ b/app/code/Magento/Customer/Model/ResourceModel/Group/GetCustomerGroupCodesByIds.php
@@ -0,0 +1,50 @@
+collectionFactory = $collectionFactory;
+ }
+
+ /**
+ * Returns customer group codes indexed by their IDs
+ *
+ * @param array $customerGroupIds
+ * @return array
+ */
+ public function execute(array $customerGroupIds): array
+ {
+ $result = [];
+ if ($customerGroupIds) {
+ $collection = $this->collectionFactory->create();
+ $collection->addFieldToFilter(
+ 'customer_group_id',
+ ['in' => array_map('intval', array_unique($customerGroupIds))]
+ );
+ $collection->addFieldToSelect('customer_group_id');
+ $collection->addFieldToSelect('customer_group_code');
+ $result = array_column($collection->getData(), 'customer_group_code', 'customer_group_id');
+ }
+ return $result;
+ }
+}
diff --git a/app/code/Magento/Customer/Model/ResourceModel/GroupRepository.php b/app/code/Magento/Customer/Model/ResourceModel/GroupRepository.php
index 1ad002d5f4311..9022929f3d236 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/GroupRepository.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/GroupRepository.php
@@ -13,6 +13,9 @@
use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\State\InvalidTransitionException;
+use Magento\Framework\Validator\NotEmpty;
+use Magento\Framework\Validator\ValidateException;
+use Magento\Framework\Validator\ValidatorChain;
use Magento\Tax\Api\Data\TaxClassInterface;
use Magento\Tax\Api\TaxClassManagementInterface;
use Magento\Framework\Api\ExtensibleDataInterface;
@@ -28,7 +31,7 @@ class GroupRepository implements \Magento\Customer\Api\GroupRepositoryInterface
/**
* The default tax class id if no tax class id is specified
*/
- const DEFAULT_TAX_CLASS_ID = 3;
+ public const DEFAULT_TAX_CLASS_ID = 3;
/**
* @var \Magento\Customer\Model\GroupRegistry
@@ -230,6 +233,7 @@ public function getList(SearchCriteriaInterface $searchCriteria)
* Helper function that adds a FilterGroup to the collection.
*
* @deprecated 101.0.0
+ * @see we don't recommend this approach anymore
* @param FilterGroup $filterGroup
* @param Collection $collection
* @return void
@@ -253,6 +257,7 @@ protected function addFilterGroupToCollection(FilterGroup $filterGroup, Collecti
* Translates a field name to a DB column name for use in collection queries.
*
* @deprecated 101.0.0
+ * @see we don't recommend this approach anymore
* @param string $field a field name that should be translated to a DB column name.
* @return string
*/
@@ -310,7 +315,7 @@ public function deleteById($id)
*
* @param \Magento\Customer\Api\Data\GroupInterface $group
* @throws InputException
- * @throws \Zend_Validate_Exception
+ * @throws ValidateException
* @return void
*
* @SuppressWarnings(PHPMD.NPathComplexity)
@@ -319,7 +324,7 @@ public function deleteById($id)
private function _validate($group)
{
$exception = new InputException();
- if (!\Zend_Validate::is($group->getCode(), 'NotEmpty')) {
+ if (!ValidatorChain::is($group->getCode(), NotEmpty::class)) {
$exception->addError(__('"%fieldName" is required. Enter and try again.', ['fieldName' => 'code']));
}
@@ -353,11 +358,13 @@ protected function _verifyTaxClassModel($taxClassId, $group)
* Retrieve collection processor
*
* @deprecated 101.0.0
+ * @see we don't recommend this approach anymore
* @return CollectionProcessorInterface
*/
private function getCollectionProcessor()
{
if (!$this->collectionProcessor) {
+ //phpcs:disable Magento2.PHP.LiteralNamespaces
$this->collectionProcessor = \Magento\Framework\App\ObjectManager::getInstance()->get(
'Magento\Customer\Model\Api\SearchCriteria\GroupCollectionProcessor'
);
diff --git a/app/code/Magento/Customer/Model/ResourceModel/Online/Grid/Collection.php b/app/code/Magento/Customer/Model/ResourceModel/Online/Grid/Collection.php
index 0262bf30dc827..712ba02d59355 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/Online/Grid/Collection.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/Online/Grid/Collection.php
@@ -5,12 +5,13 @@
*/
namespace Magento\Customer\Model\ResourceModel\Online\Grid;
-use Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult;
use Magento\Customer\Model\Visitor;
-use Magento\Framework\Api;
-use Magento\Framework\Event\ManagerInterface as EventManager;
use Magento\Framework\Data\Collection\Db\FetchStrategyInterface as FetchStrategy;
use Magento\Framework\Data\Collection\EntityFactoryInterface as EntityFactory;
+use Magento\Framework\Event\ManagerInterface as EventManager;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Stdlib\DateTime\DateTime;
+use Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult;
use Psr\Log\LoggerInterface as Logger;
/**
@@ -23,17 +24,17 @@ class Collection extends SearchResult
/**
* Value of seconds in one minute
*/
- const SECONDS_IN_MINUTE = 60;
+ public const SECONDS_IN_MINUTE = 60;
/**
- * @var \Magento\Framework\Stdlib\DateTime\DateTime
+ * @var DateTime
*/
- protected $date;
+ protected DateTime $date;
/**
* @var Visitor
*/
- protected $visitorModel;
+ protected Visitor $visitorModel;
/**
* @param EntityFactory $entityFactory
@@ -43,7 +44,8 @@ class Collection extends SearchResult
* @param string $mainTable
* @param string $resourceModel
* @param Visitor $visitorModel
- * @param \Magento\Framework\Stdlib\DateTime\DateTime $date
+ * @param DateTime $date
+ * @throws LocalizedException
*/
public function __construct(
EntityFactory $entityFactory,
@@ -53,7 +55,7 @@ public function __construct(
$mainTable,
$resourceModel,
Visitor $visitorModel,
- \Magento\Framework\Stdlib\DateTime\DateTime $date
+ DateTime $date
) {
$this->date = $date;
$this->visitorModel = $visitorModel;
@@ -65,7 +67,7 @@ public function __construct(
*
* @return $this
*/
- protected function _initSelect()
+ protected function _initSelect(): Collection
{
parent::_initSelect();
$connection = $this->getConnection();
@@ -78,6 +80,7 @@ protected function _initSelect()
'main_table.last_visit_at >= ?',
$connection->formatDate($lastDate)
);
+ $this->addFilterToMap('customer_id', 'main_table.customer_id');
$expression = $connection->getCheckSql(
'main_table.customer_id IS NOT NULL AND main_table.customer_id != 0',
$connection->quote(Visitor::VISITOR_TYPE_CUSTOMER),
@@ -92,9 +95,9 @@ protected function _initSelect()
*
* @param string|array $field
* @param string|int|array|null $condition
- * @return \Magento\Cms\Model\ResourceModel\Block\Collection
+ * @return Collection
*/
- public function addFieldToFilter($field, $condition = null)
+ public function addFieldToFilter($field, $condition = null): Collection
{
if ($field == 'visitor_type') {
$field = 'customer_id';
diff --git a/app/code/Magento/Customer/Observer/AfterAddressSaveObserver.php b/app/code/Magento/Customer/Observer/AfterAddressSaveObserver.php
index 33290306e4843..0a98f6521def1 100644
--- a/app/code/Magento/Customer/Observer/AfterAddressSaveObserver.php
+++ b/app/code/Magento/Customer/Observer/AfterAddressSaveObserver.php
@@ -34,7 +34,7 @@ class AfterAddressSaveObserver implements ObserverInterface
/**
* VAT ID validation processed flag code
*/
- const VIV_PROCESSED_FLAG = 'viv_after_address_save_processed';
+ public const VIV_PROCESSED_FLAG = 'viv_after_address_save_processed';
/**
* @var HelperAddress
@@ -141,8 +141,7 @@ public function execute(Observer $observer)
if ($customerAddress->getVatId() == ''
|| !$this->_customerVat->isCountryInEU($customerAddress->getCountry())
) {
- $defaultGroupId = $customer->getGroupId() ? $customer->getGroupId() :
- $this->_groupManagement->getDefaultGroup($customer->getStore())->getId();
+ $defaultGroupId = $this->_groupManagement->getDefaultGroup($customer->getStore())->getId();
if (!$customer->getDisableAutoGroupChange() && $customer->getGroupId() != $defaultGroupId) {
$customer->setGroupId($defaultGroupId);
$customer->save();
diff --git a/app/code/Magento/Customer/Test/Fixture/Customer.php b/app/code/Magento/Customer/Test/Fixture/Customer.php
new file mode 100644
index 0000000000000..77224eb210ad1
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Fixture/Customer.php
@@ -0,0 +1,201 @@
+ 'password',
+ CustomerInterface::ID => null,
+ CustomerInterface::CONFIRMATION => null,
+ CustomerInterface::CREATED_AT => null,
+ CustomerInterface::UPDATED_AT => null,
+ CustomerInterface::CREATED_IN => null,
+ CustomerInterface::DOB => null,
+ CustomerInterface::EMAIL => 'customer%uniqid%@mail.com',
+ CustomerInterface::FIRSTNAME => 'Firstname%uniqid%',
+ CustomerInterface::GENDER => null,
+ CustomerInterface::GROUP_ID => null,
+ CustomerInterface::LASTNAME => 'Lastname%uniqid%',
+ CustomerInterface::MIDDLENAME => null,
+ CustomerInterface::PREFIX => null,
+ CustomerInterface::STORE_ID => null,
+ CustomerInterface::SUFFIX => null,
+ CustomerInterface::TAXVAT => null,
+ CustomerInterface::WEBSITE_ID => null,
+ CustomerInterface::DEFAULT_BILLING => null,
+ CustomerInterface::DEFAULT_SHIPPING => null,
+ CustomerInterface::KEY_ADDRESSES => [],
+ CustomerInterface::DISABLE_AUTO_GROUP_CHANGE => null,
+ CustomerInterface::CUSTOM_ATTRIBUTES => [],
+ CustomerInterface::EXTENSION_ATTRIBUTES_KEY => [],
+ ];
+
+ private const DEFAULT_DATA_ADDRESS = [
+ AddressInterface::ID => null,
+ AddressInterface::CUSTOMER_ID => null,
+ AddressInterface::REGION => 'Massachusetts',
+ AddressInterface::REGION_ID => '32',
+ AddressInterface::COUNTRY_ID => 'US',
+ AddressInterface::STREET => ['%street_number% Test Street%uniqid%'],
+ AddressInterface::COMPANY => null,
+ AddressInterface::TELEPHONE => '1234567890',
+ AddressInterface::FAX => null,
+ AddressInterface::POSTCODE => '02108',
+ AddressInterface::CITY => 'Boston',
+ AddressInterface::FIRSTNAME => 'Firstname%uniqid%',
+ AddressInterface::LASTNAME => 'Lastname%uniqid%',
+ AddressInterface::MIDDLENAME => null,
+ AddressInterface::PREFIX => null,
+ AddressInterface::SUFFIX => null,
+ AddressInterface::VAT_ID => null,
+ AddressInterface::DEFAULT_BILLING => true,
+ AddressInterface::DEFAULT_SHIPPING => true,
+ AddressInterface::CUSTOM_ATTRIBUTES => [],
+ AddressInterface::EXTENSION_ATTRIBUTES_KEY => [],
+ ];
+
+ /**
+ * @var ServiceFactory
+ */
+ private ServiceFactory $serviceFactory;
+
+ /**
+ * @var AccountManagementInterface
+ */
+ private AccountManagementInterface $accountManagement;
+
+ /**
+ * @var CustomerRegistry
+ */
+ private CustomerRegistry $customerRegistry;
+
+ /**
+ * @var ProcessorInterface
+ */
+ private ProcessorInterface $dataProcessor;
+
+ /**
+ * @var DataMerger
+ */
+ private DataMerger $dataMerger;
+
+ /**
+ * @param ServiceFactory $serviceFactory
+ * @param AccountManagementInterface $accountManagement
+ * @param CustomerRegistry $customerRegistry
+ * @param ProcessorInterface $dataProcessor
+ * @param DataMerger $dataMerger
+ */
+ public function __construct(
+ ServiceFactory $serviceFactory,
+ AccountManagementInterface $accountManagement,
+ CustomerRegistry $customerRegistry,
+ ProcessorInterface $dataProcessor,
+ DataMerger $dataMerger
+ ) {
+ $this->serviceFactory = $serviceFactory;
+ $this->accountManagement = $accountManagement;
+ $this->customerRegistry = $customerRegistry;
+ $this->dataProcessor = $dataProcessor;
+ $this->dataMerger = $dataMerger;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @param array $data Parameters. Same format as Customer::DEFAULT_DATA.
+ * @return DataObject|null
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
+ */
+ public function apply(array $data = []): ?DataObject
+ {
+ $customerSaveService = $this->serviceFactory->create(CustomerRepositoryInterface::class, 'save');
+ $data = $this->prepareData($data);
+ $passwordHash = $this->accountManagement->getPasswordHash($data['password']);
+ unset($data['password']);
+ $customerSaveService->execute(
+ [
+ 'customer' => $data,
+ 'passwordHash' => $passwordHash
+ ]
+ );
+ return $this->customerRegistry->retrieveByEmail($data['email'], $data['website_id']);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function revert(DataObject $data): void
+ {
+ $data->setCustomerId($data->getId());
+ $service = $this->serviceFactory->create(CustomerRepositoryInterface::class, 'deleteById');
+ $service->execute(
+ [
+ 'customerId' => $data->getId()
+ ]
+ );
+ }
+
+ /**
+ * Prepare customer data
+ *
+ * @param array $data
+ * @return array
+ */
+ private function prepareData(array $data): array
+ {
+ $data = $this->dataMerger->merge(self::DEFAULT_DATA, $data);
+ $data[CustomerInterface::KEY_ADDRESSES] = $this->prepareAddresses($data[CustomerInterface::KEY_ADDRESSES]);
+
+ return $this->dataProcessor->process($this, $data);
+ }
+
+ /**
+ * Prepare customer addresses
+ *
+ * @param array $data
+ * @return array
+ */
+ private function prepareAddresses(array $data): array
+ {
+ $addresses = [];
+ $default = self::DEFAULT_DATA_ADDRESS;
+ $streetNumber = 123;
+ foreach ($data as $dataAddress) {
+ $dataAddress = $this->dataMerger->merge($default, $dataAddress);
+ $placeholders = ['%street_number%' => $streetNumber++];
+ $dataAddress[AddressInterface::STREET] = array_map(
+ fn ($str) => strtr($str, $placeholders),
+ $dataAddress[AddressInterface::STREET]
+ );
+ $addresses[] = $dataAddress;
+ $default[AddressInterface::DEFAULT_BILLING] = false;
+ $default[AddressInterface::DEFAULT_SHIPPING] = false;
+ }
+
+ return $addresses;
+ }
+}
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillBillingAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillBillingAddressActionGroup.xml
index accf8f40bb282..b66c19610f574 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillBillingAddressActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillBillingAddressActionGroup.xml
@@ -5,7 +5,7 @@
* See COPYING.txt for license details.
*/
-->
-
+
@@ -21,4 +21,4 @@
-
\ No newline at end of file
+
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 @@
+
+
+
+
+
+
+ Verify a customer's cookies expiry date on browser's local storage in storefront
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{$grabCookieForMagentoDataIds['expiry']}}
+ {{$generateExpireDate}}
+
+
+ {{$grabCookieForMagentoCacheSessionId['expiry']}}
+ {{$generateExpireDate}}
+
+
+ {{$grabCookieForMagentoCacheStorage['expiry']}}
+ {{$generateExpireDate}}
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
index a8bb88d948241..d47409cb0953d 100644
--- a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
@@ -435,4 +435,47 @@
true
66
+
+
+ - 1234 Some France address
+ - 113
+
+ City
+ FR
+ France
+ 12345
+ 512-345-6789
+
+
+
+ - 123 Street Line
+ - empty
+ - empty
+ - Region Line
+
+ New York
+ New York
+ 10001
+ 512-345-6789
+ RegionNY
+ US
+ United States
+
+
+ John
+ Doe
+
+ - 4423 St. John Street
+ - 113
+
+ Magento
+ Alameda
+ Saskatchewan
+ CA
+ Canada
+ S4P3Y2
+ true
+ true
+ 613-582-4782
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
index e1fdc734c34b2..ca9a742fe3e7f 100644
--- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
@@ -457,5 +457,36 @@
General
BillingAddressTX
-
+
+ UK_Not_Default_Address
+
+
+ 1
+ true
+ true
+ John.Doe@example.com
+ John
+ Doe
+ John Doe
+ pwdTest123!
+ 0
+ 0
+ General
+ taxValue
+ US_Address_TX
+
+
+ 1
+ true
+ true
+ John.Doe@example.com
+ John
+ Doe
+ John Doe
+ pwdTest123!
+ 0
+ 0
+ General
+ Canada_Address
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCreateUserSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCreateUserSection.xml
index a9c5b6cbd88a6..d5ebbdcceda87 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCreateUserSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCreateUserSection.xml
@@ -21,6 +21,7 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressFiltersSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressFiltersSection.xml
index f3df6cc5e8c00..8dafe009c45f2 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressFiltersSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressFiltersSection.xml
@@ -13,6 +13,7 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerCreateNewOrderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerCreateNewOrderSection.xml
index 6b0cafe8dc00b..a6af92699c34a 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerCreateNewOrderSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerCreateNewOrderSection.xml
@@ -14,5 +14,6 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerInformationSection.xml
index d680015230b9d..0de8f0c898917 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerInformationSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerInformationSection.xml
@@ -18,5 +18,6 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminDeleteUserSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminDeleteUserSection.xml
index f43666a032956..6894fade3dbee 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminDeleteUserSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminDeleteUserSection.xml
@@ -9,7 +9,7 @@
-
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml
index fb3d1570848e4..7e7f952834708 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml
@@ -11,5 +11,6 @@
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressFormSection.xml
index 5b111489a8414..0643d76f847d5 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressFormSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressFormSection.xml
@@ -14,6 +14,7 @@
+
@@ -22,5 +23,6 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerMessagesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerMessagesSection.xml
index 07d044921c8e5..aba74798ae2cc 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerMessagesSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerMessagesSection.xml
@@ -11,5 +11,6 @@
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml
index abb7809e8ddf0..3b098a95b88d9 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml
@@ -32,5 +32,10 @@
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeAllCustomersGroupViaGridTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeAllCustomersGroupViaGridTest.xml
index dada747dd16c4..63bef6ba7f336 100644
--- a/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeAllCustomersGroupViaGridTest.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminChangeAllCustomersGroupViaGridTest.xml
@@ -15,6 +15,9 @@
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminPanelIsFrozenIfStorefrontIsOpenedViaCustomerViewTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminPanelIsFrozenIfStorefrontIsOpenedViaCustomerViewTest.xml
index b975cf62765c1..5f20eb9cd5e67 100644
--- a/app/code/Magento/Customer/Test/Mftf/Test/AdminPanelIsFrozenIfStorefrontIsOpenedViaCustomerViewTest.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminPanelIsFrozenIfStorefrontIsOpenedViaCustomerViewTest.xml
@@ -6,7 +6,7 @@
*/
-->
-
@@ -19,6 +19,8 @@
+
+
@@ -27,6 +29,8 @@
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml
index 824c898068342..368a5c8db7d3c 100644
--- a/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontLoginFormCheckDuplicateValidateMessageTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontLoginFormCheckDuplicateValidateMessageTest.xml
new file mode 100644
index 0000000000000..7d7218c59d149
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontLoginFormCheckDuplicateValidateMessageTest.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php
index b3fb2f6226f0f..b20e2603c24f3 100644
--- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php
@@ -44,6 +44,8 @@
use Magento\Framework\View\Result\PageFactory;
use Magento\Newsletter\Model\SubscriberFactory;
use Magento\Newsletter\Model\SubscriptionManagerInterface;
+use Magento\Store\Api\Data\WebsiteInterface;
+use Magento\Store\Model\StoreManagerInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -284,6 +286,13 @@ protected function setUp(): void
$this->emailNotificationMock = $this->getMockBuilder(EmailNotificationInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
+ $website = $this->createPartialMock(\Magento\Store\Model\Website::class, ['getStoreIds']);
+ $website->method('getStoreIds')
+ ->willReturn([1]);
+ $storeManager = $this->getMockBuilder(StoreManagerInterface::class)
+ ->getMockForAbstractClass();
+ $storeManager->method('getWebsite')
+ ->willReturn($website);
$objectManager = new ObjectManager($this);
@@ -310,6 +319,7 @@ protected function setUp(): void
'addressRepository' => $this->customerAddressRepositoryMock,
'addressMapper' => $this->customerAddressMapperMock,
'subscriptionManager' => $this->subscriptionManager,
+ 'storeManager' => $storeManager,
]
);
diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php
index 8ff6a8585212f..9e68d53fd5949 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php
@@ -1820,7 +1820,10 @@ private function reInitModel(): void
'getPasswordHash',
'setPasswordHash',
'setRpToken',
- 'setRpTokenCreatedAt'
+ 'setRpTokenCreatedAt',
+ 'setFailuresNum',
+ 'setFirstFailure',
+ 'setLockExpires',
]
)
->getMock();
@@ -2039,6 +2042,9 @@ function ($string) {
$this->customerSecure->expects($this->once())->method('setRpToken')->with(null);
$this->customerSecure->expects($this->once())->method('setRpTokenCreatedAt')->with(null);
$this->customerSecure->expects($this->any())->method('setPasswordHash')->willReturn(null);
+ $this->customerSecure->expects($this->once())->method('setFailuresNum')->with(0);
+ $this->customerSecure->expects($this->once())->method('setFirstFailure')->with(null);
+ $this->customerSecure->expects($this->once())->method('setLockExpires')->with(null);
$this->sessionCleanerMock->expects($this->once())->method('clearFor')->with($customerId)->willReturnSelf();
$this->assertTrue($this->accountManagement->resetPassword($customerEmail, $resetToken, $newPassword));
diff --git a/app/code/Magento/Customer/Test/Unit/Model/Address/AbstractAddressTest.php b/app/code/Magento/Customer/Test/Unit/Model/Address/AbstractAddressTest.php
index 203865d205935..cee6a8aefd1a4 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/Address/AbstractAddressTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/Address/AbstractAddressTest.php
@@ -217,17 +217,18 @@ protected function prepareGetRegionCode($countryId, $regionCode = 'UK')
{
$region = $this->getMockBuilder(Region::class)
->addMethods(['getCountryId', 'getCode'])
- ->onlyMethods(['__wakeup', 'load'])
+ ->onlyMethods(['__wakeup', 'load', 'loadByCode'])
->disableOriginalConstructor()
->getMock();
+ $region->method('loadByCode')
+ ->willReturnSelf();
$region->expects($this->once())
->method('getCode')
->willReturn($regionCode);
$region->expects($this->once())
->method('getCountryId')
->willReturn($countryId);
- $this->regionFactoryMock->expects($this->once())
- ->method('create')
+ $this->regionFactoryMock->method('create')
->willReturn($region);
}
diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php
index 83e6d21aeddfd..97c9a716bdcba 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php
@@ -38,8 +38,8 @@
*/
class DataProviderTest extends TestCase
{
- const ATTRIBUTE_CODE = 'test-code';
- const OPTIONS_RESULT = 'test-options';
+ private const ATTRIBUTE_CODE = 'test-code';
+ private const OPTIONS_RESULT = 'test-options';
/**
* @var Config|MockObject
@@ -966,7 +966,8 @@ function ($origName) {
'max_file_size' => $maxFileSize,
'file_extensions' => 'ext1, eXt2 '
],
- 'label' => __('frontend_label')
+ 'label' => __('frontend_label'),
+ 'attributeId' => null
]
]
]
diff --git a/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/AbstractDataTest.php b/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/AbstractDataTest.php
index 485ada310e817..33d6dad66c477 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/AbstractDataTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/AbstractDataTest.php
@@ -1,12 +1,15 @@
- '"mylabel" contains non-alphabetic or non-numeric characters.'
+ Alnum::NOT_ALNUM => '"mylabel" contains non-alphabetic or non-numeric characters.'
]
],
[
@@ -309,7 +315,7 @@ public function validateInputRuleDataProvider(): array
'mylabel',
'alphanumeric',
[
- \Zend_Validate_Alnum::NOT_ALNUM => '"mylabel" contains non-alphabetic or non-numeric characters.'
+ Alnum::NOT_ALNUM => '"mylabel" contains non-alphabetic or non-numeric characters.'
]
],
['abcqaz', 'mylabel', 'alphanumeric', true],
@@ -318,13 +324,13 @@ public function validateInputRuleDataProvider(): array
'!@#$',
'mylabel',
'numeric',
- [\Zend_Validate_Digits::NOT_DIGITS => '"mylabel" contains non-numeric characters.']
+ [Digits::NOT_DIGITS => '"mylabel" contains non-numeric characters.']
],
[
'1234',
'mylabel',
'alpha',
- [\Zend_Validate_Alpha::NOT_ALPHA => '"mylabel" contains non-alphabetic characters.']
+ [Alpha::NOT_ALPHA => '"mylabel" contains non-alphabetic characters.']
],
[
'!@#$',
@@ -332,9 +338,9 @@ public function validateInputRuleDataProvider(): array
'email',
[
// @codingStandardsIgnoreStart
- \Zend_Validate_EmailAddress::INVALID_HOSTNAME => '"mylabel" is not a valid hostname.',
- \Zend_Validate_Hostname::INVALID_HOSTNAME => "'#\$' does not match the expected structure for a DNS hostname",
- \Zend_Validate_Hostname::INVALID_LOCAL_NAME => "'#\$' does not look like a valid local network name."
+ EmailAddress::INVALID_HOSTNAME => '"mylabel" is not a valid hostname.',
+ Hostname::INVALID_HOSTNAME => "'#\$' does not match the expected structure for a DNS hostname",
+ Hostname::INVALID_LOCAL_NAME => "'#\$' does not look like a valid local network name."
// @codingStandardsIgnoreEnd
]
],
@@ -344,7 +350,7 @@ public function validateInputRuleDataProvider(): array
'1234',
'mylabel',
'date',
- [\Zend_Validate_Date::INVALID_DATE => '"mylabel" is not a valid date.']
+ [Date::INVALID_DATE => '"mylabel" is not a valid date.']
]
];
}
diff --git a/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/DateTest.php b/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/DateTest.php
index 9bc78776ed8c6..c469d12882ec4 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/DateTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/DateTest.php
@@ -1,10 +1,10 @@
- '"Space Date" does not fit the entered date format.'],
+ ['dateInvalidDate' => '"Space Date" is not a valid date.'],
]
];
}
diff --git a/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/PostcodeTest.php b/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/PostcodeTest.php
index 416feb7c8aa0c..dcf8916c2c3b9 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/PostcodeTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/PostcodeTest.php
@@ -7,12 +7,18 @@
namespace Magento\Customer\Test\Unit\Model\Metadata\Form;
+use Magento\Customer\Api\Data\ValidationRuleInterface;
use Magento\Customer\Model\Metadata\Form\Postcode;
use Magento\Directory\Helper\Data as DirectoryHelper;
+use Magento\Framework\Phrase;
use PHPUnit\Framework\MockObject\MockObject;
+use Magento\Framework\Stdlib\StringUtils;
class PostcodeTest extends AbstractFormTestCase
{
+ /** @var StringUtils */
+ private StringUtils $stringHelper;
+
/**
* @var DirectoryHelper|MockObject
*/
@@ -21,7 +27,7 @@ class PostcodeTest extends AbstractFormTestCase
protected function setUp(): void
{
parent::setUp();
-
+ $this->stringHelper = new StringUtils();
$this->directoryHelper = $this->getMockBuilder(\Magento\Directory\Helper\Data::class)
->disableOriginalConstructor()
->getMock();
@@ -43,7 +49,8 @@ protected function getClass($value)
$value,
0,
false,
- $this->directoryHelper
+ $this->directoryHelper,
+ $this->stringHelper
);
}
@@ -58,7 +65,7 @@ protected function getClass($value)
public function testValidateValue($value, $expected, $countryId, $isOptional)
{
$storeLabel = 'Zip/Postal Code';
- $this->attributeMetadataMock->expects($this->once())
+ $this->attributeMetadataMock->expects($this->atLeastOnce())
->method('getStoreLabel')
->willReturn($storeLabel);
@@ -87,4 +94,113 @@ public function validateValueDataProvider()
['90034', true, 'IE', true],
];
}
+
+ /**
+ * @param string|int|bool|null $value to assign to boolean
+ * @param string|bool $expected text output
+ * @dataProvider validateValueLengthDataProvider
+ */
+ public function testValidateValueLength($value, $expected)
+ {
+ $minTextLengthRule = $this->getMockBuilder(ValidationRuleInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getName', 'getValue'])
+ ->getMockForAbstractClass();
+ $minTextLengthRule->expects($this->any())
+ ->method('getName')
+ ->willReturn('min_text_length');
+ $minTextLengthRule->expects($this->any())
+ ->method('getValue')
+ ->willReturn(5);
+
+ $maxTextLengthRule = $this->getMockBuilder(ValidationRuleInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getName', 'getValue'])
+ ->getMockForAbstractClass();
+ $maxTextLengthRule->expects($this->any())
+ ->method('getName')
+ ->willReturn('max_text_length');
+ $maxTextLengthRule->expects($this->any())
+ ->method('getValue')
+ ->willReturn(6);
+
+ $inputValidationRule = $this->getMockBuilder(ValidationRuleInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getName', 'getValue'])
+ ->getMockForAbstractClass();
+ $inputValidationRule->expects($this->any())
+ ->method('getName')
+ ->willReturn('input_validation');
+ $inputValidationRule->expects($this->any())
+ ->method('getValue')
+ ->willReturn('numeric');
+
+ $validationRules = [
+ 'input_validation' => $inputValidationRule,
+ 'min_text_length' => $minTextLengthRule,
+ 'max_text_length' => $maxTextLengthRule,
+ ];
+
+ $this->attributeMetadataMock->expects(
+ $this->any()
+ )->method(
+ 'getValidationRules'
+ )->willReturn(
+ $validationRules
+ );
+
+ $sut = $this->getClass($value);
+ $actual = $sut->validateValue($value);
+
+ if (is_bool($actual)) {
+ $this->assertEquals($expected, $actual);
+ } else {
+ if (is_array($actual)) {
+ $actual = array_map(function ($message) {
+ return $message instanceof Phrase ? $message->__toString() : $message;
+ }, $actual);
+ }
+
+ if (is_array($expected)) {
+ foreach ($expected as $key => $row) {
+ $this->assertEquals($row, $actual[$key]);
+ }
+ } else {
+ $this->assertContains($expected, $actual);
+ }
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function validateValueLengthDataProvider(): array
+ {
+ return [
+ 'false' => [false, ['"" is a required value.', '"" length must be equal or greater than 5 characters.']],
+ 'empty' => ['', ['"" is a required value.', '"" length must be equal or greater than 5 characters.']],
+ 'null' => [null, ['"" is a required value.', '"" length must be equal or greater than 5 characters.']],
+ 'one' => [1, '"" length must be equal or greater than 5 characters.'],
+ 'L1' => ['6', '"" length must be equal or greater than 5 characters.'],
+ 'L2' => ['66', '"" length must be equal or greater than 5 characters.'],
+ 'L5' => ['66666', true],
+ 'thousand' => ['10000', true],
+ 'L6' => ['666666', true],
+ 'L7' => ['6666666', '"" length must be equal or less than 6 characters.'],
+ 'S1' => ['s',
+ [
+ '"" length must be equal or greater than 5 characters.',
+ "notDigits" => '"" contains non-numeric characters.'
+ ]
+ ],
+ 'S6' => ['string', ["notDigits" => '"" contains non-numeric characters.']],
+ 'S7' => ['strings',
+ [
+ '"" length must be equal or less than 6 characters.',
+ "notDigits" => '"" contains non-numeric characters.'
+ ]
+ ],
+ 'L6s' => ['66666s', ["notDigits" => '"" contains non-numeric characters.']],
+ ];
+ }
}
diff --git a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/AddressRepositoryTest.php b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/AddressRepositoryTest.php
index ee5dd8af02cf2..704537ce28fef 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/AddressRepositoryTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/AddressRepositoryTest.php
@@ -12,6 +12,7 @@
use Magento\Customer\Api\Data\AddressSearchResultsInterfaceFactory;
use Magento\Customer\Model\AddressFactory;
use Magento\Customer\Model\AddressRegistry;
+use Magento\Customer\Model\Config\Share;
use Magento\Customer\Model\Customer;
use Magento\Customer\Model\CustomerRegistry;
use Magento\Customer\Model\ResourceModel\Address;
@@ -91,6 +92,11 @@ class AddressRepositoryTest extends TestCase
*/
private $collectionProcessor;
+ /**
+ * @var Share|MockObject
+ */
+ private $configShare;
+
protected function setUp(): void
{
$this->addressFactory = $this->createPartialMock(AddressFactory::class, ['create']);
@@ -112,9 +118,20 @@ protected function setUp(): void
'',
false
);
- $this->customer = $this->createMock(Customer::class);
+ $this->customer = $this->createPartialMock(
+ Customer::class,
+ ['getSharingConfig','getAddressesCollection']
+ );
$this->address = $this->getMockBuilder(\Magento\Customer\Model\Address::class)->addMethods(
- ['getCountryId', 'getFirstname', 'getLastname', 'getCity', 'getTelephone', 'getShouldIgnoreValidation']
+ [
+ 'getCountryId',
+ 'getFirstname',
+ 'getLastname',
+ 'getCity',
+ 'getTelephone',
+ 'getShouldIgnoreValidation',
+ 'setStoreId'
+ ]
)
->onlyMethods(
[
@@ -148,6 +165,11 @@ protected function setUp(): void
$this->extensionAttributesJoinProcessor,
$this->collectionProcessor
);
+ $this->configShare = $this->getMockBuilder(Share::class)->onlyMethods(
+ ['isWebsiteScope']
+ )
+ ->disableOriginalConstructor()
+ ->getMock();
}
public function testSave()
@@ -212,6 +234,146 @@ public function testSave()
$this->repository->save($customerAddress);
}
+ public function testSaveWithConfigCustomerAccountShareScopeWebsite()
+ {
+ $customerId = 34;
+ $addressId = 53;
+ $customerAddress = $this->getMockForAbstractClass(
+ AddressInterface::class,
+ [],
+ '',
+ false
+ );
+ $addressCollection =
+ $this->createMock(Collection::class);
+ $customerAddress->expects($this->atLeastOnce())
+ ->method('getCustomerId')
+ ->willReturn($customerId);
+ $customerAddress->expects($this->atLeastOnce())
+ ->method('getId')
+ ->willReturn($addressId);
+ $this->customerRegistry->expects($this->once())
+ ->method('retrieve')
+ ->with($customerId)
+ ->willReturn($this->customer);
+ $this->address->expects($this->atLeastOnce())
+ ->method("getId")
+ ->willReturn($addressId);
+ $this->addressRegistry->expects($this->once())
+ ->method('retrieve')
+ ->with($addressId)
+ ->willReturn(null);
+ $this->addressFactory->expects($this->once())
+ ->method('create')
+ ->willReturn($this->address);
+ $this->address->expects($this->once())
+ ->method('updateData')
+ ->with($customerAddress);
+ $this->address->expects($this->once())
+ ->method('setCustomer')
+ ->with($this->customer);
+ $this->customer->expects($this->exactly(2))
+ ->method('getSharingConfig')
+ ->willReturn($this->configShare);
+ $this->configShare->expects($this->once())
+ ->method('isWebsiteScope')
+ ->willReturn(true);
+ $this->address->expects($this->once())
+ ->method('setStoreId');
+ $this->address->expects($this->once())
+ ->method('validate')
+ ->willReturn(true);
+ $this->address->expects($this->once())
+ ->method('save');
+ $this->addressRegistry->expects($this->once())
+ ->method('push')
+ ->with($this->address);
+ $this->customer->expects($this->exactly(2))
+ ->method('getAddressesCollection')
+ ->willReturn($addressCollection);
+ $addressCollection->expects($this->once())
+ ->method("removeItemByKey")
+ ->with($addressId);
+ $addressCollection->expects($this->once())
+ ->method("addItem")
+ ->with($this->address);
+ $this->address->expects($this->once())
+ ->method('getDataModel')
+ ->willReturn($customerAddress);
+
+ $this->repository->save($customerAddress);
+ }
+
+ public function testSaveWithConfigCustomerAccountShareScopeGlobal()
+ {
+ $customerId = 34;
+ $addressId = 53;
+ $customerAddress = $this->getMockForAbstractClass(
+ AddressInterface::class,
+ [],
+ '',
+ false
+ );
+ $addressCollection =
+ $this->createMock(Collection::class);
+ $customerAddress->expects($this->atLeastOnce())
+ ->method('getCustomerId')
+ ->willReturn($customerId);
+ $customerAddress->expects($this->atLeastOnce())
+ ->method('getId')
+ ->willReturn($addressId);
+ $this->customerRegistry->expects($this->once())
+ ->method('retrieve')
+ ->with($customerId)
+ ->willReturn($this->customer);
+ $this->address->expects($this->atLeastOnce())
+ ->method("getId")
+ ->willReturn($addressId);
+ $this->addressRegistry->expects($this->once())
+ ->method('retrieve')
+ ->with($addressId)
+ ->willReturn(null);
+ $this->addressFactory->expects($this->once())
+ ->method('create')
+ ->willReturn($this->address);
+ $this->address->expects($this->once())
+ ->method('updateData')
+ ->with($customerAddress);
+ $this->address->expects($this->once())
+ ->method('setCustomer')
+ ->with($this->customer);
+ $this->customer->expects($this->exactly(2))
+ ->method('getSharingConfig')
+ ->willReturn($this->configShare);
+ $this->configShare->expects($this->once())
+ ->method('isWebsiteScope')
+ ->willReturn(false);
+ $this->address->expects($this->never())
+ ->method('setStoreId');
+ $this->address->expects($this->once())
+ ->method('validate')
+ ->willReturn(true);
+ $this->address->expects($this->once())
+ ->method('save');
+ $this->addressRegistry->expects($this->once())
+ ->method('push')
+ ->with($this->address);
+ $this->customer->expects($this->exactly(2))
+ ->method('getAddressesCollection')
+ ->willReturn($addressCollection);
+ $addressCollection->expects($this->once())
+ ->method("removeItemByKey")
+ ->with($addressId);
+ $addressCollection->expects($this->once())
+ ->method("addItem")
+ ->with($this->address);
+ $this->address->expects($this->once())
+ ->method('getDataModel')
+ ->willReturn($customerAddress);
+
+ $this->repository->save($customerAddress);
+ }
+
public function testSaveWithException()
{
$this->expectException(InputException::class);
diff --git a/app/code/Magento/Customer/Test/Unit/Observer/AfterAddressSaveObserverTest.php b/app/code/Magento/Customer/Test/Unit/Observer/AfterAddressSaveObserverTest.php
index 146cecb09351f..476d51de93768 100644
--- a/app/code/Magento/Customer/Test/Unit/Observer/AfterAddressSaveObserverTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Observer/AfterAddressSaveObserverTest.php
@@ -129,15 +129,15 @@ protected function setUp(): void
* @param bool $processedFlag
* @param bool $forceProcess
* @param int $addressId
- * @param int $registeredAddressId
- * @param string $configAddressType
+ * @param mixed $registeredAddressId
+ * @param mixed $configAddressType
* @dataProvider dataProviderAfterAddressSaveRestricted
*/
public function testAfterAddressSaveRestricted(
- $isVatValidationEnabled,
- $processedFlag,
- $forceProcess,
- $addressId,
+ bool $isVatValidationEnabled,
+ bool $processedFlag,
+ bool $forceProcess,
+ int $addressId,
$registeredAddressId,
$configAddressType
) {
@@ -303,17 +303,21 @@ public function testAfterAddressSaveException()
}
/**
- * @param string $vatId
+ * @param mixed $vatId
* @param int $countryId
* @param bool $isCountryInEU
+ * @param int $customerGroupId
* @param int $defaultGroupId
+ * @param bool $disableAutoGroupChange
* @dataProvider dataProviderAfterAddressSaveDefaultGroup
*/
public function testAfterAddressSaveDefaultGroup(
$vatId,
- $countryId,
- $isCountryInEU,
- $defaultGroupId
+ int $countryId,
+ bool $isCountryInEU,
+ int $customerGroupId,
+ int $defaultGroupId,
+ bool $disableAutoGroupChange
) {
$store = $this->getMockBuilder(Store::class)
->disableOriginalConstructor()
@@ -340,15 +344,15 @@ public function testAfterAddressSaveDefaultGroup(
->willReturn($store);
$customer->expects($this->once())
->method('getDisableAutoGroupChange')
- ->willReturn(false);
- $customer->expects($this->exactly(2))
+ ->willReturn($disableAutoGroupChange);
+ $customer->expects($this->any())
->method('getGroupId')
- ->willReturn(null);
- $customer->expects($this->once())
+ ->willReturn($customerGroupId);
+ $customer->expects($this->any())
->method('setGroupId')
->with($defaultGroupId)
->willReturnSelf();
- $customer->expects($this->once())
+ $customer->expects($this->any())
->method('save')
->willReturnSelf();
@@ -408,14 +412,17 @@ public function testAfterAddressSaveDefaultGroup(
public function dataProviderAfterAddressSaveDefaultGroup()
{
return [
- ['', 1, false, 1],
- [1, 1, false, 1],
+ 'when vatId is empty, non EU country and disable auto group false' => ['', 1, false, 1, 1, false],
+ 'when vatId is empty, non EU country and disable auto group true' => ['', 1, false, 1, 1, true],
+ 'when vatId is empty, non EU country, disable auto group true
+ and different groupId' => ['', 1, false, 1, 2, true],
+ 'when vatId is not empty, non EU country and disable auto group false' => [1, 1, false, 1, 1, false],
];
}
/**
- * @param string $vatId
- * @param $vatClass
+ * @param mixed $vatId
+ * @param mixed $vatClass
* @param int $countryId
* @param string $country
* @param int $newGroupId
@@ -432,15 +439,15 @@ public function dataProviderAfterAddressSaveDefaultGroup()
public function testAfterAddressSaveNewGroup(
$vatId,
$vatClass,
- $countryId,
- $country,
- $newGroupId,
- $areaCode,
- $resultVatIsValid,
- $resultRequestSuccess,
- $resultValidMessage,
- $resultInvalidMessage,
- $resultErrorMessage
+ int $countryId,
+ string $country,
+ int $newGroupId,
+ string $areaCode,
+ bool $resultVatIsValid,
+ bool $resultRequestSuccess,
+ string $resultValidMessage,
+ string $resultInvalidMessage,
+ string $resultErrorMessage
) {
$store = $this->getMockBuilder(Store::class)
->disableOriginalConstructor()
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/etc/di.xml b/app/code/Magento/Customer/etc/di.xml
index 1c703ba07f077..b178f51f89199 100644
--- a/app/code/Magento/Customer/etc/di.xml
+++ b/app/code/Magento/Customer/etc/di.xml
@@ -570,4 +570,19 @@
+
+
+
+ -
+
- website_id
+ - store_id
+ - group_id
+ - dob
+
+ -
+
- country_id
+
+
+
+
diff --git a/app/code/Magento/Customer/view/adminhtml/web/js/form/element/website.js b/app/code/Magento/Customer/view/adminhtml/web/js/form/element/website.js
index 9f119e6512652..180e64b77a5d5 100644
--- a/app/code/Magento/Customer/view/adminhtml/web/js/form/element/website.js
+++ b/app/code/Magento/Customer/view/adminhtml/web/js/form/element/website.js
@@ -5,8 +5,9 @@
define([
'Magento_Ui/js/form/element/website',
- 'uiRegistry'
-], function (Website, registry) {
+ 'uiRegistry',
+ 'underscore'
+], function (Website, registry, _) {
'use strict';
return Website.extend({
@@ -20,8 +21,24 @@ define([
sendEmailStoreIdFieldKey = 'sendemail_store_id',
groupId = registry.get('index = ' + groupIdFieldKey),
sendEmailStoreId = registry.get('index = ' + sendEmailStoreIdFieldKey),
+ customerAttributes = registry.filter('parentScope = data.customer'),
option = this.getOption(value);
+ customerAttributes.forEach(element => {
+ var requiredWebsites = element.validation['required-entry-website'];
+
+ if (!_.isArray(requiredWebsites)) {
+ return;
+ }
+ if (requiredWebsites.includes(parseInt(value, 10))) {
+ element.validation['required-entry'] = true;
+ element.required(true);
+ } else {
+ delete element.validation['required-entry'];
+ element.required(false);
+ }
+ });
+
if (groupId) {
groupId.value(option[groupIdFieldKey]);
}
diff --git a/app/code/Magento/Customer/view/adminhtml/web/template/default-address.html b/app/code/Magento/Customer/view/adminhtml/web/template/default-address.html
index e6fb2b50e3d77..f600e2867ce80 100644
--- a/app/code/Magento/Customer/view/adminhtml/web/template/default-address.html
+++ b/app/code/Magento/Customer/view/adminhtml/web/template/default-address.html
@@ -19,32 +19,32 @@
-
+
-
+
-
-
+
-
+
- T:
+ T:
- F:
+ F:
- VAT:
+ VAT:
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();
?>
-getNoWrap()) : ?>
+getNoWrap()): ?>
-
= $block->escapeHtml(__('Name')) ?>
+
+ = $block->escapeHtml(__('Name')) ?>
+
-
+
-
= $block->escapeHtml($block->getStoreLabel('prefix')) ?>
+
+ = $block->escapeHtml($block->getStoreLabel('prefix')) ?>
+
- getPrefixOptions() === false) : ?>
+ getPrefixOptions() === false): ?>
isPrefixRequired() ? ' data-validate="{required:true}"' : '' ?>>
-
+ class="input-text
+ = $block->escapeHtmlAttr($block->getAttributeValidationClass('prefix')) ?>"
+ = $block->isPrefixRequired() ? ' data-validate="{required:true}"' : '' ?>>
+
isPrefixRequired() ? ' data-validate="{required:true}"' : '' ?> >
- getPrefixOptions() as $_option) : ?>
- getObject()->getPrefix() == $_option) : ?> selected="selected">
+ class="= $block->escapeHtmlAttr($block->getAttributeValidationClass('prefix')) ?>"
+ = $block->isPrefixRequired() ? ' data-validate="{required:true}"' : '' ?> >
+ getPrefixOptions() as $_option): ?>
+ getObject()->getPrefix() == $_option): ?>
+ selected="selected">
= $block->escapeHtml(__($_option)) ?>
@@ -58,55 +67,76 @@ $suffix = $block->showSuffix();
-
= $block->escapeHtml($block->getStoreLabel('firstname')) ?>
+
+ = $block->escapeHtml($block->getStoreLabel('firstname')) ?>
+
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}"' : '' ?>>
-
+
isMiddlenameRequired(); ?>
-
= $block->escapeHtml($block->getStoreLabel('middlename')) ?>
+
+ = $block->escapeHtml($block->getStoreLabel('middlename')) ?>
+
>
+ class="input-text
+ = $block->escapeHtmlAttr($block->getAttributeValidationClass('middlename')) ?>"
+ = $isMiddlenameRequired ? ' data-validate="{required:true}"' : '' ?>>
-
= $block->escapeHtml($block->getStoreLabel('lastname')) ?>
+
+ = $block->escapeHtml($block->getStoreLabel('lastname')) ?>
+
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}"' : '' ?>>
-
+
-
= $block->escapeHtml($block->getStoreLabel('suffix')) ?>
+
+ = $block->escapeHtml($block->getStoreLabel('suffix')) ?>
+
- getSuffixOptions() === false) : ?>
+ getSuffixOptions() === false): ?>
isSuffixRequired() ? ' data-validate="{required:true}"' : '' ?>>
-
+ class="input-text
+ = $block->escapeHtmlAttr($block->getAttributeValidationClass('suffix')) ?>"
+ = $block->isSuffixRequired() ? ' data-validate="{required:true}"' : '' ?>>
+
isSuffixRequired() ? ' data-validate="{required:true}"' : '' ?>>
- getSuffixOptions() as $_option) : ?>
- getObject()->getSuffix() == $_option) : ?> selected="selected">
+ class="= $block->escapeHtmlAttr($block->getAttributeValidationClass('suffix')) ?>"
+ = $block->isSuffixRequired() ? ' data-validate="{required:true}"' : '' ?>>
+ getSuffixOptions() as $_option): ?>
+ getObject()->getSuffix() == $_option): ?>
+ selected="selected">
= $block->escapeHtml(__($_option)) ?>
@@ -116,7 +146,7 @@ $suffix = $block->showSuffix();
- getNoWrap()) : ?>
+ getNoWrap()): ?>
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/Model/Customer/DeleteCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Customer/DeleteCustomer.php
new file mode 100644
index 0000000000000..d55f50d5c074e
--- /dev/null
+++ b/app/code/Magento/CustomerGraphQl/Model/Customer/DeleteCustomer.php
@@ -0,0 +1,52 @@
+customerRepository = $customerRepository;
+ }
+
+ /**
+ * Delete customer
+ *
+ * @param CustomerInterface $customer
+ * @return void
+ * @throws GraphQlInputException
+ * @throws GraphQlNoSuchEntityException
+ */
+ public function execute(CustomerInterface $customer): void
+ {
+ try {
+ $this->customerRepository->delete($customer);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
+ }
+ }
+}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/DeleteCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/DeleteCustomer.php
new file mode 100644
index 0000000000000..a2b8c3fa78a2c
--- /dev/null
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/DeleteCustomer.php
@@ -0,0 +1,81 @@
+getCustomer = $getCustomer;
+ $this->deleteCustomer = $deleteCustomer;
+ $this->registry = $registry;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ /** @var ContextInterface $context */
+ if (false === $context->getExtensionAttributes()->getIsCustomer()) {
+ throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.'));
+ }
+
+ $isSecure = $this->registry->registry('isSecureArea');
+
+ $this->registry->unregister('isSecureArea');
+ $this->registry->register('isSecureArea', true);
+
+ $customer = $this->getCustomer->execute($context);
+ $this->deleteCustomer->execute($customer);
+
+ $this->registry->unregister('isSecureArea');
+ $this->registry->register('isSecureArea', $isSecure);
+ return true;
+ }
+}
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/CustomerGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls
index d178a6b3cf242..439ce4742ca3b 100644
--- a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls
@@ -21,6 +21,7 @@ type Mutation {
createCustomerV2 (input: CustomerCreateInput! @doc(description: "An input object that defines the customer to be created.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomer") @doc(description:"Create a customer account.")
updateCustomer (input: CustomerInput! @doc(description: "An input object that defines the customer characteristics to update.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @doc(description:"Use `updateCustomerV2` instead.")
updateCustomerV2 (input: CustomerUpdateInput! @doc(description: "An input object that defines the customer characteristics to update.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @doc(description:"Update the customer's personal information.")
+ deleteCustomer: Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\DeleteCustomer") @doc(description:"Delete customer account")
revokeCustomerToken: RevokeCustomerTokenOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RevokeCustomerToken") @doc(description:"Revoke the customer token.")
createCustomerAddress(input: CustomerAddressInput!): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomerAddress") @doc(description: "Create a billing or shipping address for a customer or guest.")
updateCustomerAddress(id: Int! @doc(description: "The ID assigned to the customer address."), input: CustomerAddressInput @doc(description: "An input object that contains changes to the customer address.")): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerAddress") @doc(description: "Update the billing or shipping address of a customer or guest.")
diff --git a/app/code/Magento/CustomerImportExport/Model/Import/AbstractCustomer.php b/app/code/Magento/CustomerImportExport/Model/Import/AbstractCustomer.php
index 6d24e8b02cb64..8fb08cecc54c4 100644
--- a/app/code/Magento/CustomerImportExport/Model/Import/AbstractCustomer.php
+++ b/app/code/Magento/CustomerImportExport/Model/Import/AbstractCustomer.php
@@ -6,6 +6,9 @@
namespace Magento\CustomerImportExport\Model\Import;
+use Magento\Framework\Validator\EmailAddress;
+use Magento\Framework\Validator\ValidateException;
+use Magento\Framework\Validator\ValidatorChain;
use Magento\ImportExport\Model\Import;
use Magento\CustomerImportExport\Model\ResourceModel\Import\Customer\Storage;
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
@@ -227,6 +230,7 @@ abstract protected function _validateRowForDelete(array $rowData, $rowNumber);
* @param array $rowData
* @param int $rowNumber
* @return bool
+ * @throws ValidateException
*/
protected function _checkUniqueKey(array $rowData, $rowNumber)
{
@@ -238,7 +242,7 @@ protected function _checkUniqueKey(array $rowData, $rowNumber)
$email = strtolower($rowData[static::COLUMN_EMAIL]);
$website = $rowData[static::COLUMN_WEBSITE];
- if (!\Zend_Validate::is($email, \Magento\Framework\Validator\EmailAddress::class)) {
+ if (!ValidatorChain::is($email, EmailAddress::class)) {
$this->addRowError(static::ERROR_INVALID_EMAIL, $rowNumber, static::COLUMN_EMAIL);
} elseif (!isset($this->_websiteCodeToId[$website])) {
$this->addRowError(static::ERROR_INVALID_WEBSITE, $rowNumber, static::COLUMN_WEBSITE);
diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Address.php b/app/code/Magento/CustomerImportExport/Model/Import/Address.php
index 3390bdf7219b4..4ba93557f8542 100644
--- a/app/code/Magento/CustomerImportExport/Model/Import/Address.php
+++ b/app/code/Magento/CustomerImportExport/Model/Import/Address.php
@@ -160,6 +160,7 @@ class Address extends AbstractCustomer
*
* @var array
* @deprecated 100.3.4 field not in use
+ * @see Nothing
*/
protected $_regionParameters;
@@ -190,18 +191,21 @@ class Address extends AbstractCustomer
/**
* @var \Magento\Eav\Model\Config
* @deprecated 100.3.4 field not-in use
+ * @see Nothing
*/
protected $_eavConfig;
/**
* @var \Magento\Customer\Model\AddressFactory
* @deprecated 100.3.4 not utilized anymore
+ * @see Nothing
*/
protected $_addressFactory;
/**
* @var \Magento\Framework\Stdlib\DateTime
* @deprecated 100.3.4 the property isn't used
+ * @see Nothing
*/
protected $dateTime;
@@ -512,7 +516,7 @@ protected function _importData()
{
//Preparing data for mass validation/import.
$rows = [];
- while ($bunch = $this->_dataSourceModel->getNextBunch()) {
+ while ($bunch = $this->_dataSourceModel->getNextUniqueBunch($this->getIds())) {
$rows[] = $bunch;
}
@@ -521,7 +525,7 @@ protected function _importData()
$this->_dataSourceModel->getIterator()->rewind();
//Importing
- while ($bunch = $this->_dataSourceModel->getNextBunch()) {
+ while ($bunch = $this->_dataSourceModel->getNextUniqueBunch($this->getIds())) {
$newRows = [];
$updateRows = [];
$attributes = [];
diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php
index c2383a2e9f6ba..0249985f27e82 100644
--- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php
+++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php
@@ -502,12 +502,13 @@ protected function _prepareDataForUpdate(array $rowData)
* Import data rows
*
* @return bool
+ * @throws \Exception
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
protected function _importData()
{
- while ($bunch = $this->_dataSourceModel->getNextBunch()) {
+ while ($bunch = $this->_dataSourceModel->getNextUniqueBunch($this->getIds())) {
$this->prepareCustomerData($bunch);
$entitiesToCreate = [];
$entitiesToUpdate = [];
diff --git a/app/code/Magento/CustomerImportExport/Model/ResourceModel/Import/CustomerComposite/Data.php b/app/code/Magento/CustomerImportExport/Model/ResourceModel/Import/CustomerComposite/Data.php
index bf82bfa364d7c..26ffe809e5c20 100644
--- a/app/code/Magento/CustomerImportExport/Model/ResourceModel/Import/CustomerComposite/Data.php
+++ b/app/code/Magento/CustomerImportExport/Model/ResourceModel/Import/CustomerComposite/Data.php
@@ -10,14 +10,14 @@
class Data extends \Magento\ImportExport\Model\ResourceModel\Import\Data
{
/**
- * Entity type
+ * Entity
*
* @var string
*/
protected $_entityType = CustomerComposite::COMPONENT_ENTITY_CUSTOMER;
/**
- * Customer attributes
+ * Customer attributes data
*
* @var array
*/
@@ -27,7 +27,7 @@ class Data extends \Magento\ImportExport\Model\ResourceModel\Import\Data
* Class constructor
*
* @param \Magento\Framework\Model\ResourceModel\Db\Context $context
- * @param \Magento\Framework\Json\Helper\Data $coreHelper
+ * @param \Magento\Framework\Json\Helper\Data $jsonHelper
* @param string $connectionName
* @param array $arguments
*/
@@ -50,11 +50,12 @@ public function __construct(
/**
* Get next bunch of validated rows.
*
+ * @param array|null $ids
* @return array|null
*/
- public function getNextBunch()
+ public function getNextUniqueBunch($ids = null)
{
- $bunchRows = parent::getNextBunch();
+ $bunchRows = parent::getNextUniqueBunch($ids);
if ($bunchRows != null) {
$rows = [];
foreach ($bunchRows as $rowNumber => $rowData) {
diff --git a/app/code/Magento/CustomerImportExport/Test/Unit/Model/ResourceModel/Import/CustomerComposite/DataTest.php b/app/code/Magento/CustomerImportExport/Test/Unit/Model/ResourceModel/Import/CustomerComposite/DataTest.php
index ad8361e4e8762..f21827060d428 100644
--- a/app/code/Magento/CustomerImportExport/Test/Unit/Model/ResourceModel/Import/CustomerComposite/DataTest.php
+++ b/app/code/Magento/CustomerImportExport/Test/Unit/Model/ResourceModel/Import/CustomerComposite/DataTest.php
@@ -125,7 +125,7 @@ public function testGetNextBunch($entityType, $bunchData, $expectedData)
'arguments' => $dependencies,
]
);
- $this->assertEquals($expectedData, $object->getNextBunch());
+ $this->assertEquals($expectedData, $object->getNextUniqueBunch());
}
/**
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/Collector/Collector.php b/app/code/Magento/Deploy/Collector/Collector.php
index b09001a7ac04c..441d165f6792a 100644
--- a/app/code/Magento/Deploy/Collector/Collector.php
+++ b/app/code/Magento/Deploy/Collector/Collector.php
@@ -93,6 +93,9 @@ public function collect()
if ($file->getModule() && !$this->moduleManager->isEnabled($file->getModule())) {
continue;
}
+ if (!$file->getFileName()) {
+ continue;
+ }
$file->setDeployedFileName($this->fileNameResolver->resolve($file->getFileName()));
$params = $this->getParams($file);
$packagePath = "{$params['area']}/{$params['theme']}/{$params['locale']}";
diff --git a/app/code/Magento/Deploy/Console/Command/ShowModeCommand.php b/app/code/Magento/Deploy/Console/Command/ShowModeCommand.php
index b28051e603f6c..bcc2ec7d3425f 100644
--- a/app/code/Magento/Deploy/Console/Command/ShowModeCommand.php
+++ b/app/code/Magento/Deploy/Console/Command/ShowModeCommand.php
@@ -6,6 +6,7 @@
namespace Magento\Deploy\Console\Command;
+use Magento\Framework\Console\Cli;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\App\State;
use Symfony\Component\Console\Command\Command;
@@ -69,7 +70,9 @@ protected function execute(InputInterface $input, OutputInterface $output)
if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
$output->writeln($e->getTraceAsString());
}
- return;
+ return Cli::RETURN_FAILURE;
}
+
+ return Cli::RETURN_SUCCESS;
}
}
diff --git a/app/code/Magento/Deploy/Package/Processor/PreProcessor/Css.php b/app/code/Magento/Deploy/Package/Processor/PreProcessor/Css.php
index 152c95f86552c..6fc9c05eb3527 100644
--- a/app/code/Magento/Deploy/Package/Processor/PreProcessor/Css.php
+++ b/app/code/Magento/Deploy/Package/Processor/PreProcessor/Css.php
@@ -120,6 +120,20 @@ private function hasOverrides(PackageFile $parentFile, Package $package)
return false;
}
+ /**
+ * See if given path is local or remote URL
+ *
+ * @param string $path
+ * @return bool
+ */
+ private function isLocal(string $path): bool
+ {
+ $pattern = '{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i';
+ $result = preg_match($pattern, $path);
+
+ return is_int($result) ? (bool) $result : true;
+ }
+
/**
* Build map file
*
@@ -135,13 +149,22 @@ private function buildMap($packagePath, $filePath, $fullPath)
$imports = [];
$this->map[$fullPath] = [];
- $content = $this->staticDir->readFile($this->minification->addMinifiedSign($fullPath));
+ $tmpFilename = $this->minification->addMinifiedSign($fullPath);
+ if ($this->staticDir->isReadable($tmpFilename)) {
+ $content = $this->staticDir->readFile($tmpFilename);
+ } else {
+ $content = '';
+ }
$callback = function ($matchContent) use ($packagePath, $filePath, &$imports) {
- $importRelPath = $this->normalize(pathinfo($filePath, PATHINFO_DIRNAME) . '/' . $matchContent['path']);
- $imports[$importRelPath] = $this->normalize(
- $packagePath . '/' . pathinfo($filePath, PATHINFO_DIRNAME) . '/' . $matchContent['path']
- );
+ if ($this->isLocal($matchContent['path'])) {
+ $importRelPath = $this->normalize(
+ pathinfo($filePath, PATHINFO_DIRNAME) . '/' . $matchContent['path']
+ );
+ $imports[$importRelPath] = $this->normalize(
+ $packagePath . '/' . pathinfo($filePath, PATHINFO_DIRNAME) . '/' . $matchContent['path']
+ );
+ }
};
preg_replace_callback(Import::REPLACE_PATTERN, $callback, $content);
diff --git a/app/code/Magento/Deploy/Package/Processor/PreProcessor/Less.php b/app/code/Magento/Deploy/Package/Processor/PreProcessor/Less.php
index dbda66cc948f1..29283c1c26914 100644
--- a/app/code/Magento/Deploy/Package/Processor/PreProcessor/Less.php
+++ b/app/code/Magento/Deploy/Package/Processor/PreProcessor/Less.php
@@ -59,6 +59,11 @@ class Less implements ProcessorInterface
*/
private $map = [];
+ /**
+ * @var array
+ */
+ private $pFileCache = [];
+
/**
* Less constructor
*
@@ -95,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;
@@ -132,6 +137,7 @@ private function hasOverrides(PackageFile $parentFile, Package $package)
$currentPackageFiles = array_merge($package->getFilesByType('less'), $package->getFilesByType('css'));
foreach ($currentPackageFiles as $file) {
+ $this->pFileCache = [];
if ($this->inParentFiles($file->getDeployedFileName(), $parentFile->getFileName(), $map)) {
return true;
}
@@ -154,6 +160,10 @@ private function inParentFiles($fileName, $parentFile, $map)
return true;
} else {
foreach ($map[$parentFile] as $pFile) {
+ if (in_array($pFile, $this->pFileCache)) {
+ continue;
+ }
+ $this->pFileCache[] = $pFile;
if ($this->inParentFiles($fileName, $pFile, $map)) {
return true;
}
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/Collector/CollectorTest.php b/app/code/Magento/Deploy/Test/Unit/Collector/CollectorTest.php
new file mode 100644
index 0000000000000..b37b6981e8213
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Collector/CollectorTest.php
@@ -0,0 +1,116 @@
+sourcePool = $this->getMockBuilder(SourcePool::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->fileNameResolver = $this->getMockBuilder(FileNameResolver::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->packageFactory = $this->getMockBuilder(PackageFactory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->moduleManager = $this->getMockBuilder(Manager::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->source = $this->getMockBuilder(SourceInterface::class)
+ ->getMockForAbstractClass();
+ $this->fileWithName = $this->getMockBuilder(PackageFile::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->fileWithName->expects($this->any())
+ ->method('getFileName')
+ ->willReturn('name');
+ $this->fileWithoutName = $this->getMockBuilder(PackageFile::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->fileWithoutName->expects($this->any())
+ ->method('getFileName')
+ ->willReturn('');
+
+ $this->model = new Collector(
+ $this->sourcePool,
+ $this->fileNameResolver,
+ $this->packageFactory,
+ $this->moduleManager
+ );
+ }
+
+ public function testCollect(): void
+ {
+ $this->sourcePool->expects($this->once())
+ ->method('getAll')
+ ->willReturn([$this->source]);
+ $this->source->expects($this->once())
+ ->method('get')
+ ->willReturn([$this->fileWithoutName, $this->fileWithName]);
+ $this->fileWithoutName->expects($this->exactly(0))
+ ->method('setDeployedFileName');
+ $this->fileWithName->expects($this->exactly(1))
+ ->method('setDeployedFileName');
+ $this->model->collect();
+ }
+}
diff --git a/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php b/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php
index bb545b1d51b05..64750847295ea 100644
--- a/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php
+++ b/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php
@@ -42,7 +42,7 @@ class InputValidatorTest extends TestCase
protected $localeValidator;
/**
- * @throws \Zend_Validate_Exception
+ * @inheritdoc
*/
protected function setUp(): void
{
@@ -76,9 +76,6 @@ protected function setUp(): void
);
}
- /**
- * @throws \Zend_Validate_Exception
- */
public function testValidate()
{
$input = $this->getMockBuilder(ArrayInput::class)
diff --git a/app/code/Magento/Deploy/Test/Unit/Package/Processor/PreProcessor/CssTest.php b/app/code/Magento/Deploy/Test/Unit/Package/Processor/PreProcessor/CssTest.php
new file mode 100644
index 0000000000000..39f14bb2e9780
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Package/Processor/PreProcessor/CssTest.php
@@ -0,0 +1,109 @@
+filesystem = $this->getMockBuilder(Filesystem::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->minification = $this->getMockBuilder(Minification::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->staticDir = $this->getMockBuilder(ReadInterface::class)
+ ->getMockForAbstractClass();
+ $this->filesystem->expects($this->any())
+ ->method('getDirectoryRead')
+ ->willReturn($this->staticDir);
+ $this->package = $this->getMockBuilder(Package::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->file = $this->getMockBuilder(PackageFile::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->model = new Css($this->filesystem, $this->minification);
+ }
+
+ public function testProcessWithNonExistingFile(): void
+ {
+ $options = [
+ DeployStaticOptions::NO_CSS => false
+ ];
+ $nonReadableFile = 'nonReadableFile';
+
+ $this->package->expects($this->once())
+ ->method('getParentFiles')
+ ->with('css')
+ ->willReturn([$this->file]);
+ $this->file->expects($this->once())
+ ->method('getPackage')
+ ->willReturn($this->package);
+ $this->file->expects($this->any())
+ ->method('getDeployedFilePath')
+ ->willReturn('some/path/to/file');
+ $this->package->expects($this->any())
+ ->method('getFiles')
+ ->willReturn([]);
+ $this->minification->expects($this->once())
+ ->method('addMinifiedSign')
+ ->willReturn($nonReadableFile);
+ $this->staticDir->expects($this->once())
+ ->method('isReadable')
+ ->with($nonReadableFile)
+ ->willReturn(false);
+ $this->staticDir->expects($this->never())
+ ->method('readFile');
+ $this->model->process($this->package, $options);
+ }
+}
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/Console/Command/ProfilerDisableCommand.php b/app/code/Magento/Developer/Console/Command/ProfilerDisableCommand.php
index c6c7fd8ca1cfd..2743c4191e3f1 100644
--- a/app/code/Magento/Developer/Console/Command/ProfilerDisableCommand.php
+++ b/app/code/Magento/Developer/Console/Command/ProfilerDisableCommand.php
@@ -6,6 +6,8 @@
namespace Magento\Developer\Console\Command;
+use InvalidArgumentException;
+use Magento\Framework\Console\Cli;
use Magento\Framework\Filesystem\Io\File;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -14,19 +16,13 @@
class ProfilerDisableCommand extends Command
{
/**
- * Profiler flag file
+ * Profiler flag file path
*/
- const PROFILER_FLAG_FILE = 'var/profiler.flag';
+ public const PROFILER_FLAG_FILE = 'var/profiler.flag';
- /**
- * Command name
- */
- const COMMAND_NAME = 'dev:profiler:disable';
+ public const COMMAND_NAME = 'dev:profiler:disable';
- /**
- * Success message
- */
- const SUCCESS_MESSAGE = 'Profiler disabled.';
+ public const SUCCESS_MESSAGE = 'Profiler disabled.';
/**
* @var File
@@ -46,7 +42,7 @@ public function __construct(File $filesystem)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function configure()
{
@@ -57,16 +53,19 @@ protected function configure()
}
/**
- * {@inheritdoc}
- * @throws \InvalidArgumentException
+ * @inheritdoc
+ *
+ * @throws InvalidArgumentException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->filesystem->rm(BP . '/' . self::PROFILER_FLAG_FILE);
if (!$this->filesystem->fileExists(BP . '/' . self::PROFILER_FLAG_FILE)) {
$output->writeln('
'. self::SUCCESS_MESSAGE . ' ');
- return;
+ return Cli::RETURN_SUCCESS;
}
$output->writeln('
Something went wrong while disabling the profiler. ');
+
+ return Cli::RETURN_FAILURE;
}
}
diff --git a/app/code/Magento/Developer/Console/Command/ProfilerEnableCommand.php b/app/code/Magento/Developer/Console/Command/ProfilerEnableCommand.php
index 21e2f5780c4c5..eb9575b08fbd0 100644
--- a/app/code/Magento/Developer/Console/Command/ProfilerEnableCommand.php
+++ b/app/code/Magento/Developer/Console/Command/ProfilerEnableCommand.php
@@ -6,38 +6,35 @@
namespace Magento\Developer\Console\Command;
+use InvalidArgumentException;
+use Magento\Framework\Console\Cli;
use Magento\Framework\Filesystem\Io\File;
+use Magento\Framework\Profiler\Driver\Standard\Output\Csvfile;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\Console\Input\InputArgument;
class ProfilerEnableCommand extends Command
{
/**
- * Profiler flag file
+ * Profiler flag file path
*/
- const PROFILER_FLAG_FILE = 'var/profiler.flag';
+ public const PROFILER_FLAG_FILE = 'var/profiler.flag';
/**
* Profiler type default setting
*/
- const TYPE_DEFAULT = 'html';
+ public const TYPE_DEFAULT = 'html';
/**
* Built in profiler types
*/
- const BUILT_IN_TYPES = ['html', 'csvfile'];
+ public const BUILT_IN_TYPES = ['html', 'csvfile'];
- /**
- * Command name
- */
- const COMMAND_NAME = 'dev:profiler:enable';
+ public const COMMAND_NAME = 'dev:profiler:enable';
- /**
- * Success message
- */
- const SUCCESS_MESSAGE = 'Profiler enabled with %s output.';
+ public const SUCCESS_MESSAGE = 'Profiler enabled with %s output.';
/**
* @var File
@@ -57,7 +54,7 @@ public function __construct(File $filesystem)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function configure()
{
@@ -69,8 +66,9 @@ protected function configure()
}
/**
- * {@inheritdoc}
- * @throws \InvalidArgumentException
+ * @inheritdoc
+ *
+ * @throws InvalidArgumentException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -95,15 +93,17 @@ protected function execute(InputInterface $input, OutputInterface $output)
$output->write(
'
' . sprintf(
'Output will be saved in %s',
- \Magento\Framework\Profiler\Driver\Standard\Output\Csvfile::DEFAULT_FILEPATH
+ Csvfile::DEFAULT_FILEPATH
)
. ' '
);
}
$output->write(PHP_EOL);
- return;
+ return Cli::RETURN_SUCCESS;
}
$output->writeln('
Something went wrong while enabling the profiler. ');
+
+ return Cli::RETURN_FAILURE;
}
}
diff --git a/app/code/Magento/Developer/Console/Command/QueryLogDisableCommand.php b/app/code/Magento/Developer/Console/Command/QueryLogDisableCommand.php
index 52bb148ffff0c..4db239b6b74ad 100644
--- a/app/code/Magento/Developer/Console/Command/QueryLogDisableCommand.php
+++ b/app/code/Magento/Developer/Console/Command/QueryLogDisableCommand.php
@@ -6,24 +6,20 @@
namespace Magento\Developer\Console\Command;
-use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
+use InvalidArgumentException;
use Magento\Framework\App\DeploymentConfig\Writer;
use Magento\Framework\Config\File\ConfigFilePool;
+use Magento\Framework\Console\Cli;
use Magento\Framework\DB\Logger\LoggerProxy;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
class QueryLogDisableCommand extends Command
{
- /**
- * command name
- */
- const COMMAND_NAME = 'dev:query-log:disable';
+ public const COMMAND_NAME = 'dev:query-log:disable';
- /**
- * Success message
- */
- const SUCCESS_MESSAGE = "DB query logging disabled.";
+ public const SUCCESS_MESSAGE = "DB query logging disabled.";
/**
* @var Writer
@@ -33,7 +29,7 @@ class QueryLogDisableCommand extends Command
/**
* QueryLogDisableCommand constructor.
* @param Writer $deployConfigWriter
- * @param null $name
+ * @param ?string $name
*/
public function __construct(
Writer $deployConfigWriter,
@@ -44,7 +40,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function configure()
{
@@ -55,8 +51,9 @@ protected function configure()
}
/**
- * {@inheritdoc}
- * @throws \InvalidArgumentException
+ * @inheritdoc
+ *
+ * @throws InvalidArgumentException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -64,5 +61,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->deployConfigWriter->saveConfig([ConfigFilePool::APP_ENV => [LoggerProxy::CONF_GROUP_NAME => $data]]);
$output->writeln("
". self::SUCCESS_MESSAGE . " ");
+
+ return Cli::RETURN_SUCCESS;
}
}
diff --git a/app/code/Magento/Developer/Console/Command/QueryLogEnableCommand.php b/app/code/Magento/Developer/Console/Command/QueryLogEnableCommand.php
index c221bed3877bf..34634a6ed4ac0 100644
--- a/app/code/Magento/Developer/Console/Command/QueryLogEnableCommand.php
+++ b/app/code/Magento/Developer/Console/Command/QueryLogEnableCommand.php
@@ -6,40 +6,36 @@
namespace Magento\Developer\Console\Command;
+use InvalidArgumentException;
+use Magento\Framework\App\DeploymentConfig\Writer;
+use Magento\Framework\Config\File\ConfigFilePool;
+use Magento\Framework\Console\Cli;
use Magento\Framework\DB\Logger\LoggerProxy;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-use Magento\Framework\App\DeploymentConfig\Writer;
-use Magento\Framework\Config\File\ConfigFilePool;
class QueryLogEnableCommand extends Command
{
/**
* input parameter log-all-queries
*/
- const INPUT_ARG_LOG_ALL_QUERIES = 'include-all-queries';
+ public const INPUT_ARG_LOG_ALL_QUERIES = 'include-all-queries';
/**
* input parameter log-query-time
*/
- const INPUT_ARG_LOG_QUERY_TIME = 'query-time-threshold';
+ public const INPUT_ARG_LOG_QUERY_TIME = 'query-time-threshold';
/**
* input parameter log-call-stack
*/
- const INPUT_ARG_LOG_CALL_STACK = 'include-call-stack';
+ public const INPUT_ARG_LOG_CALL_STACK = 'include-call-stack';
- /**
- * command name
- */
- const COMMAND_NAME = 'dev:query-log:enable';
+ public const COMMAND_NAME = 'dev:query-log:enable';
- /**
- * Success message
- */
- const SUCCESS_MESSAGE = "DB query logging enabled.";
+ public const SUCCESS_MESSAGE = "DB query logging enabled.";
/**
* @var Writer
@@ -49,7 +45,7 @@ class QueryLogEnableCommand extends Command
/**
* QueryLogEnableCommand constructor.
* @param Writer $deployConfigWriter
- * @param null $name
+ * @param ?string $name
*/
public function __construct(
Writer $deployConfigWriter,
@@ -60,7 +56,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function configure()
{
@@ -96,8 +92,9 @@ protected function configure()
}
/**
- * {@inheritdoc}
- * @throws \InvalidArgumentException
+ * @inheritdoc
+ *
+ * @throws InvalidArgumentException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -116,5 +113,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->deployConfigWriter->saveConfig([ConfigFilePool::APP_ENV => $configGroup]);
$output->writeln("
". self::SUCCESS_MESSAGE . " ");
+
+ return Cli::RETURN_SUCCESS;
}
}
diff --git a/app/code/Magento/Developer/Console/Command/SourceThemeDeployCommand.php b/app/code/Magento/Developer/Console/Command/SourceThemeDeployCommand.php
index 309d6c09a4ac1..66a4ff1d96f4f 100644
--- a/app/code/Magento/Developer/Console/Command/SourceThemeDeployCommand.php
+++ b/app/code/Magento/Developer/Console/Command/SourceThemeDeployCommand.php
@@ -5,9 +5,13 @@
*/
namespace Magento\Developer\Console\Command;
+use InvalidArgumentException;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\View\Asset\Publisher;
use Magento\Framework\Console\Cli;
+use Magento\Framework\Filesystem\Io\File;
use Magento\Framework\Validator\Locale;
+use Magento\Framework\View\Asset\File\NotFoundException;
use Magento\Framework\View\Asset\Repository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@@ -62,22 +66,30 @@ class SourceThemeDeployCommand extends Command
*/
private $assetRepository;
+ /**
+ * @var File
+ */
+ private $file;
+
/**
* Constructor
*
* @param Locale $validator
* @param Publisher $assetPublisher
* @param Repository $assetRepository
+ * @param File|null $file
*/
public function __construct(
Locale $validator,
Publisher $assetPublisher,
- Repository $assetRepository
+ Repository $assetRepository,
+ File $file = null
) {
parent::__construct('dev:source-theme:deploy');
$this->validator = $validator;
$this->assetPublisher = $assetPublisher;
$this->assetRepository = $assetRepository;
+ $this->file = $file ?: ObjectManager::getInstance()->get(File::class);
}
/**
@@ -130,7 +142,8 @@ protected function configure()
/**
* @inheritdoc
- * @throws \InvalidArgumentException
+ *
+ * @throws InvalidArgumentException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -142,13 +155,13 @@ protected function execute(InputInterface $input, OutputInterface $output)
$files = $input->getArgument(self::FILE_ARGUMENT);
if (!$this->validator->isValid($locale)) {
- throw new \InvalidArgumentException(
+ throw new InvalidArgumentException(
$locale . ' argument has invalid value, please run info:language:list for list of available locales'
);
}
if ($theme === null || !preg_match('#^[\w\-]+\/[\w\-]+$#', $theme)) {
- throw new \InvalidArgumentException(
+ throw new InvalidArgumentException(
'Value "' . $theme . '" of the option "' . self::THEME_OPTION .
'" has invalid format. The format should be "Vendor/theme".'
);
@@ -164,8 +177,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$output->writeln($message);
foreach ($files as $file) {
- // phpcs:ignore Magento2.Functions.DiscouragedFunction
- $fileInfo = pathinfo($file);
+ $fileInfo = $this->file->getPathInfo($file);
$asset = $this->assetRepository->createAsset(
$fileInfo['dirname'] . DIRECTORY_SEPARATOR . $fileInfo['basename'] . '.' . $type,
[
@@ -177,8 +189,8 @@ protected function execute(InputInterface $input, OutputInterface $output)
try {
$this->assetPublisher->publish($asset);
- } catch (\Magento\Framework\View\Asset\File\NotFoundException $e) {
- throw new \InvalidArgumentException(
+ } catch (NotFoundException $e) {
+ throw new InvalidArgumentException(
'Verify entered values of the argument and options. ' . $e->getMessage()
);
}
diff --git a/app/code/Magento/Developer/Console/Command/TemplateHintsDisableCommand.php b/app/code/Magento/Developer/Console/Command/TemplateHintsDisableCommand.php
index dbecc673c1b85..5b19f77469ec8 100644
--- a/app/code/Magento/Developer/Console/Command/TemplateHintsDisableCommand.php
+++ b/app/code/Magento/Developer/Console/Command/TemplateHintsDisableCommand.php
@@ -6,22 +6,18 @@
namespace Magento\Developer\Console\Command;
+use InvalidArgumentException;
+use Magento\Framework\App\Config\ConfigResource\ConfigInterface;
+use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use Magento\Framework\App\Config\ConfigResource\ConfigInterface;
class TemplateHintsDisableCommand extends Command
{
- /**
- * command name
- */
- const COMMAND_NAME = 'dev:template-hints:disable';
+ public const COMMAND_NAME = 'dev:template-hints:disable';
- /**
- * Success message
- */
- const SUCCESS_MESSAGE = "Template hints disabled. Refresh cache types";
+ public const SUCCESS_MESSAGE = "Template hints disabled. Refresh cache types";
/**
* @var ConfigInterface
@@ -40,7 +36,7 @@ public function __construct(ConfigInterface $resourceConfig)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function configure()
{
@@ -51,12 +47,15 @@ protected function configure()
}
/**
- * {@inheritdoc}
- * @throws \InvalidArgumentException
+ * @inheritdoc
+ *
+ * @throws InvalidArgumentException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->resourceConfig->saveConfig('dev/debug/template_hints_storefront', 0, 'default', 0);
$output->writeln("
". self::SUCCESS_MESSAGE . " ");
+
+ return Cli::RETURN_SUCCESS;
}
}
diff --git a/app/code/Magento/Developer/Console/Command/TemplateHintsEnableCommand.php b/app/code/Magento/Developer/Console/Command/TemplateHintsEnableCommand.php
index b1813686c565f..7661f5c6416f8 100644
--- a/app/code/Magento/Developer/Console/Command/TemplateHintsEnableCommand.php
+++ b/app/code/Magento/Developer/Console/Command/TemplateHintsEnableCommand.php
@@ -6,23 +6,18 @@
namespace Magento\Developer\Console\Command;
+use InvalidArgumentException;
+use Magento\Framework\App\Config\ConfigResource\ConfigInterface;
+use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use Magento\Framework\App\Config\ConfigResource\ConfigInterface;
class TemplateHintsEnableCommand extends Command
{
+ public const COMMAND_NAME = 'dev:template-hints:enable';
- /**
- * command name
- */
- const COMMAND_NAME = 'dev:template-hints:enable';
-
- /**
- * Success message
- */
- const SUCCESS_MESSAGE = "Template hints enabled.";
+ public const SUCCESS_MESSAGE = "Template hints enabled.";
/**
* @var ConfigInterface
@@ -41,7 +36,7 @@ public function __construct(ConfigInterface $resourceConfig)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function configure()
{
@@ -52,12 +47,15 @@ protected function configure()
}
/**
- * {@inheritdoc}
- * @throws \InvalidArgumentException
+ * @inheritdoc
+ *
+ * @throws InvalidArgumentException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->resourceConfig->saveConfig('dev/debug/template_hints_storefront', 1, 'default', 0);
$output->writeln("
". self::SUCCESS_MESSAGE . " ");
+
+ return Cli::RETURN_SUCCESS;
}
}
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/Block/Adminhtml/Unitofmeasure.php b/app/code/Magento/Dhl/Block/Adminhtml/Unitofmeasure.php
index bf94a3b5c89ad..ec9ca694fe309 100644
--- a/app/code/Magento/Dhl/Block/Adminhtml/Unitofmeasure.php
+++ b/app/code/Magento/Dhl/Block/Adminhtml/Unitofmeasure.php
@@ -6,6 +6,7 @@
namespace Magento\Dhl\Block\Adminhtml;
use Magento\Dhl\Model;
+use Magento\Framework\Measure\Weight;
use Magento\Shipping\Helper;
use Magento\Backend\Block\Template\Context;
use Magento\Config\Block\System\Config\Form\Field;
@@ -17,8 +18,6 @@
class Unitofmeasure extends Field
{
/**
- * Carrier helper
- *
* @var Helper\Carrier
*/
protected $carrierHelper;
@@ -73,8 +72,8 @@ public function _construct()
$convertedWeight = $this->carrierHelper->convertMeasureWeight(
$kgWeight,
- \Zend_Measure_Weight::KILOGRAM,
- \Zend_Measure_Weight::POUND
+ Weight::KILOGRAM,
+ Weight::POUND
);
$weight = sprintf('%.3f', $convertedWeight);
diff --git a/app/code/Magento/Dhl/Model/Carrier.php b/app/code/Magento/Dhl/Model/Carrier.php
index 5d47871f2298d..e64c7f1ae377b 100644
--- a/app/code/Magento/Dhl/Model/Carrier.php
+++ b/app/code/Magento/Dhl/Model/Carrier.php
@@ -6,6 +6,7 @@
namespace Magento\Dhl\Model;
+use Laminas\Http\Request as HttpRequest;
use Magento\Catalog\Model\Product\Type;
use Magento\Dhl\Model\Validator\XmlValidator;
use Magento\Framework\App\ObjectManager;
@@ -15,6 +16,9 @@
use Magento\Framework\HTTP\AsyncClient\HttpResponseDeferredInterface;
use Magento\Framework\HTTP\AsyncClient\Request;
use Magento\Framework\HTTP\AsyncClientInterface;
+use Magento\Framework\HTTP\LaminasClient;
+use Magento\Framework\Measure\Length;
+use Magento\Framework\Measure\Weight;
use Magento\Framework\Module\Dir;
use Magento\Framework\Xml\Security;
use Magento\Quote\Model\Quote\Address\RateRequest;
@@ -57,7 +61,7 @@ class Carrier extends \Magento\Dhl\Model\AbstractDhl implements \Magento\Shippin
*
* @var string[]
*/
- protected $_customizableContainerTypes = [self::DHL_CONTENT_TYPE_NON_DOC];
+ protected $_customizableContainerTypes = [self::DHL_CONTENT_TYPE_NON_DOC, self::DHL_CONTENT_TYPE_DOC];
/**
* Code of the carrier
@@ -201,7 +205,8 @@ class Carrier extends \Magento\Dhl\Model\AbstractDhl implements \Magento\Shippin
protected $_dateTime;
/**
- * @var \Magento\Framework\HTTP\ZendClientFactory
+ * @var \Magento\Framework\HTTP\LaminasClientFactory
+ * phpcs:ignore Magento2.Commenting.ClassAndInterfacePHPDocFormatting
* @deprecated Use asynchronous client.
* @see $httpClient
*/
@@ -260,7 +265,7 @@ class Carrier extends \Magento\Dhl\Model\AbstractDhl implements \Magento\Shippin
* @param \Magento\Framework\Math\Division $mathDivision
* @param \Magento\Framework\Filesystem\Directory\ReadFactory $readFactory
* @param \Magento\Framework\Stdlib\DateTime $dateTime
- * @param \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory
+ * @param \Magento\Framework\HTTP\LaminasClientFactory $httpClientFactory
* @param array $data
* @param \Magento\Dhl\Model\Validator\XmlValidator|null $xmlValidator
* @param ProductMetadataInterface|null $productMetadata
@@ -292,7 +297,7 @@ public function __construct(
\Magento\Framework\Math\Division $mathDivision,
\Magento\Framework\Filesystem\Directory\ReadFactory $readFactory,
\Magento\Framework\Stdlib\DateTime $dateTime,
- \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory,
+ \Magento\Framework\HTTP\LaminasClientFactory $httpClientFactory,
array $data = [],
\Magento\Dhl\Model\Validator\XmlValidator $xmlValidator = null,
ProductMetadataInterface $productMetadata = null,
@@ -604,18 +609,18 @@ public function getCode($type, $code = '')
'dimensions' => ['HEIGHT' => __('Height'), 'DEPTH' => __('Depth'), 'WIDTH' => __('Width')],
'size' => ['0' => __('Regular'), '1' => __('Specific')],
'dimensions_variables' => [
- 'L' => \Zend_Measure_Weight::POUND,
- 'LB' => \Zend_Measure_Weight::POUND,
- 'POUND' => \Zend_Measure_Weight::POUND,
- 'K' => \Zend_Measure_Weight::KILOGRAM,
- 'KG' => \Zend_Measure_Weight::KILOGRAM,
- 'KILOGRAM' => \Zend_Measure_Weight::KILOGRAM,
- 'I' => \Zend_Measure_Length::INCH,
- 'IN' => \Zend_Measure_Length::INCH,
- 'INCH' => \Zend_Measure_Length::INCH,
- 'C' => \Zend_Measure_Length::CENTIMETER,
- 'CM' => \Zend_Measure_Length::CENTIMETER,
- 'CENTIMETER' => \Zend_Measure_Length::CENTIMETER,
+ 'L' => Weight::POUND,
+ 'LB' => Weight::POUND,
+ 'POUND' => Weight::POUND,
+ 'K' => Weight::KILOGRAM,
+ 'KG' => Weight::KILOGRAM,
+ 'KILOGRAM' => Weight::KILOGRAM,
+ 'I' => Length::INCH,
+ 'IN' => Length::INCH,
+ 'INCH' => Length::INCH,
+ 'C' => Length::CENTIMETER,
+ 'CM' => Length::CENTIMETER,
+ 'CENTIMETER' => Length::CENTIMETER,
],
];
@@ -719,7 +724,7 @@ public function getDhlProductTitle($code)
protected function _getWeight($weight, $maxWeight = false, $configWeightUnit = false)
{
if ($maxWeight) {
- $configWeightUnit = \Zend_Measure_Weight::KILOGRAM;
+ $configWeightUnit = Weight::KILOGRAM;
} elseif ($configWeightUnit) {
$configWeightUnit = $this->getCode('dimensions_variables', $configWeightUnit);
} else {
@@ -924,10 +929,10 @@ protected function _getDimension($dimension, $configWeightUnit = false)
$configWeightUnit = $this->getCode('dimensions_variables', $configWeightUnit);
}
- if ($configWeightUnit == \Zend_Measure_Weight::POUND) {
- $configDimensionUnit = \Zend_Measure_Length::INCH;
+ if ($configWeightUnit == Weight::POUND) {
+ $configDimensionUnit = Length::INCH;
} else {
- $configDimensionUnit = \Zend_Measure_Length::CENTIMETER;
+ $configDimensionUnit = Length::CENTIMETER;
}
$countryDimensionUnit = $this->getCode('dimensions_variables', $this->_getDimensionUnit());
@@ -1096,12 +1101,14 @@ function () use ($deferredResponses, $responseBodies) {
*/
protected function _getQuotesFromServer($request)
{
+ /** @var LaminasClient $client */
$client = $this->_httpClientFactory->create();
$client->setUri($this->getGatewayURL());
- $client->setConfig(['maxredirects' => 0, 'timeout' => 30]);
- $client->setRawData(utf8_encode($request));
+ $client->setOptions(['maxredirects' => 0, 'timeout' => 30]);
+ $client->setRawBody(utf8_encode($request));
+ $client->setMethod(HttpRequest::METHOD_POST);
- return $client->request(\Zend_Http_Client::POST)->getBody();
+ return $client->send()->getBody();
}
/**
@@ -1403,7 +1410,9 @@ protected function _doShipmentRequest(\Magento\Framework\DataObject $request)
*
* @param \Magento\Framework\DataObject $request
* @return $this|\Magento\Framework\DataObject|boolean
+ * phpcs:disable Magento2.Annotation.MethodAnnotationStructure
* @deprecated 100.2.3
+ * @see use processAdditionalValidation method instead
*/
public function proccessAdditionalValidation(\Magento\Framework\DataObject $request)
{
@@ -1532,7 +1541,7 @@ protected function _doRequest()
' xmlns:req="http://www.dhl.com"' .
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' .
' xsi:schemaLocation="http://www.dhl.com ship-val-global-req.xsd"' .
- ' schemaVersion="6.2" />';
+ ' schemaVersion="10.0" />';
$xml = $this->_xmlElFactory->create(['data' => $xmlStr]);
$nodeRequest = $xml->addChild('Request', '', '');
@@ -1561,13 +1570,11 @@ protected function _doRequest()
$xml->addChild('RegionCode', $originRegion, '');
}
$xml->addChild('RequestedPickupTime', 'N', '');
- $xml->addChild('NewShipper', 'N', '');
$xml->addChild('LanguageCode', 'EN', '');
- $xml->addChild('PiecesEnabled', 'Y', '');
/** Billing */
$nodeBilling = $xml->addChild('Billing', '', '');
- $nodeBilling->addChild('ShipperAccountNumber', (string)$this->getConfigData('account'));
+ $nodeBilling->addChild('ShipperAccountNumber', (string)substr($this->getConfigData('account'), 0, 9));
/**
* Method of Payment:
* S (Shipper)
@@ -1579,9 +1586,13 @@ protected function _doRequest()
/**
* Shipment bill to account – required if Shipping PaymentType is other than 'S'
*/
- $nodeBilling->addChild('BillingAccountNumber', (string)$this->getConfigData('account'));
- $nodeBilling->addChild('DutyPaymentType', 'S');
- $nodeBilling->addChild('DutyAccountNumber', (string)$this->getConfigData('account'));
+ $nodeBilling->addChild('BillingAccountNumber', (string)substr($this->getConfigData('account'), 0, 9));
+ if ($this->isDutiable(
+ $rawRequest->getShipperAddressCountryCode(),
+ $rawRequest->getRecipientAddressCountryCode()
+ )) {
+ $nodeBilling->addChild('DutyAccountNumber', (string)substr($this->getConfigData('account'), 0, 9));
+ }
/** Receiver */
$nodeConsignee = $xml->addChild('Consignee', '', '');
@@ -1595,11 +1606,16 @@ protected function _doRequest()
$address = $rawRequest->getRecipientAddressStreet1() . ' ' . $rawRequest->getRecipientAddressStreet2();
$address = $this->string->split($address, 45, false, true);
if (is_array($address)) {
+ $addressLineNumber = 1;
foreach ($address as $addressLine) {
- $nodeConsignee->addChild('AddressLine', $addressLine);
+ if ($addressLineNumber > 3) {
+ break;
+ }
+ $nodeConsignee->addChild('AddressLine'.$addressLineNumber, $addressLine);
+ $addressLineNumber++;
}
} else {
- $nodeConsignee->addChild('AddressLine', $address);
+ $nodeConsignee->addChild('AddressLine1', $address);
}
$nodeConsignee->addChild('City', $rawRequest->getRecipientAddressCity());
@@ -1627,7 +1643,7 @@ protected function _doRequest()
* value should lie in between 1 to 9999.This field is mandatory.
*/
$nodeCommodity = $xml->addChild('Commodity', '', '');
- $nodeCommodity->addChild('CommodityCode', '1');
+ $nodeCommodity->addChild('CommodityCode', substr('01', 0, 18));
/** Dutiable */
if ($this->isDutiable(
@@ -1641,6 +1657,7 @@ protected function _doRequest()
);
$baseCurrencyCode = $this->_storeManager->getWebsite($rawRequest->getWebsiteId())->getBaseCurrencyCode();
$nodeDutiable->addChild('DeclaredCurrency', $baseCurrencyCode);
+ $nodeDutiable->addChild('TermsOfTrade', 'DAP');
}
/**
@@ -1657,18 +1674,23 @@ protected function _doRequest()
/** Shipper */
$nodeShipper = $xml->addChild('Shipper', '', '');
- $nodeShipper->addChild('ShipperID', (string)$this->getConfigData('account'));
+ $nodeShipper->addChild('ShipperID', (string)substr($this->getConfigData('account'), 0, 9));
$nodeShipper->addChild('CompanyName', $rawRequest->getShipperContactCompanyName());
- $nodeShipper->addChild('RegisteredAccount', (string)$this->getConfigData('account'));
+ $nodeShipper->addChild('RegisteredAccount', (string)substr($this->getConfigData('account'), 0, 9));
$address = $rawRequest->getShipperAddressStreet1() . ' ' . $rawRequest->getShipperAddressStreet2();
$address = $this->string->split($address, 45, false, true);
if (is_array($address)) {
+ $addressLineNumber = 1;
foreach ($address as $addressLine) {
- $nodeShipper->addChild('AddressLine', $addressLine);
+ if ($addressLineNumber > 3) {
+ break;
+ }
+ $nodeShipper->addChild('AddressLine'.$addressLineNumber, $addressLine);
+ $addressLineNumber++;
}
} else {
- $nodeShipper->addChild('AddressLine', $address);
+ $nodeShipper->addChild('AddressLine1', $address);
}
$nodeShipper->addChild('City', $rawRequest->getShipperAddressCity());
@@ -1736,7 +1758,6 @@ protected function _doRequest()
protected function _shipmentDetails($xml, $rawRequest, $originRegion = '')
{
$nodeShipmentDetails = $xml->addChild('ShipmentDetails', '', '');
- $nodeShipmentDetails->addChild('NumberOfPieces', count($rawRequest->getPackages()));
$nodePieces = $nodeShipmentDetails->addChild('Pieces', '', '');
@@ -1770,7 +1791,6 @@ protected function _shipmentDetails($xml, $rawRequest, $originRegion = '')
$nodePiece->addChild('PieceContents', $this->string->substr(implode(',', $content), 0, 34));
}
- $nodeShipmentDetails->addChild('Weight', sprintf('%.3f', $rawRequest->getPackageWeight()));
$nodeShipmentDetails->addChild('WeightUnit', substr($this->_getWeightUnit(), 0, 1));
$nodeShipmentDetails->addChild('GlobalProductCode', $rawRequest->getShippingMethod());
$nodeShipmentDetails->addChild('LocalProductCode', $rawRequest->getShippingMethod());
@@ -1779,17 +1799,15 @@ protected function _shipmentDetails($xml, $rawRequest, $originRegion = '')
$this->_coreDate->date('Y-m-d', strtotime('now + 1day'))
);
$nodeShipmentDetails->addChild('Contents', 'DHL Parcel');
- /**
- * The DoorTo Element defines the type of delivery service that applies to the shipment.
- * The valid values are DD (Door to Door), DA (Door to Airport) , AA and DC (Door to
- * Door non-compliant)
- */
- $nodeShipmentDetails->addChild('DoorTo', 'DD');
+
$nodeShipmentDetails->addChild('DimensionUnit', substr($this->_getDimensionUnit(), 0, 1));
$contentType = isset($package['params']['container']) ? $package['params']['container'] : '';
$packageType = $contentType === self::DHL_CONTENT_TYPE_NON_DOC ? 'CP' : 'EE';
$nodeShipmentDetails->addChild('PackageType', $packageType);
- if ($this->isDutiable($rawRequest->getOrigCountryId(), $rawRequest->getDestCountryId())) {
+ if ($this->isDutiable(
+ $rawRequest->getShipperAddressCountryCode(),
+ $rawRequest->getRecipientAddressCountryCode()
+ )) {
$nodeShipmentDetails->addChild('IsDutiable', 'Y');
}
$nodeShipmentDetails->addChild(
@@ -2044,9 +2062,8 @@ protected function _checkDomesticStatus($origCountryCode, $destCountryCode)
$origCountry = (string)$this->getCountryParams($origCountryCode)->getData('name');
$destCountry = (string)$this->getCountryParams($destCountryCode)->getData('name');
- $isDomestic = (string)$this->getCountryParams($destCountryCode)->getData('domestic');
- if (($origCountry == $destCountry && $isDomestic)
+ if (($origCountry == $destCountry)
|| (
$this->_carrierHelper->isCountryInEU($origCountryCode)
&& $this->_carrierHelper->isCountryInEU($destCountryCode)
diff --git a/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php b/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php
index 489157b442c8c..c142586966334 100644
--- a/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php
+++ b/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php
@@ -7,6 +7,7 @@
namespace Magento\Dhl\Test\Unit\Model;
+use Laminas\Http\Response;
use Magento\Dhl\Model\Carrier;
use Magento\Dhl\Model\Validator\XmlValidator;
use Magento\Framework\App\Config\ScopeConfigInterface;
@@ -14,8 +15,8 @@
use Magento\Framework\DataObject;
use Magento\Framework\Filesystem\Directory\Read;
use Magento\Framework\Filesystem\Directory\ReadFactory;
-use Magento\Framework\HTTP\ZendClient;
-use Magento\Framework\HTTP\ZendClientFactory;
+use Magento\Framework\HTTP\LaminasClient;
+use Magento\Framework\HTTP\LaminasClientFactory;
use Magento\Framework\Locale\ResolverInterface;
use Magento\Framework\Module\Dir\Reader;
use Magento\Framework\Stdlib\DateTime\DateTime;
@@ -48,7 +49,7 @@ class CarrierTest extends TestCase
private $objectManager;
/**
- * @var \Zend_Http_Response|MockObject
+ * @var Response|MockObject
*/
private $httpResponse;
@@ -73,7 +74,7 @@ class CarrierTest extends TestCase
private $scope;
/**
- * @var ZendClient|MockObject
+ * @var LaminasClient|MockObject
*/
private $httpClient;
@@ -653,16 +654,16 @@ private function getCarrierHelper(): CarrierHelper
*/
private function getHttpClientFactory(): MockObject
{
- $this->httpResponse = $this->getMockBuilder(\Zend_Http_Response::class)
+ $this->httpResponse = $this->getMockBuilder(Response::class)
->disableOriginalConstructor()
->getMock();
- $this->httpClient = $this->getMockBuilder(ZendClient::class)
+ $this->httpClient = $this->getMockBuilder(LaminasClient::class)
->disableOriginalConstructor()
- ->setMethods(['request'])
+ ->setMethods(['send'])
->getMock();
- $this->httpClient->method('request')
+ $this->httpClient->method('send')
->willReturn($this->httpResponse);
- $httpClientFactory = $this->getMockBuilder(ZendClientFactory::class)
+ $httpClientFactory = $this->getMockBuilder(LaminasClientFactory::class)
->disableOriginalConstructor()
->getMock();
$httpClientFactory->method('create')
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.php b/app/code/Magento/Directory/Model/Currency.php
index 71b9d366f55dc..a7637b83fa3f0 100644
--- a/app/code/Magento/Directory/Model/Currency.php
+++ b/app/code/Magento/Directory/Model/Currency.php
@@ -6,14 +6,14 @@
namespace Magento\Directory\Model;
+use Magento\Directory\Model\Currency\Filter;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\InputException;
-use Magento\Directory\Model\Currency\Filter;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Locale\Currency as LocaleCurrency;
use Magento\Framework\Locale\ResolverInterface as LocalResolverInterface;
use Magento\Framework\NumberFormatterFactory;
use Magento\Framework\Serialize\Serializer\Json;
-use Magento\Framework\Exception\LocalizedException;
/**
* Currency model
@@ -432,11 +432,6 @@ private function formatCurrency(string $price, array $options): string
$this->getCode() ?? $this->numberFormatter->getTextAttribute(\NumberFormatter::CURRENCY_CODE)
);
- if (array_key_exists(LocaleCurrency::CURRENCY_OPTION_SYMBOL, $options)) {
- // remove only one non-breaking space from custom currency symbol to allow custom NBSP in currency symbol
- $formattedCurrency = preg_replace('/ /u', '', $formattedCurrency, 1);
- }
-
if ((array_key_exists(LocaleCurrency::CURRENCY_OPTION_DISPLAY, $options)
&& $options[LocaleCurrency::CURRENCY_OPTION_DISPLAY] === \Magento\Framework\Currency::NO_SYMBOL)) {
$formattedCurrency = str_replace(' ', '', $formattedCurrency);
@@ -591,7 +586,7 @@ public function saveRates($rates)
private function trimUnicodeDirectionMark($string)
{
if (preg_match('/^(\x{200E}|\x{200F})/u', $string, $match)) {
- $string = preg_replace('/^'.$match[1].'/u', '', $string);
+ $string = preg_replace('/^' . $match[1] . '/u', '', $string);
}
return $string;
}
diff --git a/app/code/Magento/Directory/Model/Currency/Filter.php b/app/code/Magento/Directory/Model/Currency/Filter.php
index 5fb876f999623..4e44587a15e18 100644
--- a/app/code/Magento/Directory/Model/Currency/Filter.php
+++ b/app/code/Magento/Directory/Model/Currency/Filter.php
@@ -9,9 +9,10 @@
*/
namespace Magento\Directory\Model\Currency;
+use Laminas\Filter\FilterInterface;
use Magento\Framework\Pricing\PriceCurrencyInterface;
-class Filter implements \Zend_Filter_Interface
+class Filter implements FilterInterface
{
/**
* Rate value
@@ -43,8 +44,6 @@ class Filter implements \Zend_Filter_Interface
protected $_localeCurrency;
/**
- * Price currency
- *
* @var PriceCurrencyInterface
*/
protected $priceCurrency;
diff --git a/app/code/Magento/Directory/Model/Currency/Import/CurrencyConverterApi.php b/app/code/Magento/Directory/Model/Currency/Import/CurrencyConverterApi.php
index 8e27b9905ed6f..5b27259197aba 100644
--- a/app/code/Magento/Directory/Model/Currency/Import/CurrencyConverterApi.php
+++ b/app/code/Magento/Directory/Model/Currency/Import/CurrencyConverterApi.php
@@ -7,11 +7,12 @@
namespace Magento\Directory\Model\Currency\Import;
+use Laminas\Http\Request;
use Magento\Directory\Model\CurrencyFactory;
use Magento\Store\Model\ScopeInterface;
use Magento\Framework\App\Config\ScopeConfigInterface as ScopeConfig;
-use Magento\Framework\HTTP\ZendClient;
-use Magento\Framework\HTTP\ZendClientFactory;
+use Magento\Framework\HTTP\LaminasClient;
+use Magento\Framework\HTTP\LaminasClientFactory;
use Exception;
/**
@@ -26,9 +27,7 @@ class CurrencyConverterApi extends AbstractImport
. '&q={{CURRENCY_RATES}}&compact=ultra';
/**
- * Http Client Factory
- *
- * @var ZendClientFactory
+ * @var LaminasClientFactory
*/
private $httpClientFactory;
@@ -52,12 +51,12 @@ class CurrencyConverterApi extends AbstractImport
/**
* @param CurrencyFactory $currencyFactory
* @param ScopeConfig $scopeConfig
- * @param ZendClientFactory $httpClientFactory
+ * @param LaminasClientFactory $httpClientFactory
*/
public function __construct(
CurrencyFactory $currencyFactory,
ScopeConfig $scopeConfig,
- ZendClientFactory $httpClientFactory
+ LaminasClientFactory $httpClientFactory
) {
parent::__construct($currencyFactory);
$this->scopeConfig = $scopeConfig;
@@ -188,23 +187,22 @@ private function getServiceURL(string $currencyFrom, array $currenciesTo): strin
*/
private function getServiceResponse($url, $retry = 0): array
{
- /** @var ZendClient $httpClient */
+ /** @var LaminasClient $httpClient */
$httpClient = $this->httpClientFactory->create();
$response = [];
try {
- $jsonResponse = $httpClient->setUri(
- $url
- )->setConfig(
+ $httpClient->setUri($url);
+ $httpClient->setOptions(
[
'timeout' => $this->scopeConfig->getValue(
'currency/currencyconverterapi/timeout',
ScopeInterface::SCOPE_STORE
),
]
- )->request(
- 'GET'
- )->getBody();
+ );
+ $httpClient->setMethod(Request::METHOD_GET);
+ $jsonResponse = $httpClient->send()->getBody();
$response = json_decode($jsonResponse, true) ?: [];
} catch (Exception $e) {
diff --git a/app/code/Magento/Directory/Model/Currency/Import/FixerIo.php b/app/code/Magento/Directory/Model/Currency/Import/FixerIo.php
index 29cdbabb9b993..38d92f2f6632b 100644
--- a/app/code/Magento/Directory/Model/Currency/Import/FixerIo.php
+++ b/app/code/Magento/Directory/Model/Currency/Import/FixerIo.php
@@ -7,7 +7,13 @@
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 (From http://fixer.io/)
@@ -17,20 +23,18 @@ class FixerIo extends AbstractImport
/**
* @var string
*/
- const CURRENCY_CONVERTER_URL = 'http://data.fixer.io/api/latest?access_key={{ACCESS_KEY}}'
+ public const CURRENCY_CONVERTER_URL = 'http://data.fixer.io/api/latest?access_key={{ACCESS_KEY}}'
. '&base={{CURRENCY_FROM}}&symbols={{CURRENCY_TO}}';
/**
- * Http Client Factory
- *
- * @var \Magento\Framework\HTTP\ZendClientFactory
+ * @var HttpClientFactory
*/
protected $httpClientFactory;
/**
* Core scope config
*
- * @var \Magento\Framework\App\Config\ScopeConfigInterface
+ * @var ScopeConfigInterface
*/
private $scopeConfig;
@@ -42,14 +46,14 @@ class FixerIo extends AbstractImport
/**
* Initialize dependencies
*
- * @param \Magento\Directory\Model\CurrencyFactory $currencyFactory
- * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
- * @param \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory
+ * @param CurrencyFactory $currencyFactory
+ * @param ScopeConfigInterface $scopeConfig
+ * @param HttpClientFactory $httpClientFactory
*/
public function __construct(
- \Magento\Directory\Model\CurrencyFactory $currencyFactory,
- \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
- \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory
+ CurrencyFactory $currencyFactory,
+ ScopeConfigInterface $scopeConfig,
+ HttpClientFactory $httpClientFactory
) {
parent::__construct($currencyFactory);
$this->scopeConfig = $scopeConfig;
@@ -146,25 +150,25 @@ private function convertBatch(array $data, string $currencyFrom, array $currenci
*/
private function getServiceResponse(string $url, int $retry = 0): array
{
- /** @var \Magento\Framework\HTTP\ZendClient $httpClient */
+ /** @var LaminasClient $httpClient */
$httpClient = $this->httpClientFactory->create();
$response = [];
try {
- $jsonResponse = $httpClient->setUri($url)
- ->setConfig(
- [
- 'timeout' => $this->scopeConfig->getValue(
- 'currency/fixerio/timeout',
- ScopeInterface::SCOPE_STORE
- ),
- ]
- )
- ->request('GET')
- ->getBody();
+ $httpClient->setUri($url);
+ $httpClient->setOptions(
+ [
+ 'timeout' => $this->scopeConfig->getValue(
+ 'currency/fixerio/timeout',
+ ScopeInterface::SCOPE_STORE
+ ),
+ ]
+ );
+ $httpClient->setMethod(Request::METHOD_GET);
+ $jsonResponse = $httpClient->send()->getBody();
$response = json_decode($jsonResponse, true);
- } catch (\Exception $e) {
+ } catch (Exception $e) {
if ($retry == 0) {
$response = $this->getServiceResponse($url, 1);
}
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 @@
+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/Setup/Patch/Data/AddDataForCzechia.php b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForCzechia.php
new file mode 100644
index 0000000000000..68b189d882ef4
--- /dev/null
+++ b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForCzechia.php
@@ -0,0 +1,99 @@
+moduleDataSetup = $moduleDataSetup;
+ $this->dataInstallerFactory = $dataInstallerFactory;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply()
+ {
+ /** @var DataInstaller $dataInstaller */
+ $dataInstaller = $this->dataInstallerFactory->create();
+ $dataInstaller->addCountryRegions(
+ $this->moduleDataSetup->getConnection(),
+ $this->getDataForCzechia()
+ );
+
+ return $this;
+ }
+
+ /**
+ * Czechia states data.
+ *
+ * @return array
+ */
+ private function getDataForCzechia()
+ {
+ return [
+ ['CZ', 'CZ-10', 'Praha, Hlavní město'],
+ ['CZ', 'CZ-20', 'Středočeský kraj'],
+ ['CZ', 'CZ-31', 'Jihočeský kraj'],
+ ['CZ', 'CZ-32', 'Plzeňský kraj'],
+ ['CZ', 'CZ-41', 'Karlovarský kraj'],
+ ['CZ', 'CZ-42', 'Ústecký kraj'],
+ ['CZ', 'CZ-51', 'Liberecký kraj'],
+ ['CZ', 'CZ-52', 'Královéhradecký kraj'],
+ ['CZ', 'CZ-53', 'Pardubický kraj'],
+ ['CZ', 'CZ-63', 'Kraj Vysočina'],
+ ['CZ', 'CZ-64', 'Jihomoravský kraj'],
+ ['CZ', 'CZ-71', 'Olomoucký kraj'],
+ ['CZ', 'CZ-72', 'Zlínský kraj'],
+ ['CZ', 'CZ-80', 'Moravskoslezský kraj'],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function getDependencies()
+ {
+ return [
+ InitializeDirectoryData::class,
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAliases()
+ {
+ return [];
+ }
+}
diff --git a/app/code/Magento/Directory/Test/Mftf/Test/CustomCurrencySymbolWithSpaceTest.xml b/app/code/Magento/Directory/Test/Mftf/Test/CustomCurrencySymbolWithSpaceTest.xml
new file mode 100644
index 0000000000000..62cf00e7735b6
--- /dev/null
+++ b/app/code/Magento/Directory/Test/Mftf/Test/CustomCurrencySymbolWithSpaceTest.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $grabStartQuantity
+ IDRx 0.00
+
+
+
+
+
+
+
+
+
+
+
+ grabProductPrice
+ IDRx {{ApiSimpleProduct.price}}
+
+
+
diff --git a/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/CurrencyConverterApiTest.php b/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/CurrencyConverterApiTest.php
index 9f1094e4cfff1..e973e21eb5d0c 100644
--- a/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/CurrencyConverterApiTest.php
+++ b/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/CurrencyConverterApiTest.php
@@ -12,8 +12,8 @@
use Magento\Directory\Model\CurrencyFactory;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\DataObject;
-use Magento\Framework\HTTP\ZendClient;
-use Magento\Framework\HTTP\ZendClientFactory;
+use Magento\Framework\HTTP\LaminasClient;
+use Magento\Framework\HTTP\LaminasClientFactory;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -34,7 +34,7 @@ class CurrencyConverterApiTest extends TestCase
private $currencyFactory;
/**
- * @var ZendClientFactory|MockObject
+ * @var LaminasClientFactory|MockObject
*/
private $httpClientFactory;
@@ -52,7 +52,7 @@ protected function setUp(): void
->disableOriginalConstructor()
->setMethods(['create'])
->getMock();
- $this->httpClientFactory = $this->getMockBuilder(ZendClientFactory::class)
+ $this->httpClientFactory = $this->getMockBuilder(LaminasClientFactory::class)
->disableOriginalConstructor()
->setMethods(['create'])
->getMock();
@@ -106,8 +106,8 @@ private function prepareFetchRatesTest(string $responseBody): void
)
->willReturnOnConsecutiveCalls('api_key', 100);
- /** @var ZendClient|MockObject $httpClient */
- $httpClient = $this->getMockBuilder(ZendClient::class)
+ /** @var LaminasClient|MockObject $httpClient */
+ $httpClient = $this->getMockBuilder(LaminasClient::class)
->disableOriginalConstructor()
->getMock();
/** @var DataObject|MockObject $currencyMock */
@@ -118,8 +118,9 @@ private function prepareFetchRatesTest(string $responseBody): void
$this->httpClientFactory->expects($this->once())->method('create')->willReturn($httpClient);
$httpClient->expects($this->once())->method('setUri')->willReturnSelf();
- $httpClient->expects($this->once())->method('setConfig')->willReturnSelf();
- $httpClient->expects($this->once())->method('request')->willReturn($httpResponse);
+ $httpClient->expects($this->once())->method('setOptions')->willReturnSelf();
+ $httpClient->expects($this->once())->method('setMethod')->willReturnSelf();
+ $httpClient->expects($this->once())->method('send')->willReturn($httpResponse);
$httpResponse->expects($this->once())->method('getBody')->willReturn($responseBody);
}
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 @@
+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/Test/Unit/Model/Currency/Import/FixerIoTest.php b/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/FixerIoTest.php
index 466391ffca3c3..e99bfce3a3b49 100644
--- a/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/FixerIoTest.php
+++ b/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/FixerIoTest.php
@@ -12,8 +12,8 @@
use Magento\Directory\Model\CurrencyFactory;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\DataObject;
-use Magento\Framework\HTTP\ZendClient;
-use Magento\Framework\HTTP\ZendClientFactory;
+use Magento\Framework\HTTP\LaminasClient;
+use Magento\Framework\HTTP\LaminasClientFactory;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -30,7 +30,7 @@ class FixerIoTest extends TestCase
private $currencyFactory;
/**
- * @var ZendClientFactory|MockObject
+ * @var LaminasClientFactory|MockObject
*/
private $httpClientFactory;
@@ -48,7 +48,7 @@ protected function setUp(): void
->disableOriginalConstructor()
->setMethods(['create'])
->getMock();
- $this->httpClientFactory = $this->getMockBuilder(ZendClientFactory::class)
+ $this->httpClientFactory = $this->getMockBuilder(LaminasClientFactory::class)
->disableOriginalConstructor()
->setMethods(['create'])
->getMock();
@@ -85,8 +85,8 @@ public function testFetchRates(): void
$currency = $this->getMockBuilder(Currency::class)
->disableOriginalConstructor()
->getMock();
- /** @var ZendClient|MockObject $httpClient */
- $httpClient = $this->getMockBuilder(ZendClient::class)
+ /** @var LaminasClient|MockObject $httpClient */
+ $httpClient = $this->getMockBuilder(LaminasClient::class)
->disableOriginalConstructor()
->getMock();
/** @var DataObject|MockObject $currencyMock */
@@ -106,9 +106,11 @@ public function testFetchRates(): void
->willReturn($httpClient);
$httpClient->method('setUri')
->willReturnSelf();
- $httpClient->method('setConfig')
+ $httpClient->method('setOptions')
->willReturnSelf();
- $httpClient->method('request')
+ $httpClient->method('setMethod')
+ ->willReturnSelf();
+ $httpClient->method('send')
->willReturn($httpResponse);
$httpResponse->method('getBody')
->willReturn($responseBody);
diff --git a/app/code/Magento/Directory/Test/Unit/Model/CurrencyTest.php b/app/code/Magento/Directory/Test/Unit/Model/CurrencyTest.php
index cd8405d414d78..a3a6b6826366f 100644
--- a/app/code/Magento/Directory/Test/Unit/Model/CurrencyTest.php
+++ b/app/code/Magento/Directory/Test/Unit/Model/CurrencyTest.php
@@ -9,6 +9,8 @@
use Magento\Directory\Model\Currency as CurrencyModel;
use Magento\Framework\Currency;
+use Magento\Framework\Currency\Data\Currency as CurrencyData;
+use Magento\Framework\Currency\Exception\CurrencyException;
use Magento\Framework\Locale\CurrencyInterface;
use Magento\Framework\Locale\ResolverInterface as LocalResolverInterface;
use Magento\Framework\NumberFormatterFactory;
@@ -203,36 +205,36 @@ public function getFormatTxtNumberFormatterDataProvider(): array
['en_US', 'USD', '9999', [], '$9,999.00'],
['en_US', 'EUR', '9999', [], '€9,999.00'],
['en_US', 'LBP', '9999', [], "LBP\u{00A0}9,999"],
- ['ar_AE', 'USD', '9', [], "\u{0669}\u{066B}\u{0660}\u{0660}\u{00A0}US$"],
- ['ar_AE', 'AED', '9', [], "\u{0669}\u{066B}\u{0660}\u{0660}\u{00A0}\u{062F}.\u{0625}.\u{200F}"],
+ ['ar_SA', 'USD', '9', [], "\u{0669}\u{066B}\u{0660}\u{0660}\u{00A0}US$"],
+ ['ar_SA', 'AED', '9', [], "\u{0669}\u{066B}\u{0660}\u{0660}\u{00A0}\u{062F}.\u{0625}.\u{200F}"],
['de_DE', 'USD', '9999', [], "9.999,00\u{00A0}$"],
['de_DE', 'EUR', '9999', [], "9.999,00\u{00A0}€"],
- ['en_US', 'USD', '9999', ['display' => Currency::NO_SYMBOL, 'precision' => 2], '9,999.00'],
- ['en_US', 'USD', '9999', ['display' => Currency::NO_SYMBOL], '9,999.00'],
- ['en_US', 'PLN', '9999', ['display' => Currency::NO_SYMBOL], '9,999.00'],
- ['en_US', 'LBP', '9999', ['display' => Currency::NO_SYMBOL], '9,999'],
+ ['en_US', 'USD', '9999', ['display' => CurrencyData::NO_SYMBOL, 'precision' => 2], '9,999.00'],
+ ['en_US', 'USD', '9999', ['display' => CurrencyData::NO_SYMBOL], '9,999.00'],
+ ['en_US', 'PLN', '9999', ['display' => CurrencyData::NO_SYMBOL], '9,999.00'],
+ ['en_US', 'LBP', '9999', ['display' => CurrencyData::NO_SYMBOL], '9,999'],
[
- 'ar_AE',
+ 'ar_SA',
'USD',
'9999',
- ['display' => Currency::NO_SYMBOL],
+ ['display' => CurrencyData::NO_SYMBOL],
"\u{0669}\u{066C}\u{0669}\u{0669}\u{0669}\u{066B}\u{0660}\u{0660}"
],
[
- 'ar_AE',
+ 'ar_SA',
'AED',
'9999',
- ['display' => Currency::NO_SYMBOL],
+ ['display' => CurrencyData::NO_SYMBOL],
"\u{0669}\u{066C}\u{0669}\u{0669}\u{0669}\u{066B}\u{0660}\u{0660}"
],
- ['en_US', 'USD', ' 9999', ['display' => Currency::NO_SYMBOL], '9,999.00'],
+ ['en_US', 'USD', ' 9999', ['display' => CurrencyData::NO_SYMBOL], '9,999.00'],
['en_US', 'USD', '9999', ['precision' => 1], '$9,999.0'],
- ['en_US', 'USD', '9999', ['precision' => 2, 'symbol' => '#'], '#9,999.00'],
+ ['en_US', 'USD', '9999', ['precision' => 2, 'symbol' => '#'], '# 9,999.00'],
[
'en_US',
'USD',
'9999.99',
- ['precision' => 2, 'symbol' => '#', 'display' => Currency::NO_SYMBOL],
+ ['precision' => 2, 'symbol' => '#', 'display' => CurrencyData::NO_SYMBOL],
'9,999.99'
],
];
@@ -243,7 +245,7 @@ public function getFormatTxtNumberFormatterDataProvider(): array
* @param string $price
* @param array $options
* @param string $expected
- * @throws \Zend_Currency_Exception
+ * @throws CurrencyException
*/
public function testFormatTxtWithZendCurrency(string $price, array $options, string $expected): void
{
@@ -251,7 +253,7 @@ public function testFormatTxtWithZendCurrency(string $price, array $options, str
->expects(self::once())
->method('getCurrency')
->with($this->currencyCode)
- ->willReturn(new \Zend_Currency($options, 'en_US'));
+ ->willReturn(new CurrencyData($options, 'en_US'));
$this->serializer->method('serialize')->willReturnMap(
[
[[], '[]']
@@ -273,7 +275,7 @@ public function getFormatTxtZendCurrencyDataProvider(): array
['9999', ['display' => Currency::USE_SHORTNAME, 'foo' => 'bar'], 'USD9,999.00'],
['9999', ['currency' => 'USD'], '$9,999.00'],
['9999', ['currency' => 'CNY'], 'CN¥9,999.00'],
- ['9999', ['locale' => 'fr_FR'], "9\u{00A0}999,00\u{00A0}$"]
+ ['9999', ['locale' => 'fr_FR'], "9\u{202F}999,00\u{00A0}$"]
];
}
}
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 @@
- Fixer.io
+ Fixer.io (legacy)
API Key
currency/fixerio/api_key
Magento\Config\Model\Config\Backend\Encrypted
+ 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.
+
+
+ Connection Timeout in Seconds
+ validate-zero-or-greater validate-number
+
+
+
+ Fixer Api (APILayer)
+
+ API Key
+ currency/fixerio_apilayer/api_key
+ Magento\Config\Model\Config\Backend\Encrypted
Connection Timeout in Seconds
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 @@
100
+
+ 100
+
+
100
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 @@
-
-
- Fixer.io
+ - Fixer.io (legacy)
- Magento\Directory\Model\Currency\Import\FixerIo
+ -
+
- Fixer Api (APILayer)
+ - Magento\Directory\Model\Currency\Import\FixerIoApiLayer
+
-
- Currency Converter API
- Magento\Directory\Model\Currency\Import\CurrencyConverterApi
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/Console/Command/DomainsAddCommand.php b/app/code/Magento/Downloadable/Console/Command/DomainsAddCommand.php
index 76d9a13f70f1f..475e8ebfbd763 100644
--- a/app/code/Magento/Downloadable/Console/Command/DomainsAddCommand.php
+++ b/app/code/Magento/Downloadable/Console/Command/DomainsAddCommand.php
@@ -6,11 +6,13 @@
*/
namespace Magento\Downloadable\Console\Command;
+use Exception;
+use Magento\Downloadable\Api\DomainManagerInterface as DomainManager;
+use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
-use Magento\Downloadable\Api\DomainManagerInterface as DomainManager;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
/**
* Class DomainsAddCommand
@@ -80,12 +82,14 @@ protected function execute(InputInterface $input, OutputInterface $output)
);
}
}
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$output->writeln('' . $e->getMessage() . ' ');
if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
$output->writeln($e->getTraceAsString());
}
- return;
+ return Cli::RETURN_FAILURE;
}
+
+ return Cli::RETURN_SUCCESS;
}
}
diff --git a/app/code/Magento/Downloadable/Console/Command/DomainsRemoveCommand.php b/app/code/Magento/Downloadable/Console/Command/DomainsRemoveCommand.php
index a30e99a24859c..d3acb8e4ae75a 100644
--- a/app/code/Magento/Downloadable/Console/Command/DomainsRemoveCommand.php
+++ b/app/code/Magento/Downloadable/Console/Command/DomainsRemoveCommand.php
@@ -6,11 +6,13 @@
*/
namespace Magento\Downloadable\Console\Command;
+use Exception;
+use Magento\Downloadable\Api\DomainManagerInterface as DomainManager;
+use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
-use Magento\Downloadable\Api\DomainManagerInterface as DomainManager;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
/**
* Class DomainsRemoveCommand
@@ -80,12 +82,14 @@ protected function execute(InputInterface $input, OutputInterface $output)
);
}
}
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$output->writeln('' . $e->getMessage() . ' ');
if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
$output->writeln($e->getTraceAsString());
}
- return;
+ return Cli::RETURN_FAILURE;
}
+
+ return Cli::RETURN_SUCCESS;
}
}
diff --git a/app/code/Magento/Downloadable/Console/Command/DomainsShowCommand.php b/app/code/Magento/Downloadable/Console/Command/DomainsShowCommand.php
index eb4488353a096..475f56b63275c 100644
--- a/app/code/Magento/Downloadable/Console/Command/DomainsShowCommand.php
+++ b/app/code/Magento/Downloadable/Console/Command/DomainsShowCommand.php
@@ -6,10 +6,12 @@
*/
namespace Magento\Downloadable\Console\Command;
+use Exception;
+use Magento\Downloadable\Api\DomainManagerInterface as DomainManager;
+use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputInterface;
-use Magento\Downloadable\Api\DomainManagerInterface as DomainManager;
+use Symfony\Component\Console\Output\OutputInterface;
/**
* Class DomainsShowCommand
@@ -56,12 +58,14 @@ protected function execute(InputInterface $input, OutputInterface $output)
$output->writeln(
"Downloadable domains whitelist:\n$whitelist"
);
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$output->writeln('' . $e->getMessage() . ' ');
if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
$output->writeln($e->getTraceAsString());
}
- return;
+ return Cli::RETURN_FAILURE;
}
+
+ return Cli::RETURN_SUCCESS;
}
}
diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/EndToEndB2CAdminTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/EndToEndB2CAdminTest.xml
index 66f6586d8347e..910e398a69875 100644
--- a/app/code/Magento/Downloadable/Test/Mftf/Test/EndToEndB2CAdminTest.xml
+++ b/app/code/Magento/Downloadable/Test/Mftf/Test/EndToEndB2CAdminTest.xml
@@ -8,6 +8,9 @@
+
+
+
@@ -56,5 +59,8 @@
+
+
+
diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAdvanceCatalogSearchDownloadableBySkuWithHyphenTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAdvanceCatalogSearchDownloadableBySkuWithHyphenTest.xml
index da33a0ce43af1..8d7398460c7c4 100644
--- a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAdvanceCatalogSearchDownloadableBySkuWithHyphenTest.xml
+++ b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAdvanceCatalogSearchDownloadableBySkuWithHyphenTest.xml
@@ -17,7 +17,7 @@
-
+
@@ -34,4 +34,4 @@
-
\ No newline at end of file
+
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/Model/Adminhtml/System/Config/Source/Inputtype/Validator.php b/app/code/Magento/Eav/Model/Adminhtml/System/Config/Source/Inputtype/Validator.php
index ee8fdd7c3f987..72e40404a2905 100644
--- a/app/code/Magento/Eav/Model/Adminhtml/System/Config/Source/Inputtype/Validator.php
+++ b/app/code/Magento/Eav/Model/Adminhtml/System/Config/Source/Inputtype/Validator.php
@@ -5,16 +5,16 @@
*/
namespace Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype;
+use Laminas\Validator\InArray;
+
/**
* Validator for check input type value
*
* @author Magento Core Team
*/
-class Validator extends \Zend_Validate_InArray
+class Validator extends InArray
{
/**
- * Eav data
- *
* @var \Magento\Eav\Helper\Data
*/
protected $_eavData = null;
@@ -30,7 +30,7 @@ public function __construct(\Magento\Eav\Helper\Data $eavData)
$haystack = $this->_eavData->getInputTypesValidatorData();
//reset message template and set custom
- $this->_messageTemplates = null;
+ $this->messageTemplates = null;
$this->_initMessageTemplates();
//parent construct with options
@@ -44,8 +44,8 @@ public function __construct(\Magento\Eav\Helper\Data $eavData)
*/
protected function _initMessageTemplates()
{
- if (!$this->_messageTemplates) {
- $this->_messageTemplates = [
+ if (!$this->messageTemplates) {
+ $this->messageTemplates = [
self::NOT_IN_ARRAY => __('Input type "%value%" not found in the input types list.'),
];
}
@@ -60,8 +60,8 @@ protected function _initMessageTemplates()
*/
public function addInputType($type)
{
- if (!in_array((string)$type, $this->_haystack, true)) {
- $this->_haystack[] = (string)$type;
+ if (!in_array((string)$type, $this->haystack, true)) {
+ $this->haystack[] = (string)$type;
}
return $this;
}
diff --git a/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php b/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php
index 3bc87ed977517..8ee18a68b9923 100644
--- a/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php
+++ b/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php
@@ -6,13 +6,19 @@
namespace Magento\Eav\Model\Attribute\Data;
+use Laminas\I18n\Validator\Alpha;
+use Laminas\Validator\Date;
+use Laminas\Validator\Digits;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Exception\LocalizedException as CoreException;
+use Magento\Framework\Validator\Alnum;
use Magento\Framework\Validator\EmailAddress;
+use Magento\Framework\Validator\Hostname;
/**
* EAV Attribute Abstract Data Model
*
+ * phpcs:disable Magento2.Classes.AbstractApi
* @api
* @author Magento Core Team
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -64,8 +70,6 @@ abstract class AbstractData
protected $_extractedData = [];
/**
- * Date filter format
- *
* @var string
*/
protected $_dateFilterFormat;
@@ -317,41 +321,42 @@ protected function _validateInputRule($value)
case 'alphanum-with-spaces':
$allowWhiteSpace = true;
// Continue to alphanumeric validation
+ // no break
case 'alphanumeric':
- $validator = new \Zend_Validate_Alnum($allowWhiteSpace);
- $validator->setMessage(__('"%1" invalid type entered.', $label), \Zend_Validate_Alnum::INVALID);
+ $validator = new Alnum($allowWhiteSpace);
+ $validator->setMessage(__('"%1" invalid type entered.', $label), Alnum::INVALID);
$validator->setMessage(
__('"%1" contains non-alphabetic or non-numeric characters.', $label),
- \Zend_Validate_Alnum::NOT_ALNUM
+ Alnum::NOT_ALNUM
);
- $validator->setMessage(__('"%1" is an empty string.', $label), \Zend_Validate_Alnum::STRING_EMPTY);
+ $validator->setMessage(__('"%1" is an empty string.', $label), Alnum::STRING_EMPTY);
if (!$validator->isValid($value)) {
return $validator->getMessages();
}
break;
case 'numeric':
- $validator = new \Zend_Validate_Digits();
- $validator->setMessage(__('"%1" invalid type entered.', $label), \Zend_Validate_Digits::INVALID);
+ $validator = new Digits();
+ $validator->setMessage(__('"%1" invalid type entered.', $label), Digits::INVALID);
$validator->setMessage(
__('"%1" contains non-numeric characters.', $label),
- \Zend_Validate_Digits::NOT_DIGITS
+ Digits::NOT_DIGITS
);
$validator->setMessage(
__('"%1" is an empty string.', $label),
- \Zend_Validate_Digits::STRING_EMPTY
+ Digits::STRING_EMPTY
);
if (!$validator->isValid($value)) {
return $validator->getMessages();
}
break;
case 'alpha':
- $validator = new \Zend_Validate_Alpha(true);
- $validator->setMessage(__('"%1" invalid type entered.', $label), \Zend_Validate_Alpha::INVALID);
+ $validator = new Alpha(true);
+ $validator->setMessage(__('"%1" invalid type entered.', $label), Alpha::INVALID);
$validator->setMessage(
__('"%1" contains non-alphabetic characters.', $label),
- \Zend_Validate_Alpha::NOT_ALPHA
+ Alpha::NOT_ALPHA
);
- $validator->setMessage(__('"%1" is an empty string.', $label), \Zend_Validate_Alpha::STRING_EMPTY);
+ $validator->setMessage(__('"%1" is an empty string.', $label), Alpha::STRING_EMPTY);
if (!$validator->isValid($value)) {
return $validator->getMessages();
}
@@ -374,99 +379,100 @@ protected function _validateInputRule($value)
$validator = new EmailAddress();
$validator->setMessage(
__('"%1" invalid type entered.', $label),
- \Zend_Validate_EmailAddress::INVALID
+ EmailAddress::INVALID
);
$validator->setMessage(
__('"%1" is not a valid email address.', $label),
- \Zend_Validate_EmailAddress::INVALID_FORMAT
+ EmailAddress::INVALID_FORMAT
);
$validator->setMessage(
__('"%1" is not a valid hostname.', $label),
- \Zend_Validate_EmailAddress::INVALID_HOSTNAME
+ EmailAddress::INVALID_HOSTNAME
);
$validator->setMessage(
__('"%1" is not a valid hostname.', $label),
- \Zend_Validate_EmailAddress::INVALID_MX_RECORD
+ EmailAddress::INVALID_MX_RECORD
);
$validator->setMessage(
__('"%1" is not a valid hostname.', $label),
- \Zend_Validate_EmailAddress::INVALID_MX_RECORD
+ EmailAddress::INVALID_MX_RECORD
);
$validator->setMessage(
__('"%1" is not a valid email address.', $label),
- \Zend_Validate_EmailAddress::DOT_ATOM
+ EmailAddress::DOT_ATOM
);
$validator->setMessage(
__('"%1" is not a valid email address.', $label),
- \Zend_Validate_EmailAddress::QUOTED_STRING
+ EmailAddress::QUOTED_STRING
);
$validator->setMessage(
__('"%1" is not a valid email address.', $label),
- \Zend_Validate_EmailAddress::INVALID_LOCAL_PART
+ EmailAddress::INVALID_LOCAL_PART
);
$validator->setMessage(
__('"%1" uses too many characters.', $label),
- \Zend_Validate_EmailAddress::LENGTH_EXCEEDED
+ EmailAddress::LENGTH_EXCEEDED
);
$validator->setMessage(
__("'%value%' looks like an IP address, which is not an acceptable format."),
- \Zend_Validate_Hostname::IP_ADDRESS_NOT_ALLOWED
+ Hostname::IP_ADDRESS_NOT_ALLOWED
);
$validator->setMessage(
__("'%value%' looks like a DNS hostname but contains a dash in an invalid position."),
- \Zend_Validate_Hostname::INVALID_DASH
+ Hostname::INVALID_DASH
);
$validator->setMessage(
__(
"'%value%' looks like a DNS hostname but we cannot match it against the "
. "hostname schema for TLD '%tld%'."
),
- \Zend_Validate_Hostname::INVALID_HOSTNAME_SCHEMA
+ Hostname::INVALID_HOSTNAME_SCHEMA
);
$validator->setMessage(
__("'%value%' looks like a DNS hostname but cannot extract TLD part."),
- \Zend_Validate_Hostname::UNDECIPHERABLE_TLD
+ Hostname::UNDECIPHERABLE_TLD
);
$validator->setMessage(
__("'%value%' does not look like a valid local network name."),
- \Zend_Validate_Hostname::INVALID_LOCAL_NAME
+ Hostname::INVALID_LOCAL_NAME
);
$validator->setMessage(
__("'%value%' looks like a local network name, which is not an acceptable format."),
- \Zend_Validate_Hostname::LOCAL_NAME_NOT_ALLOWED
+ Hostname::LOCAL_NAME_NOT_ALLOWED
);
$validator->setMessage(
__(
"'%value%' appears to be a DNS hostname, but the given punycode notation cannot be decoded."
),
- \Zend_Validate_Hostname::CANNOT_DECODE_PUNYCODE
+ Hostname::CANNOT_DECODE_PUNYCODE
);
if (!$validator->isValid($value)) {
return array_unique($validator->getMessages());
}
break;
case 'url':
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$parsedUrl = parse_url($value);
if ($parsedUrl === false || empty($parsedUrl['scheme']) || empty($parsedUrl['host'])) {
return [__('"%1" is not a valid URL.', $label)];
}
- $validator = new \Zend_Validate_Hostname();
+ $validator = new Hostname();
if (!$validator->isValid($parsedUrl['host'])) {
return [__('"%1" is not a valid URL.', $label)];
}
break;
case 'date':
- $validator = new \Zend_Validate_Date(
+ $validator = new Date(
[
- 'format' => \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT,
+ 'format' => \Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT,
'locale' => $this->_localeResolver->getLocale(),
]
);
- $validator->setMessage(__('"%1" invalid type entered.', $label), \Zend_Validate_Date::INVALID);
- $validator->setMessage(__('"%1" is not a valid date.', $label), \Zend_Validate_Date::INVALID_DATE);
+ $validator->setMessage(__('"%1" invalid type entered.', $label), Date::INVALID);
+ $validator->setMessage(__('"%1" is not a valid date.', $label), Date::INVALID_DATE);
$validator->setMessage(
__('"%1" does not fit the entered date format.', $label),
- \Zend_Validate_Date::FALSEFORMAT
+ Date::FALSEFORMAT
);
if (!$validator->isValid($value)) {
return array_unique($validator->getMessages());
diff --git a/app/code/Magento/Eav/Model/Attribute/Data/Multiselect.php b/app/code/Magento/Eav/Model/Attribute/Data/Multiselect.php
index b54addb236ded..b926ed4d58656 100644
--- a/app/code/Magento/Eav/Model/Attribute/Data/Multiselect.php
+++ b/app/code/Magento/Eav/Model/Attribute/Data/Multiselect.php
@@ -12,7 +12,7 @@
*
* @author Magento Core Team
*/
-class Multiselect extends \Magento\Eav\Model\Attribute\Data\Select
+class Multiselect extends AbstractData
{
/**
* Extract data from request and return value
@@ -40,7 +40,11 @@ public function compactValue($value)
if (is_array($value)) {
$value = implode(',', $value);
}
- return parent::compactValue($value);
+ if ($value !== false) {
+ $this->getEntity()->setData($this->getAttribute()->getAttributeCode(), $value);
+ }
+
+ return $this;
}
/**
@@ -75,4 +79,63 @@ public function outputValue($format = \Magento\Eav\Model\AttributeDataFactory::O
return $output;
}
+
+ /**
+ * @inheritdoc
+ */
+ public function validateValue($value)
+ {
+ $errors = [];
+ $attribute = $this->getAttribute();
+
+ if ($value === false) {
+ // try to load original value and validate it
+ $value = $this->getEntity()->getData($attribute->getAttributeCode());
+ }
+
+ if ($attribute->getIsRequired() && empty($value) && $value != '0') {
+ $label = __($attribute->getStoreLabel());
+ $errors[] = __('"%1" is a required value.', $label);
+ }
+
+ if (!empty($value) && $attribute->getSourceModel()) {
+ $values = is_array($value) ? $value : explode(',', (string) $value);
+ $errors = array_merge(
+ $errors,
+ $this->validateBySource($values)
+ );
+ }
+
+ return empty($errors) ? true : $errors;
+ }
+
+ /**
+ * Validate values using source
+ *
+ * @param array $values
+ * @return array
+ */
+ private function validateBySource(array $values): array
+ {
+ $errors = [];
+ foreach ($values as $value) {
+ if (!$this->getAttribute()->getSource()->getOptionText($value)) {
+ $errors[] = __(
+ 'Attribute %1 does not contain option with Id %2',
+ $this->getAttribute()->getAttributeCode(),
+ $value
+ );
+ }
+ }
+
+ return $errors;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function restoreValue($value)
+ {
+ return $this->compactValue($value);
+ }
}
diff --git a/app/code/Magento/Eav/Model/Attribute/Data/Select.php b/app/code/Magento/Eav/Model/Attribute/Data/Select.php
index a74f4d10a5428..f975f24e97657 100644
--- a/app/code/Magento/Eav/Model/Attribute/Data/Select.php
+++ b/app/code/Magento/Eav/Model/Attribute/Data/Select.php
@@ -27,6 +27,7 @@ public function extractValue(RequestInterface $request)
/**
* Validate data
+ *
* Return true or array of errors
*
* @param array|string $value
@@ -47,8 +48,11 @@ public function validateValue($value)
$errors[] = __('"%1" is a required value.', $label);
}
- if (!$errors && !$attribute->getIsRequired() && empty($value)) {
- return true;
+ if (!empty($value)
+ && $attribute->getSourceModel()
+ && !$attribute->getSource()->getOptionText($value)
+ ) {
+ $errors[] = __('Attribute %1 does not contain option with Id %2', $attribute->getAttributeCode(), $value);
}
if (count($errors) == 0) {
diff --git a/app/code/Magento/Eav/Model/Attribute/Data/Text.php b/app/code/Magento/Eav/Model/Attribute/Data/Text.php
index 22cac884491ae..c41a65a6bfd3e 100644
--- a/app/code/Magento/Eav/Model/Attribute/Data/Text.php
+++ b/app/code/Magento/Eav/Model/Attribute/Data/Text.php
@@ -79,6 +79,9 @@ public function validateValue($value)
return $errors;
}
+ // if string with diacritics encode it.
+ $value = $this->encodeDiacritics($value);
+
$validateLengthResult = $this->validateLength($attribute, $value);
$errors = array_merge($errors, $validateLengthResult);
@@ -173,4 +176,19 @@ private function validateInputRule(string $value): array
$result = $this->_validateInputRule($value);
return \is_array($result) ? $result : [];
}
+
+ /**
+ * Encode strings with diacritics for validate.
+ *
+ * @param array|string $value
+ * @return array|string
+ */
+ private function encodeDiacritics($value): array|string
+ {
+ $encoded = $value;
+ if (is_string($value)) {
+ $encoded = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value);
+ }
+ return $encoded;
+ }
}
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute.php b/app/code/Magento/Eav/Model/Entity/Attribute.php
index e24787bb0056f..e1094a331149e 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute.php
@@ -5,6 +5,7 @@
*/
namespace Magento\Eav\Model\Entity;
+use Magento\Eav\Model\ReservedAttributeCheckerInterface;
use Magento\Eav\Model\Validator\Attribute\Code as AttributeCodeValidator;
use Magento\Framework\Api\AttributeValueFactory;
use Magento\Framework\App\ObjectManager;
@@ -29,17 +30,17 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute\AbstractAttribute im
* The value is defined as 60 because in the flat mode attribute code will be transformed into column name.
* MySQL allows only 64 symbols in column name.
*/
- const ATTRIBUTE_CODE_MAX_LENGTH = 60;
+ public const ATTRIBUTE_CODE_MAX_LENGTH = 60;
/**
* Min accepted length of an attribute code.
*/
- const ATTRIBUTE_CODE_MIN_LENGTH = 1;
+ public const ATTRIBUTE_CODE_MIN_LENGTH = 1;
/**
* Tag to use for attributes caching.
*/
- const CACHE_TAG = 'EAV_ATTRIBUTE';
+ public const CACHE_TAG = 'EAV_ATTRIBUTE';
/**
* Prefix of model events names
@@ -69,6 +70,9 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute\AbstractAttribute im
/**
* @var \Magento\Catalog\Model\Product\ReservedAttributeList
+ *
+ * @deprecated Incorrect direct dependency on Product attribute.
+ * @see \Magento\Eav\Model\Entity\Attribute::$reservedAttributeChecker
*/
protected $reservedAttributeList;
@@ -87,6 +91,11 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute\AbstractAttribute im
*/
private $attributeCodeValidator;
+ /**
+ * @var ReservedAttributeCheckerInterface|null
+ */
+ private $reservedAttributeChecker;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -108,6 +117,7 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute\AbstractAttribute im
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
* @param array $data
* @param AttributeCodeValidator|null $attributeCodeValidator
+ * @param ReservedAttributeCheckerInterface|null $reservedAttributeChecker
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
* @codeCoverageIgnore
*/
@@ -131,7 +141,8 @@ public function __construct(
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
array $data = [],
- AttributeCodeValidator $attributeCodeValidator = null
+ AttributeCodeValidator $attributeCodeValidator = null,
+ ?ReservedAttributeCheckerInterface $reservedAttributeChecker = null
) {
parent::__construct(
$context,
@@ -157,6 +168,9 @@ public function __construct(
$this->attributeCodeValidator = $attributeCodeValidator ?: ObjectManager::getInstance()->get(
AttributeCodeValidator::class
);
+ $this->reservedAttributeChecker = $reservedAttributeChecker ?: ObjectManager::getInstance()->get(
+ ReservedAttributeCheckerInterface::class
+ );
}
/**
@@ -251,7 +265,7 @@ public function beforeSave()
}
// prevent overriding product data
- if (isset($this->_data['attribute_code']) && $this->reservedAttributeList->isReservedAttribute($this)) {
+ if (isset($this->_data['attribute_code']) && $this->reservedAttributeChecker->isReservedAttribute($this)) {
throw new LocalizedException(
__(
'The attribute code \'%1\' is reserved by system. Please try another attribute code',
@@ -535,6 +549,7 @@ public function __wakeup()
$this->_localeDate = $objectManager->get(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class);
$this->_localeResolver = $objectManager->get(\Magento\Framework\Locale\ResolverInterface::class);
$this->reservedAttributeList = $objectManager->get(\Magento\Catalog\Model\Product\ReservedAttributeList::class);
+ $this->reservedAttributeChecker = $objectManager->get(ReservedAttributeCheckerInterface::class);
$this->dateTimeFormatter = $objectManager->get(DateTimeFormatterInterface::class);
}
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Backend/ArrayBackend.php b/app/code/Magento/Eav/Model/Entity/Attribute/Backend/ArrayBackend.php
index 112483094465e..7094503544cee 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute/Backend/ArrayBackend.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute/Backend/ArrayBackend.php
@@ -45,7 +45,9 @@ public function validate($object)
if ($object->hasData($attributeCode)) {
$data = $object->getData($attributeCode);
if (is_array($data)) {
- $object->setData($attributeCode, implode(',', array_filter($data)));
+ $object->setData($attributeCode, $this->prepare($data));
+ } elseif (is_string($data)) {
+ $object->setData($attributeCode, $this->prepare(explode(',', $data)));
} elseif (empty($data)) {
$object->setData($attributeCode, null);
}
@@ -53,4 +55,15 @@ public function validate($object)
return parent::validate($object);
}
+
+ /**
+ * Prepare attribute values
+ *
+ * @param array $data
+ * @return string
+ */
+ private function prepare(array $data): string
+ {
+ return implode(',', array_filter(array_unique($data), 'is_numeric'));
+ }
}
diff --git a/app/code/Magento/Eav/Model/ReservedAttributeChecker.php b/app/code/Magento/Eav/Model/ReservedAttributeChecker.php
index be941ae98842c..06cc4b68b3207 100644
--- a/app/code/Magento/Eav/Model/ReservedAttributeChecker.php
+++ b/app/code/Magento/Eav/Model/ReservedAttributeChecker.php
@@ -18,7 +18,7 @@
class ReservedAttributeChecker implements ReservedAttributeCheckerInterface
{
/**
- * @var ReservedAttributeCheckerInterface[][]
+ * @var ReservedAttributeCheckerInterface[]
*/
private $validators;
@@ -37,7 +37,8 @@ public function __construct(
public function isReservedAttribute(AbstractAttribute $attribute): bool
{
$isReserved = false;
- $validators = $this->validators[$attribute->getEntityType()->getEntityTypeCode()] ?? [];
+ $entityTypeCode = $this->getAttributeEntityTypeCode($attribute);
+ $validators = $this->validators[$entityTypeCode] ?? [];
foreach ($validators as $validator) {
$isReserved = $validator->isReservedAttribute($attribute);
if ($isReserved === true) {
@@ -47,4 +48,21 @@ public function isReservedAttribute(AbstractAttribute $attribute): bool
return $isReserved;
}
+
+ /**
+ * Returns attribute entity type code.
+ *
+ * @param AbstractAttribute $attribute
+ * @return string|null
+ */
+ private function getAttributeEntityTypeCode(AbstractAttribute $attribute): ?string
+ {
+ try {
+ $result = $attribute->getEntityType()->getEntityTypeCode();
+ } catch (LocalizedException $e) {
+ $result = null;
+ }
+
+ return $result;
+ }
}
diff --git a/app/code/Magento/Eav/Model/Validator/Attribute/Code.php b/app/code/Magento/Eav/Model/Validator/Attribute/Code.php
index 8525422bedebd..c9db342027afe 100644
--- a/app/code/Magento/Eav/Model/Validator/Attribute/Code.php
+++ b/app/code/Magento/Eav/Model/Validator/Attribute/Code.php
@@ -10,7 +10,9 @@
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Eav\Model\Entity\Attribute;
use Magento\Framework\Validator\AbstractValidator;
-use Zend_Validate;
+use Magento\Framework\Validator\StringLength;
+use Magento\Framework\Validator\ValidateException;
+use Magento\Framework\Validator\ValidatorChain;
/**
* Class Code
@@ -29,7 +31,7 @@ class Code extends AbstractValidator
*
* @param string $attributeCode
* @return bool
- * @throws \Zend_Validate_Exception
+ * @throws ValidateException
*/
public function isValid($attributeCode): bool
{
@@ -53,9 +55,9 @@ public function isValid($attributeCode): bool
*/
$minLength = Attribute::ATTRIBUTE_CODE_MIN_LENGTH;
$maxLength = Attribute::ATTRIBUTE_CODE_MAX_LENGTH;
- $isAllowedLength = Zend_Validate::is(
+ $isAllowedLength = ValidatorChain::is(
trim($attributeCode),
- 'StringLength',
+ StringLength::class,
['min' => $minLength, 'max' => $maxLength]
);
if (!$isAllowedLength) {
diff --git a/app/code/Magento/Eav/Model/Validator/Attribute/Data.php b/app/code/Magento/Eav/Model/Validator/Attribute/Data.php
index 7e434166a15be..7b29b9dde6790 100644
--- a/app/code/Magento/Eav/Model/Validator/Attribute/Data.php
+++ b/app/code/Magento/Eav/Model/Validator/Attribute/Data.php
@@ -7,6 +7,8 @@
namespace Magento\Eav\Model\Validator\Attribute;
use Magento\Eav\Model\Attribute;
+use Magento\Eav\Model\AttributeDataFactory;
+use Magento\Framework\DataObject;
/**
* EAV attribute data validator
@@ -36,16 +38,25 @@ class Data extends \Magento\Framework\Validator\AbstractValidator
protected $_data = [];
/**
- * @var \Magento\Eav\Model\AttributeDataFactory
+ * @var AttributeDataFactory
*/
protected $_attrDataFactory;
/**
- * @param \Magento\Eav\Model\AttributeDataFactory $attrDataFactory
+ * @var array
*/
- public function __construct(\Magento\Eav\Model\AttributeDataFactory $attrDataFactory)
- {
+ private $ignoredAttributesByTypesList;
+
+ /**
+ * @param AttributeDataFactory $attrDataFactory
+ * @param array $ignoredAttributesByTypesList
+ */
+ public function __construct(
+ AttributeDataFactory $attrDataFactory,
+ array $ignoredAttributesByTypesList = []
+ ) {
$this->_attrDataFactory = $attrDataFactory;
+ $this->ignoredAttributesByTypesList = $ignoredAttributesByTypesList;
}
/**
@@ -111,18 +122,17 @@ public function isValid($entity)
/** @var $attributes Attribute[] */
$attributes = $this->_getAttributes($entity);
- $data = [];
- if ($this->_data) {
- $data = $this->_data;
- } elseif ($entity instanceof \Magento\Framework\DataObject) {
- $data = $entity->getData();
- }
+ $data = $this->retrieveData($entity);
foreach ($attributes as $attribute) {
$attributeCode = $attribute->getAttributeCode();
if (!$attribute->getDataModel() && !$attribute->getFrontendInput()) {
continue;
}
+ if (!isset($data[$attributeCode]) && !$attribute->getIsVisible()) {
+ continue;
+ }
+
$dataModel = $this->_attrDataFactory->create($attribute, $entity);
$dataModel->setExtractedData($data);
if (!isset($data[$attributeCode])) {
@@ -149,6 +159,7 @@ protected function _getAttributes($entity)
{
/** @var \Magento\Eav\Model\Attribute[] $attributes */
$attributes = [];
+ $ignoreAttributes = $this->deniedAttributesList;
if ($this->_attributes) {
$attributes = $this->_attributes;
@@ -158,27 +169,27 @@ protected function _getAttributes($entity)
/** @var \Magento\Eav\Model\Entity\Type $entityType */
$entityType = $entity->getEntityType();
$attributes = $entityType->getAttributeCollection()->getItems();
+
+ $ignoredTypeAttributes = $this->ignoredAttributesByTypesList[$entityType->getEntityTypeCode()] ?? [];
+ if ($ignoredTypeAttributes) {
+ $ignoreAttributes = array_merge($ignoreAttributes, $ignoredTypeAttributes);
+ }
}
$attributesByCode = [];
$attributesCodes = [];
foreach ($attributes as $attribute) {
- if (!$attribute->getIsVisible()) {
- continue;
- }
$attributeCode = $attribute->getAttributeCode();
$attributesByCode[$attributeCode] = $attribute;
$attributesCodes[] = $attributeCode;
}
- $ignoreAttributes = $this->deniedAttributesList;
if ($this->allowedAttributesList) {
$ignoreAttributes = array_merge(
$ignoreAttributes,
array_diff($attributesCodes, $this->allowedAttributesList)
);
}
-
foreach ($ignoreAttributes as $attributeCode) {
unset($attributesByCode[$attributeCode]);
}
@@ -201,4 +212,22 @@ protected function _addErrorMessages($code, array $messages)
$this->_messages[$code] = array_merge($this->_messages[$code], $messages);
}
}
+
+ /**
+ * Retrieve entity data
+ *
+ * @param \Magento\Framework\Model\AbstractModel $entity
+ * @return array
+ */
+ private function retrieveData($entity): array
+ {
+ $data = [];
+ if ($this->_data) {
+ $data = $this->_data;
+ } elseif ($entity instanceof DataObject) {
+ $data = $entity->getData();
+ }
+
+ return $data;
+ }
}
diff --git a/app/code/Magento/Eav/Setup/EavSetup.php b/app/code/Magento/Eav/Setup/EavSetup.php
index 7ed0ec4ca4a48..e4c3b4ab8ad38 100644
--- a/app/code/Magento/Eav/Setup/EavSetup.php
+++ b/app/code/Magento/Eav/Setup/EavSetup.php
@@ -18,6 +18,7 @@
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Setup\ModuleDataSetupInterface;
+use Magento\Framework\Validator\ValidateException;
/**
* Base eav setup class.
@@ -137,6 +138,7 @@ public function __construct(
* Gets setup model.
*
* @deprecated 102.0.0
+ * @see we don't recommend this approach anymore
* @return ModuleDataSetupInterface
*/
public function getSetup()
@@ -821,8 +823,7 @@ private function _getValue($array, $key, $default = null)
* @param string $code
* @param array $attr
* @return $this
- * @throws LocalizedException
- * @throws \Zend_Validate_Exception
+ * @throws LocalizedException|ValidateException
*/
public function addAttribute($entityTypeId, $code, array $attr)
{
@@ -1505,7 +1506,7 @@ private function _insertAttributeAdditionalData($entityTypeId, array $data)
*
* @param array $data
* @throws LocalizedException
- * @throws \Zend_Validate_Exception
+ * @throws ValidateException
*/
private function validateAttributeCode(array $data): void
{
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php b/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php
index 761f257e395f7..5ccc97589118b 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php
@@ -16,6 +16,7 @@
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\Framework\Stdlib\StringUtils;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\Validator\Alnum;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
@@ -141,10 +142,10 @@ public function alphanumDataProvider(): array
['QazWsx', true],
['QazWsx123', true],
['QazWsx 123',
- [\Zend_Validate_Alnum::NOT_ALNUM => '"Test" contains non-alphabetic or non-numeric characters.']
+ [Alnum::NOT_ALNUM => '"Test" contains non-alphabetic or non-numeric characters.']
],
['QazWsx_123',
- [\Zend_Validate_Alnum::NOT_ALNUM => '"Test" contains non-alphabetic or non-numeric characters.']
+ [Alnum::NOT_ALNUM => '"Test" contains non-alphabetic or non-numeric characters.']
],
['QazWsx12345', [
__('"%1" length must be equal or less than %2 characters.', 'Test', 10)]
@@ -190,7 +191,7 @@ public function alphanumWithSpacesDataProvider(): array
['QazWsx123', true],
['QazWsx 123', true],
['QazWsx_123',
- [\Zend_Validate_Alnum::NOT_ALNUM => '"Test" contains non-alphabetic or non-numeric characters.']
+ [Alnum::NOT_ALNUM => '"Test" contains non-alphabetic or non-numeric characters.']
],
['QazWsx12345', [
__('"%1" length must be equal or less than %2 characters.', 'Test', 10)]
@@ -198,6 +199,16 @@ public function alphanumWithSpacesDataProvider(): array
];
}
+ /**
+ * Test for string with diacritics validation
+ */
+ public function testValidateValueStringWithDiacritics(): void
+ {
+ $inputValue = "á â à å ä ð é ê è ë í î ì ï ó ô ò ø õ ö ú û ù ü æ œ ç ß a ĝ ń ŕ ý ð ñ";
+ $expectedResult = true;
+ self::assertEquals($expectedResult, $this->model->validateValue($inputValue));
+ }
+
/**
* @param array $attributeData
* @return Attribute
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Backend/ArrayBackendTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Backend/ArrayBackendTest.php
index c01dd045e0857..4614c79e351a1 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Backend/ArrayBackendTest.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Backend/ArrayBackendTest.php
@@ -78,6 +78,16 @@ public static function validateDataProvider(): array
false,
null,
],
+ [
+ ['sku' => 'test1', 'attr' => '13,13'],
+ true,
+ '13'
+ ],
+ [
+ ['sku' => 'test1', 'attr' => '0,1,2,3,4'],
+ true,
+ '0,1,2,3,4'
+ ]
];
}
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Config/XsdTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Config/XsdTest.php
index d811e6da9926a..537f2d1e85c82 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Config/XsdTest.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Config/XsdTest.php
@@ -42,7 +42,9 @@ protected function setUp(): void
public function testSchemaCorrectlyIdentifiesInvalidXml($xmlString, $expectedError)
{
$actualError = $this->_xsdValidator->validate($this->_xsdSchema, $xmlString);
- $this->assertEquals($expectedError, $actualError);
+ foreach ($expectedError as $error) {
+ $this->assertContains($error, $actualError);
+ }
}
public function testSchemaCorrectlyIdentifiesValidXml()
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Config/_files/invalidEavAttributeXmlArray.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Config/_files/invalidEavAttributeXmlArray.php
index 33f8f0c7dd0fe..9a472b6b2aec8 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Config/_files/invalidEavAttributeXmlArray.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Config/_files/invalidEavAttributeXmlArray.php
@@ -54,24 +54,11 @@
[
"Element 'entity', attribute 'type': [facet 'pattern'] The value 'Name' is not accepted by the pattern " .
"'[a-z_]+'.\nLine: 1\n",
- "Element 'entity', attribute 'type': 'Name' is not a valid value of the atomic type " .
- "'identifierType'.\nLine: 1\n",
- "Element 'entity', attribute 'type': Warning: No precomputed value available, the value" .
- " was either invalid or something strange happend.\nLine: 1\n",
"Element 'attribute', attribute 'code': [facet " .
"'pattern'] The value 'code1' is not accepted by the pattern '[a-z_]+'.\nLine: 1\n",
- "Element 'attribute', attribute " .
- "'code': 'code1' is not a valid value of the atomic type 'identifierType'.\nLine: 1\n",
- "Element 'attribute', attribute " .
- "'code': Warning: No precomputed value available, " .
- "the value was either invalid or something strange happend.\nLine: 1\n",
"Element 'field', attribute 'code': [facet 'pattern'] " .
"The value 'code::one' is not accepted by the pattern '" .
- "[a-z_]+'.\nLine: 1\n",
- "Element 'field', attribute 'code': 'code::one' is not a valid value of the atomic type " .
- "'identifierType'.\nLine: 1\n",
- "Element 'field', attribute 'code': Warning: No precomputed value available, the value " .
- "was either invalid or something strange happend.\nLine: 1\n"
+ "[a-z_]+'.\nLine: 1\n"
],
]
];
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/CodeTest.php b/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/CodeTest.php
index 911716c0ebf2b..9e033098f35b9 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/CodeTest.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/CodeTest.php
@@ -11,6 +11,7 @@
namespace Magento\Eav\Test\Unit\Model\Validator\Attribute;
use Magento\Eav\Model\Validator\Attribute\Code;
+use Magento\Framework\Validator\ValidateException;
use PHPUnit\Framework\TestCase;
class CodeTest extends TestCase
@@ -21,7 +22,7 @@ class CodeTest extends TestCase
* @dataProvider isValidDataProvider
* @param string $attributeCode
* @param bool $expected
- * @throws \Zend_Validate_Exception
+ * @throws ValidateException
*/
public function testIsValid(string $attributeCode, bool $expected): void
{
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/DataTest.php b/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/DataTest.php
index 7e1992678b404..88daf1a8a6f52 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/DataTest.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/DataTest.php
@@ -17,7 +17,6 @@
use Magento\Framework\Model\AbstractModel;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\Stdlib\StringUtils;
-use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -32,21 +31,15 @@ class DataTest extends TestCase
private $attrDataFactory;
/**
- * @var \Magento\Eav\Model\Validator\Attribute\Data
+ * @var Data
*/
private $model;
- /**
- * @var ObjectManager
- */
- private $objectManager;
-
/**
* @inheritdoc
*/
protected function setUp(): void
{
- $this->objectManager = new ObjectManager($this);
$this->attrDataFactory = $this->getMockBuilder(AttributeDataFactory::class)
->onlyMethods(['create'])
->setConstructorArgs(
@@ -57,10 +50,7 @@ protected function setUp(): void
)
->getMock();
- $this->model = $this->objectManager->getObject(
- Data::class,
- ['_attrDataFactory' => $this->attrDataFactory]
- );
+ $this->model = new Data($this->attrDataFactory);
}
/**
@@ -190,7 +180,8 @@ public function isValidDataProvider(): array
],
'attributeReturns' => ['Error'],
'isValid' => true,
- 'messages' => []
+ 'messages' => [],
+ 'data' => [],
],
];
}
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/BatchDataMapper/ProductDataMapper.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php
index 0edc63b10f9ab..a9fb67f209aa7 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php
@@ -370,8 +370,11 @@ private function getValuesLabels(Attribute $attribute, array $attributeValues, i
return $attributeLabels;
}
+ // array_flip() + foreach { isset() } is much faster than foreach { in_array() } when there are many options
+ $attributeValues = array_flip($attributeValues);
+
foreach ($options as $option) {
- if (\in_array($option['value'], $attributeValues)) {
+ if (isset($attributeValues[$option['value']])) {
$attributeLabels[] = $option['label'];
}
}
diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php b/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php
index 05dd8c9d922be..c9f32c1fa584f 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php
@@ -7,6 +7,7 @@
namespace Magento\Elasticsearch\Model\Adapter;
use Elasticsearch\Common\Exceptions\Missing404Exception;
+use Exception;
use Magento\AdvancedSearch\Model\Client\ClientInterface;
use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\StaticField;
@@ -18,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
@@ -28,10 +31,10 @@ class Elasticsearch
/**#@+
* Text flags for Elasticsearch bulk actions
*/
- const BULK_ACTION_INDEX = 'index';
- const BULK_ACTION_CREATE = 'create';
- const BULK_ACTION_DELETE = 'delete';
- const BULK_ACTION_UPDATE = 'update';
+ public const BULK_ACTION_INDEX = 'index';
+ public const BULK_ACTION_CREATE = 'create';
+ public const BULK_ACTION_DELETE = 'delete';
+ public const BULK_ACTION_UPDATE = 'update';
/**#@-*/
/**
@@ -109,6 +112,18 @@ class Elasticsearch
*/
private $arrayManager;
+ /**
+ * @var Data
+ */
+ protected $helper;
+
+ /**
+ * @var array
+ */
+ private $responseErrorExceptionList = [
+ 'elasticsearchMissing404' => Missing404Exception::class
+ ];
+
/**
* @param ConnectionManager $connectionManager
* @param FieldMapperInterface $fieldMapper
@@ -117,10 +132,12 @@ 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
* @param ArrayManager|null $arrayManager
+ * @param array $responseErrorExceptionList
* @throws LocalizedException
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -132,10 +149,12 @@ public function __construct(
LoggerInterface $logger,
IndexNameResolver $indexNameResolver,
BatchDataMapperInterface $batchDocumentDataMapper,
+ Data $helper,
$options = [],
ProductAttributeRepositoryInterface $productAttributeRepository = null,
StaticField $staticFieldProvider = null,
- ArrayManager $arrayManager = null
+ ArrayManager $arrayManager = null,
+ array $responseErrorExceptionList = []
) {
$this->connectionManager = $connectionManager;
$this->fieldMapper = $fieldMapper;
@@ -144,16 +163,18 @@ 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 ?:
ObjectManager::getInstance()->get(StaticField::class);
$this->arrayManager = $arrayManager ?:
ObjectManager::getInstance()->get(ArrayManager::class);
+ $this->responseErrorExceptionList = array_merge($this->responseErrorExceptionList, $responseErrorExceptionList);
try {
$this->client = $this->connectionManager->getConnection($options);
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$this->logger->critical($e);
throw new LocalizedException(
__('The search failed because of a search engine misconfiguration.')
@@ -171,7 +192,7 @@ public function ping()
{
try {
$response = $this->client->ping();
- } catch (\Exception $e) {
+ } catch (Exception $e) {
throw new LocalizedException(
__('Could not ping search engine: %1', $e->getMessage())
);
@@ -205,7 +226,7 @@ public function prepareDocsPerStore(array $documentData, $storeId)
* @param int $storeId
* @param string $mappedIndexerId
* @return $this
- * @throws \Exception
+ * @throws Exception
*/
public function addDocs(array $documents, $storeId, $mappedIndexerId)
{
@@ -214,7 +235,7 @@ public function addDocs(array $documents, $storeId, $mappedIndexerId)
$indexName = $this->indexNameResolver->getIndexName($storeId, $mappedIndexerId, $this->preparedIndex);
$bulkIndexDocuments = $this->getDocsArrayInBulkIndexFormat($documents, $indexName);
$this->client->bulkQuery($bulkIndexDocuments);
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$this->logger->critical($e);
throw $e;
}
@@ -258,7 +279,7 @@ public function cleanIndex($storeId, $mappedIndexerId)
// remove index if already exists, wildcard deletion may cause collisions
try {
$this->client->deleteIndex($indexToDelete);
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$this->logger->critical($e);
}
}
@@ -276,7 +297,7 @@ public function cleanIndex($storeId, $mappedIndexerId)
* @param int $storeId
* @param string $mappedIndexerId
* @return $this
- * @throws \Exception
+ * @throws Exception
*/
public function deleteDocs(array $documentIds, $storeId, $mappedIndexerId)
{
@@ -289,7 +310,7 @@ public function deleteDocs(array $documentIds, $storeId, $mappedIndexerId)
self::BULK_ACTION_DELETE
);
$this->client->bulkQuery($bulkDeleteDocuments);
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$this->logger->critical($e);
throw $e;
}
@@ -318,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;
}
@@ -391,7 +424,7 @@ public function updateAlias($storeId, $mappedIndexerId)
if ($oldIndex) {
try {
$this->client->deleteIndex($oldIndex);
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$this->logger->critical($e);
}
unset($this->indexByCode[$mappedIndexerId . '_' . $storeId]);
@@ -417,15 +450,30 @@ public function updateIndexMapping(int $storeId, string $mappedIndexerId, string
try {
$this->updateMapping($attributeCode, $indexName);
- } catch (Missing404Exception $e) {
- unset($this->indexByCode[$mappedIndexerId . '_' . $storeId]);
- $indexName = $this->getIndexFromAlias($storeId, $mappedIndexerId);
- $this->updateMapping($attributeCode, $indexName);
+ } catch (Exception $e) {
+ if ($this->validateException($e)) {
+ unset($this->indexByCode[$mappedIndexerId . '_' . $storeId]);
+ $indexName = $this->getIndexFromAlias($storeId, $mappedIndexerId);
+ $this->updateMapping($attributeCode, $indexName);
+ } else {
+ throw $e;
+ }
}
return $this;
}
+ /**
+ * Check if the given class name is in the exception list
+ *
+ * @param Exception $exception
+ * @return bool
+ */
+ private function validateException(Exception $exception): bool
+ {
+ return in_array(get_class($exception), $this->responseErrorExceptionList, true);
+ }
+
/**
* Retrieve index definition from class.
*
diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php
index 7efdb50adf8e1..2d1396347270f 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php
@@ -137,7 +137,7 @@ public function isTextType(): bool
public function isComplexType(): bool
{
return in_array($this->getAttribute()->getFrontendInput(), ['select', 'multiselect'], true)
- || $this->getAttribute()->usesSource();
+ || ($this->getAttribute()->usesSource() && $this->getAttribute()->getFrontendInput() !== 'boolean');
}
/**
diff --git a/app/code/Magento/Elasticsearch/Model/DataProvider/Base/Suggestions.php b/app/code/Magento/Elasticsearch/Model/DataProvider/Base/Suggestions.php
index 7d8aa765e3494..d16041eef4b52 100644
--- a/app/code/Magento/Elasticsearch/Model/DataProvider/Base/Suggestions.php
+++ b/app/code/Magento/Elasticsearch/Model/DataProvider/Base/Suggestions.php
@@ -6,6 +6,7 @@
namespace Magento\Elasticsearch\Model\DataProvider\Base;
use Elasticsearch\Common\Exceptions\BadRequest400Exception;
+use Exception;
use Magento\AdvancedSearch\Model\SuggestedQueriesInterface;
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface;
use Magento\Elasticsearch\Model\Config;
@@ -70,6 +71,13 @@ class Suggestions implements SuggestedQueriesInterface
*/
private $getSuggestionFrequency;
+ /**
+ * @var array
+ */
+ private $responseErrorExceptionList = [
+ 'elasticsearchBadRequest404' => BadRequest400Exception::class
+ ];
+
/**
* Suggestions constructor.
*
@@ -82,6 +90,8 @@ class Suggestions implements SuggestedQueriesInterface
* @param FieldProviderInterface $fieldProvider
* @param LoggerInterface|null $logger
* @param GetSuggestionFrequencyInterface|null $getSuggestionFrequency
+ * @param array $responseErrorExceptionList
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
ScopeConfigInterface $scopeConfig,
@@ -92,7 +102,8 @@ public function __construct(
StoreManager $storeManager,
FieldProviderInterface $fieldProvider,
LoggerInterface $logger = null,
- ?GetSuggestionFrequencyInterface $getSuggestionFrequency = null
+ ?GetSuggestionFrequencyInterface $getSuggestionFrequency = null,
+ array $responseErrorExceptionList = []
) {
$this->queryResultFactory = $queryResultFactory;
$this->connectionManager = $connectionManager;
@@ -104,6 +115,7 @@ public function __construct(
$this->logger = $logger ?: ObjectManager::getInstance()->get(LoggerInterface::class);
$this->getSuggestionFrequency = $getSuggestionFrequency ?:
ObjectManager::getInstance()->get(GetSuggestionFrequencyInterface::class);
+ $this->responseErrorExceptionList = array_merge($this->responseErrorExceptionList, $responseErrorExceptionList);
}
/**
@@ -116,9 +128,13 @@ public function getItems(QueryInterface $query)
$isResultsCountEnabled = $this->isResultsCountEnabled();
try {
$suggestions = $this->getSuggestions($query);
- } catch (BadRequest400Exception $e) {
- $this->logger->critical($e);
- $suggestions = [];
+ } catch (Exception $e) {
+ if ($this->validateException($e)) {
+ $this->logger->critical($e);
+ $suggestions = [];
+ } else {
+ throw $e;
+ }
}
foreach ($suggestions as $suggestion) {
@@ -126,7 +142,7 @@ public function getItems(QueryInterface $query)
if ($isResultsCountEnabled) {
try {
$count = $this->getSuggestionFrequency->execute($suggestion['text']);
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$this->logger->critical($e);
}
@@ -154,11 +170,21 @@ public function isResultsCountEnabled()
);
}
+ /**
+ * Check if the given class name is in the exception list
+ *
+ * @param Exception $exception
+ * @return bool
+ */
+ private function validateException(Exception $exception): bool
+ {
+ return in_array(get_class($exception), $this->responseErrorExceptionList, true);
+ }
+
/**
* Get Suggestions
*
* @param QueryInterface $query
- *
* @return array
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
@@ -180,7 +206,7 @@ private function getSuggestions(QueryInterface $query)
}
}
}
- ksort($suggestions);
+ krsort($suggestions);
$texts = array_unique(array_column($suggestions, 'text'));
$suggestions = array_slice(
array_intersect_key(array_values($suggestions), $texts),
@@ -195,12 +221,11 @@ private function getSuggestions(QueryInterface $query)
/**
* Init Search Query
*
- * @param string $query
- *
+ * @param QueryInterface $query
* @return array
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
- private function initQuery($query)
+ private function initQuery(QueryInterface $query): array
{
$searchQuery = [
'index' => $this->searchIndexNameResolver->getIndexName(
@@ -235,7 +260,7 @@ private function addSuggestFields($searchQuery, $searchSuggestionsCount)
'field' => $field,
'analyzer' => 'standard',
'size' => $searchSuggestionsCount,
- 'max_errors' => 1,
+ 'max_errors' => 0.9,
'direct_generator' => [
[
'field' => $field,
@@ -299,8 +324,7 @@ private function isSuggestionsAllowed()
ScopeInterface::SCOPE_STORE
);
$isEnabled = $this->config->isElasticsearchEnabled();
- $isSuggestionsAllowed = ($isEnabled && $isSuggestionsEnabled);
- return $isSuggestionsAllowed;
+ return $isEnabled && $isSuggestionsEnabled;
}
}
diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php
index 97cb92ab3b06d..5b6103a653142 100644
--- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php
+++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php
@@ -211,7 +211,6 @@ private function categoryProductByCustomSortOrder(int $categoryId): array
$searchCriteria = $this->searchResult->getSearchCriteria();
$sortOrders = $searchCriteria->getSortOrders() ?? [];
$sortOrders = array_merge(['is_salable' => \Magento\Framework\DB\Select::SQL_DESC], $sortOrders);
-
$connection = $this->collection->getConnection();
$query = clone $connection->select()
->reset(\Magento\Framework\DB\Select::ORDER)
@@ -231,6 +230,14 @@ private function categoryProductByCustomSortOrder(int $categoryId): array
. ' AND cat_index.store_id = ' . $storeId,
['cat_index.position']
);
+
+ $productIds = [];
+ foreach ($this->searchResult->getItems() as $item) {
+ $productIds[] = $item->getId();
+ }
+
+ $query->where('e.entity_id IN(?)', $productIds);
+
foreach ($sortOrders as $field => $dir) {
if ($field === 'name') {
$entityTypeId = $this->collection->getEntity()->getTypeId();
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/ConnectionManager.php b/app/code/Magento/Elasticsearch/SearchAdapter/ConnectionManager.php
index 57973b7fb92e4..b8392f1ae0c5c 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/ConnectionManager.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/ConnectionManager.php
@@ -11,7 +11,7 @@
use Psr\Log\LoggerInterface;
/**
- * Class provides interface for Elasticsearch connection
+ * Class provides interface for Search Engine connection
*
* @api
* @since 100.1.0
@@ -75,7 +75,7 @@ public function getConnection($options = [])
}
/**
- * Connect to Elasticsearch client with default options
+ * Connect to Search client with default options
*
* @param array $options
* @throws \RuntimeException
@@ -87,7 +87,7 @@ private function connect($options)
$this->client = $this->clientFactory->create($this->clientConfig->prepareClientOptions($options));
} catch (\Exception $e) {
$this->logger->critical($e);
- throw new \RuntimeException('Elasticsearch client is not set.');
+ throw new \RuntimeException('Search client is not set.');
}
}
}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/MatchQuery.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/MatchQuery.php
index 4e65effbdddb3..6b5e8337de101 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/MatchQuery.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/MatchQuery.php
@@ -70,51 +70,6 @@ public function __construct(
$this->config = $config;
}
- /**
- * @inheritdoc
- */
- public function build(array $selectQuery, RequestQueryInterface $requestQuery, $conditionType)
- {
- $queryValue = $this->prepareQuery($requestQuery->getValue(), $conditionType);
- $queries = $this->buildQueries($requestQuery->getMatches(), $queryValue);
- $requestQueryBoost = $requestQuery->getBoost() ?: 1;
- $minimumShouldMatch = $this->config->getElasticsearchConfigData('minimum_should_match');
-
- foreach ($queries as $query) {
- $queryBody = $query['body'];
- $matchKey = array_keys($queryBody)[0];
- foreach ($queryBody[$matchKey] as $field => $matchQuery) {
- $matchQuery['boost'] = $requestQueryBoost + $matchQuery['boost'];
- if ($minimumShouldMatch && $matchKey != 'match_phrase_prefix') {
- $matchQuery['minimum_should_match'] = $minimumShouldMatch;
- }
- $queryBody[$matchKey][$field] = $matchQuery;
- }
- $selectQuery['bool'][$query['condition']][] = $queryBody;
- }
-
- return $selectQuery;
- }
-
- /**
- * Prepare query
- *
- * @param string $queryValue
- * @param string $conditionType
- * @return array
- */
- private function prepareQuery(string $queryValue, string $conditionType): array
- {
- $condition = $conditionType === BoolExpression::QUERY_CONDITION_NOT
- ? self::QUERY_CONDITION_MUST_NOT
- : $conditionType;
-
- return [
- 'condition' => $condition,
- 'value' => $queryValue,
- ];
- }
-
/**
* Creates valid ElasticSearch search conditions from Match queries
*
@@ -125,13 +80,16 @@ private function prepareQuery(string $queryValue, string $conditionType): array
* The search query boost is an optional in the search query and therefore it will be set to 1 by default
* if none passed with a match query.
*
- * @param array $matches
- * @param array $queryValue
+ * @param array $selectQuery
+ * @param RequestQueryInterface $requestQuery
+ * @param string $conditionType
* @return array
*/
- private function buildQueries(array $matches, array $queryValue): array
+ public function build(array $selectQuery, RequestQueryInterface $requestQuery, $conditionType)
{
- $conditions = [];
+ $queryValue = $this->prepareQuery($requestQuery->getValue(), $conditionType);
+ $requestQueryBoost = $requestQuery->getBoost() ?: 1;
+ $minimumShouldMatch = $this->config->getElasticsearchConfigData('minimum_should_match');
// Checking for quoted phrase \"phrase test\", trim escaped surrounding quotes if found
$count = 0;
@@ -139,7 +97,7 @@ private function buildQueries(array $matches, array $queryValue): array
$condition = ($count) ? 'match_phrase' : 'match';
$transformedTypes = [];
- foreach ($matches as $match) {
+ foreach ($requestQuery->getMatches() as $match) {
$resolvedField = $this->fieldMapper->getFieldName(
$match['field'],
['type' => FieldMapperInterface::TYPE_QUERY]
@@ -153,29 +111,62 @@ private function buildQueries(array $matches, array $queryValue): array
$transformedTypes[$valueTransformerHash] = $valueTransformer->transform($value);
}
$transformedValue = $transformedTypes[$valueTransformerHash];
-
if (null === $transformedValue) {
//Value is incompatible with this field type.
continue;
}
+
$matchCondition = $match['matchCondition'] ?? $condition;
$fields = [];
$fields[$resolvedField] = [
'query' => $transformedValue,
- 'boost' => $match['boost'] ?? 1,
+ 'boost' => $requestQueryBoost + ($match['boost'] ?? 1),
];
if (isset($match['analyzer'])) {
$fields[$resolvedField]['analyzer'] = $match['analyzer'];
}
- $conditions[] = [
- 'condition' => $queryValue['condition'],
- 'body' => [
- $matchCondition => $fields,
- ],
- ];
+
+ if ($minimumShouldMatch && $this->isConditionSupportMinimumShouldMatch($matchCondition)) {
+ $fields[$resolvedField]['minimum_should_match'] = $minimumShouldMatch;
+ }
+
+ $selectQuery['bool'][$queryValue['condition']][] = [$matchCondition => $fields];
}
- return $conditions;
+ return $selectQuery;
+ }
+
+ /**
+ * Prepare query
+ *
+ * @param string $queryValue
+ * @param string $conditionType
+ * @return array
+ */
+ private function prepareQuery(string $queryValue, string $conditionType): array
+ {
+ $condition = $conditionType === BoolExpression::QUERY_CONDITION_NOT
+ ? self::QUERY_CONDITION_MUST_NOT
+ : $conditionType;
+
+ return [
+ 'condition' => $condition,
+ 'value' => $queryValue,
+ ];
+ }
+
+ /**
+ * Check does condition support the minimum_should_match field
+ *
+ * @param string $condition
+ * @return bool
+ */
+ private function isConditionSupportMinimumShouldMatch(string $condition): bool
+ {
+ return !in_array($condition, [
+ 'match_phrase_prefix',
+ 'match_phrase',
+ ]);
}
}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php
index f2aedde6016b1..7d41d54fb22a5 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php
@@ -92,7 +92,7 @@ public function getSort(RequestInterface $request)
if (in_array($item['field'], $this->skippedFields)) {
continue;
}
- $attribute = $this->attributeAdapterProvider->getByAttributeCode($item['field']);
+ $attribute = $this->attributeAdapterProvider->getByAttributeCode((string)$item['field']);
$fieldName = $this->fieldNameResolver->getFieldName($attribute);
if (isset($this->map[$fieldName])) {
$fieldName = $this->map[$fieldName];
diff --git a/app/code/Magento/Elasticsearch/Test/Mftf/Data/ConfigData.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Data/ConfigData.xml
deleted file mode 100644
index 5a1118d079158..0000000000000
--- a/app/code/Magento/Elasticsearch/Test/Mftf/Data/ConfigData.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
- catalog/search/engine
- 1
- Elasticsearch {{_ENV.ELASTICSEARCH_VERSION}}.0+
- elasticsearch{{_ENV.ELASTICSEARCH_VERSION}}
-
-
diff --git a/app/code/Magento/Elasticsearch/Test/Mftf/Suite/SearchEngineElasticsearchSuite.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Suite/SearchEngineElasticsearchSuite.xml
index 89577745b27a7..ff2b86d3abd55 100644
--- a/app/code/Magento/Elasticsearch/Test/Mftf/Suite/SearchEngineElasticsearchSuite.xml
+++ b/app/code/Magento/Elasticsearch/Test/Mftf/Suite/SearchEngineElasticsearchSuite.xml
@@ -7,17 +7,6 @@
-->
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/code/Magento/Elasticsearch/Test/Mftf/Test/StoreFrontSearchWithProductAttributeOptionValue.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StoreFrontSearchWithProductAttributeOptionValue.xml
new file mode 100644
index 0000000000000..3c3bac70f4dc2
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StoreFrontSearchWithProductAttributeOptionValue.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontCheckAdvancedSearchOnElasticSearchTest.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontCheckAdvancedSearchOnElasticSearchTest.xml
index b2a8f88ac86fc..c2d2f36fde498 100644
--- a/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontCheckAdvancedSearchOnElasticSearchTest.xml
+++ b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontCheckAdvancedSearchOnElasticSearchTest.xml
@@ -16,7 +16,7 @@
-
+
diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticSearchForChineseLocaleTest.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontElasticSearchForChineseLocaleTest.xml
similarity index 92%
rename from app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticSearchForChineseLocaleTest.xml
rename to app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontElasticSearchForChineseLocaleTest.xml
index f85f55511ee93..26e0d6192b060 100644
--- a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticSearchForChineseLocaleTest.xml
+++ b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontElasticSearchForChineseLocaleTest.xml
@@ -10,15 +10,15 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
-
-
-
-
+
+
+
+
-
+
diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticsearchSearchInvalidValueTest.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontElasticsearchSearchInvalidValueTest.xml
similarity index 99%
rename from app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticsearchSearchInvalidValueTest.xml
rename to app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontElasticsearchSearchInvalidValueTest.xml
index e82117c07438c..c113ebb9a0683 100644
--- a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticsearchSearchInvalidValueTest.xml
+++ b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontElasticsearchSearchInvalidValueTest.xml
@@ -10,7 +10,7 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
-
+
diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchTest.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchTest.xml
similarity index 98%
rename from app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchTest.xml
rename to app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchTest.xml
index bd49cde2a3ce4..54160eebf4328 100644
--- a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchTest.xml
+++ b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchTest.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchWithNotAvailablePageTest.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchWithNotAvailablePageTest.xml
similarity index 97%
rename from app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchWithNotAvailablePageTest.xml
rename to app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchWithNotAvailablePageTest.xml
index 5fc7dac1e24c1..d23fc13fdd8ec 100644
--- a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchWithNotAvailablePageTest.xml
+++ b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchWithNotAvailablePageTest.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchWithSynonymsTest.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchWithSynonymsTest.xml
index 99547626455b1..1eafcb0532466 100644
--- a/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchWithSynonymsTest.xml
+++ b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontProductQuickSearchUsingElasticSearchWithSynonymsTest.xml
@@ -18,7 +18,7 @@
-
+
@@ -81,8 +81,8 @@
-
-
+
+
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/AttributeAdapterTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/AttributeAdapterTest.php
index 18a158f3629f1..344c8c6707ff3 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/AttributeAdapterTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/AttributeAdapterTest.php
@@ -254,6 +254,7 @@ public function isComplexTypeProvider()
['multiselect', false, true],
['int', false, false],
['int', true, true],
+ ['boolean', true, false],
];
}
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchQueryTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchQueryTest.php
index ccdef048b880a..a0e238a8b038b 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchQueryTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchQueryTest.php
@@ -16,7 +16,6 @@
use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface;
use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool;
use Magento\Framework\Search\Request\Query\MatchQuery;
-use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -39,12 +38,14 @@ class MatchQueryTest extends TestCase
* @var MatchQueryBuilder
*/
private $matchQueryBuilder;
+
/**
- * @var MockObject
+ * @var Config|MockObject
*/
private $config;
+
/**
- * @var MockObject
+ * @var FieldMapperInterface|MockObject
*/
private $fieldMapper;
@@ -65,16 +66,13 @@ protected function setUp(): void
->willReturn($valueTransformerMock);
$valueTransformerMock->method('transform')
->willReturnArgument(0);
- $this->matchQueryBuilder = (new ObjectManager($this))->getObject(
- MatchQueryBuilder::class,
- [
- 'fieldMapper' => $this->fieldMapper,
- 'preprocessorContainer' => [],
- 'attributeProvider' => $this->attributeProvider,
- 'fieldTypeResolver' => $this->fieldTypeResolver,
- 'valueTransformerPool' => $valueTransformerPoolMock,
- 'config' => $this->config,
- ]
+
+ $this->matchQueryBuilder = new MatchQueryBuilder(
+ $this->fieldMapper,
+ $this->attributeProvider,
+ $this->fieldTypeResolver,
+ $valueTransformerPoolMock,
+ $this->config
);
}
@@ -182,6 +180,7 @@ public function buildDataProvider(): array
],
'2<75%'
],
+ //[match_phrase] query does not support [minimum_should_match]
'match_phrase query with minimum_should_match' => [
'"fitness bottle"',
[
@@ -196,14 +195,12 @@ public function buildDataProvider(): array
'name' => [
'query' => 'fitness bottle',
'boost' => 6,
- 'minimum_should_match' => '2<75%',
],
],
],
],
'2<75%'
],
-
];
}
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Setup/InstallConfigTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Setup/InstallConfigTest.php
index 86225b3634711..16b03f133790f 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/Setup/InstallConfigTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/Setup/InstallConfigTest.php
@@ -7,9 +7,9 @@
namespace Magento\Elasticsearch\Test\Unit\Setup;
-use Magento\Elasticsearch\Setup\InstallConfig;
use Magento\Framework\App\Config\Storage\WriterInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Search\Setup\InstallConfig;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
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/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml
index cb26e3afb2fbb..95aec47dbf1f6 100644
--- a/app/code/Magento/Elasticsearch/etc/di.xml
+++ b/app/code/Magento/Elasticsearch/etc/di.xml
@@ -254,12 +254,6 @@
Magento\Elasticsearch\Model\Config
-
-
- Magento\Elasticsearch\Elasticsearch5\Model\Client\ClientFactoryProxy
- Magento\Elasticsearch\Model\Config
-
-
Magento\Elasticsearch\Elasticsearch5\Model\Client\Elasticsearch
@@ -274,7 +268,7 @@
- Magento\Elasticsearch\Elasticsearch5\SearchAdapter\ConnectionManager
+ Magento\Elasticsearch\SearchAdapter\ConnectionManager
@@ -518,7 +512,8 @@
-
+
+
- elasticsearch5_server_hostname
@@ -530,7 +525,7 @@
- elasticsearch5_password
-
+
@@ -542,6 +537,7 @@
- Elasticsearch 2
+ - Elasticsearch 6
- Magento\Elasticsearch\Setup\Validator
diff --git a/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php b/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php
deleted file mode 100644
index c192b43bdc081..0000000000000
--- a/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php
+++ /dev/null
@@ -1,32 +0,0 @@
- 'catalog_search_engine',
- 'hostname' => 'catalog_search_elasticsearch6_server_hostname',
- 'port' => 'catalog_search_elasticsearch6_server_port',
- 'index' => 'catalog_search_elasticsearch6_index_prefix',
- 'enableAuth' => 'catalog_search_elasticsearch6_enable_auth',
- 'username' => 'catalog_search_elasticsearch6_username',
- 'password' => 'catalog_search_elasticsearch6_password',
- 'timeout' => 'catalog_search_elasticsearch6_server_timeout',
- ];
-
- return array_merge(parent::_getFieldMapping(), $fields);
- }
-}
diff --git a/app/code/Magento/Elasticsearch6/LICENSE.txt b/app/code/Magento/Elasticsearch6/LICENSE.txt
deleted file mode 100644
index 49525fd99da9c..0000000000000
--- a/app/code/Magento/Elasticsearch6/LICENSE.txt
+++ /dev/null
@@ -1,48 +0,0 @@
-
-Open Software License ("OSL") v. 3.0
-
-This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work:
-
-Licensed under the Open Software License version 3.0
-
- 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following:
-
- 1. to reproduce the Original Work in copies, either alone or as part of a collective work;
-
- 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work;
-
- 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License;
-
- 4. to perform the Original Work publicly; and
-
- 5. to display the Original Work publicly.
-
- 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works.
-
- 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work.
-
- 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license.
-
- 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c).
-
- 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.
-
- 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer.
-
- 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation.
-
- 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c).
-
- 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.
-
- 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License.
-
- 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.
-
- 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.
-
- 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
-
- 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.
-
- 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.
\ No newline at end of file
diff --git a/app/code/Magento/Elasticsearch6/LICENSE_AFL.txt b/app/code/Magento/Elasticsearch6/LICENSE_AFL.txt
deleted file mode 100644
index f39d641b18a19..0000000000000
--- a/app/code/Magento/Elasticsearch6/LICENSE_AFL.txt
+++ /dev/null
@@ -1,48 +0,0 @@
-
-Academic Free License ("AFL") v. 3.0
-
-This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work:
-
-Licensed under the Academic Free License version 3.0
-
- 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following:
-
- 1. to reproduce the Original Work in copies, either alone or as part of a collective work;
-
- 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work;
-
- 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License;
-
- 4. to perform the Original Work publicly; and
-
- 5. to display the Original Work publicly.
-
- 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works.
-
- 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work.
-
- 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license.
-
- 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c).
-
- 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.
-
- 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer.
-
- 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation.
-
- 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c).
-
- 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.
-
- 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License.
-
- 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.
-
- 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.
-
- 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
-
- 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.
-
- 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.
diff --git a/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php b/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php
deleted file mode 100644
index cc8f69e92a858..0000000000000
--- a/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
- Check ElasticSearch connection after enabling.
-
-
-
-
-
-
-
-
-
- $grabConnectionStatus
- {{AdminElasticsearch6TestConnectionMessageData.successMessage}}
-
-
-
diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Data/Elasticsearch6ConfigData.xml b/app/code/Magento/Elasticsearch6/Test/Mftf/Data/Elasticsearch6ConfigData.xml
deleted file mode 100644
index 7a61f13e62049..0000000000000
--- a/app/code/Magento/Elasticsearch6/Test/Mftf/Data/Elasticsearch6ConfigData.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
- catalog/search/engine
- elasticsearch6
-
-
diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php
deleted file mode 100644
index f52e24d72e4d4..0000000000000
--- a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php
+++ /dev/null
@@ -1,688 +0,0 @@
-elasticsearchClientMock = $this->getMockBuilder(Client::class)
- ->setMethods(
- [
- 'indices',
- 'ping',
- 'bulk',
- 'search',
- 'scroll',
- 'suggest',
- 'info',
- ]
- )
- ->disableOriginalConstructor()
- ->getMock();
- $this->indicesMock = $this->getMockBuilder(IndicesNamespace::class)
- ->setMethods(
- [
- 'exists',
- 'getSettings',
- 'create',
- 'delete',
- 'putMapping',
- 'deleteMapping',
- 'getMapping',
- 'stats',
- 'updateAliases',
- 'existsAlias',
- 'getAlias',
- ]
- )
- ->disableOriginalConstructor()
- ->getMock();
- $this->elasticsearchClientMock->expects($this->any())
- ->method('indices')
- ->willReturn($this->indicesMock);
- $this->elasticsearchClientMock->expects($this->any())
- ->method('ping')
- ->willReturn(true);
- $this->elasticsearchClientMock->expects($this->any())
- ->method('info')
- ->willReturn(['version' => ['number' => '6.0.0']]);
-
- $this->objectManager = new ObjectManagerHelper($this);
- $this->model = $this->objectManager->getObject(
- Elasticsearch::class,
- [
- 'options' => $this->getOptions(),
- 'elasticsearchClient' => $this->elasticsearchClientMock,
- 'fieldsMappingPreprocessors' => [
- new AddDefaultSearchField()
- ]
- ]
- );
- }
-
- public function testConstructorOptionsException()
- {
- $this->expectException('Magento\Framework\Exception\LocalizedException');
- $result = $this->objectManager->getObject(
- Elasticsearch::class,
- [
- 'options' => []
- ]
- );
- $this->assertNotNull($result);
- }
-
- /**
- * Test client creation from the list of options
- */
- public function testConstructorWithOptions()
- {
- $result = $this->objectManager->getObject(
- Elasticsearch::class,
- [
- 'options' => $this->getOptions()
- ]
- );
- $this->assertNotNull($result);
- }
-
- /**
- * Ensure that configuration returns correct url.
- *
- * @param array $options
- * @param string $expectedResult
- * @throws LocalizedException
- * @throws \ReflectionException
- * @dataProvider getOptionsDataProvider
- */
- public function testBuildConfig(array $options, $expectedResult): void
- {
- $buildConfig = new Elasticsearch($options);
- $config = $this->getPrivateMethod(Elasticsearch::class, 'buildConfig');
- $result = $config->invoke($buildConfig, $options);
- $this->assertEquals($expectedResult, $result['hosts'][0]);
- }
-
- /**
- * Return private method for elastic search class.
- *
- * @param $className
- * @param $methodName
- * @return \ReflectionMethod
- * @throws \ReflectionException
- */
- private function getPrivateMethod($className, $methodName)
- {
- $reflector = new \ReflectionClass($className);
- $method = $reflector->getMethod($methodName);
- $method->setAccessible(true);
-
- return $method;
- }
-
- /**
- * Get options data provider.
- */
- public function getOptionsDataProvider()
- {
- return [
- [
- 'without_protocol' => [
- 'hostname' => 'localhost',
- 'port' => '9200',
- 'timeout' => 15,
- 'index' => 'magento2',
- 'enableAuth' => 0,
- ],
- 'expected_result' => 'http://localhost:9200'
- ],
- [
- 'with_protocol' => [
- 'hostname' => 'https://localhost',
- 'port' => '9200',
- 'timeout' => 15,
- 'index' => 'magento2',
- 'enableAuth' => 0,
- ],
- 'expected_result' => 'https://localhost:9200'
- ]
- ];
- }
-
- /**
- * Test ping functionality
- */
- public function testPing()
- {
- $this->elasticsearchClientMock->expects($this->once())->method('ping')->willReturn(true);
- $this->assertTrue($this->model->ping());
- }
-
- /**
- * Test validation of connection parameters
- */
- public function testTestConnection()
- {
- $this->elasticsearchClientMock->expects($this->once())->method('ping')->willReturn(true);
- $this->assertTrue($this->model->testConnection());
- }
-
- /**
- * Test validation of connection parameters returns false
- */
- public function testTestConnectionFalse()
- {
- $this->elasticsearchClientMock->expects($this->once())->method('ping')->willReturn(false);
- $this->assertTrue($this->model->testConnection());
- }
-
- /**
- * Test validation of connection parameters
- */
- public function testTestConnectionPing()
- {
- $this->model = $this->objectManager->getObject(
- Elasticsearch::class,
- [
- 'options' => $this->getEmptyIndexOption(),
- 'elasticsearchClient' => $this->elasticsearchClientMock
- ]
- );
-
- $this->model->ping();
- $this->assertTrue($this->model->testConnection());
- }
-
- /**
- * Test bulkQuery() method
- */
- public function testBulkQuery()
- {
- $this->elasticsearchClientMock->expects($this->once())
- ->method('bulk')
- ->with([]);
- $this->model->bulkQuery([]);
- }
-
- /**
- * Test createIndex() method, case when such index exists
- */
- public function testCreateIndexExists()
- {
- $this->indicesMock->expects($this->once())
- ->method('create')
- ->with(
- [
- 'index' => 'indexName',
- 'body' => [],
- ]
- );
- $this->model->createIndex('indexName', []);
- }
-
- /**
- * Test deleteIndex() method.
- */
- public function testDeleteIndex()
- {
- $this->indicesMock->expects($this->once())
- ->method('delete')
- ->with(['index' => 'indexName']);
- $this->model->deleteIndex('indexName');
- }
-
- /**
- * Test isEmptyIndex() method.
- */
- public function testIsEmptyIndex()
- {
- $indexName = 'magento2_index';
- $stats['indices'][$indexName]['primaries']['docs']['count'] = 0;
-
- $this->indicesMock->expects($this->once())
- ->method('stats')
- ->with(['index' => $indexName, 'metric' => 'docs'])
- ->willReturn($stats);
- $this->assertTrue($this->model->isEmptyIndex($indexName));
- }
-
- /**
- * Test isEmptyIndex() method returns false.
- */
- public function testIsEmptyIndexFalse()
- {
- $indexName = 'magento2_index';
- $stats['indices'][$indexName]['primaries']['docs']['count'] = 1;
-
- $this->indicesMock->expects($this->once())
- ->method('stats')
- ->with(['index' => $indexName, 'metric' => 'docs'])
- ->willReturn($stats);
- $this->assertFalse($this->model->isEmptyIndex($indexName));
- }
-
- /**
- * Test updateAlias() method with new index.
- */
- public function testUpdateAlias()
- {
- $alias = 'alias1';
- $index = 'index1';
-
- $params['body']['actions'][] = ['add' => ['alias' => $alias, 'index' => $index]];
-
- $this->indicesMock->expects($this->once())
- ->method('updateAliases')
- ->with($params);
- $this->model->updateAlias($alias, $index);
- }
-
- /**
- * Test updateAlias() method with new and old index.
- */
- public function testUpdateAliasRemoveOldIndex()
- {
- $alias = 'alias1';
- $newIndex = 'index1';
- $oldIndex = 'indexOld';
-
- $params['body']['actions'][] = ['remove' => ['alias' => $alias, 'index' => $oldIndex]];
- $params['body']['actions'][] = ['add' => ['alias' => $alias, 'index' => $newIndex]];
-
- $this->indicesMock->expects($this->once())
- ->method('updateAliases')
- ->with($params);
- $this->model->updateAlias($alias, $newIndex, $oldIndex);
- }
-
- /**
- * Test indexExists() method, case when no such index exists
- */
- public function testIndexExists()
- {
- $this->indicesMock->expects($this->once())
- ->method('exists')
- ->with(['index' => 'indexName'])
- ->willReturn(true);
- $this->model->indexExists('indexName');
- }
-
- /**
- * Tests existsAlias() method checking for alias.
- */
- public function testExistsAlias()
- {
- $alias = 'alias1';
- $params = ['name' => $alias];
- $this->indicesMock->expects($this->once())
- ->method('existsAlias')
- ->with($params)
- ->willReturn(true);
- $this->assertTrue($this->model->existsAlias($alias));
- }
-
- /**
- * Tests existsAlias() method checking for alias and index.
- */
- public function testExistsAliasWithIndex()
- {
- $alias = 'alias1';
- $index = 'index1';
- $params = ['name' => $alias, 'index' => $index];
- $this->indicesMock->expects($this->once())
- ->method('existsAlias')
- ->with($params)
- ->willReturn(true);
- $this->assertTrue($this->model->existsAlias($alias, $index));
- }
-
- /**
- * Test getAlias() method.
- */
- public function testGetAlias()
- {
- $alias = 'alias1';
- $params = ['name' => $alias];
- $this->indicesMock->expects($this->once())
- ->method('getAlias')
- ->with($params)
- ->willReturn([]);
- $this->assertEquals([], $this->model->getAlias($alias));
- }
-
- /**
- * Test createIndexIfNotExists() method, case when operation fails
- */
- public function testCreateIndexFailure()
- {
- $this->expectException('Exception');
- $this->indicesMock->expects($this->once())
- ->method('create')
- ->with(
- [
- 'index' => 'indexName',
- 'body' => [],
- ]
- )
- ->willThrowException(new \Exception('Something went wrong'));
- $this->model->createIndex('indexName', []);
- }
-
- /**
- * Test testAddFieldsMapping() method
- */
- public function testAddFieldsMapping()
- {
- $this->indicesMock->expects($this->once())
- ->method('putMapping')
- ->with(
- [
- 'index' => 'indexName',
- 'type' => 'product',
- 'body' => [
- 'product' => [
- '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'
- );
- }
-
- /**
- * Test testAddFieldsMapping() method
- */
- public function testAddFieldsMappingFailure()
- {
- $this->expectException('Exception');
- $this->indicesMock->expects($this->once())
- ->method('putMapping')
- ->with(
- [
- 'index' => 'indexName',
- 'type' => 'product',
- 'body' => [
- 'product' => [
- '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',
- ],
- ],
- ],
- ],
- ],
- ],
- ]
- )
- ->willThrowException(new \Exception('Something went wrong'));
- $this->model->addFieldsMapping(
- [
- 'name' => [
- 'type' => 'text',
- ],
- ],
- 'indexName',
- 'product'
- );
- }
-
- /**
- * Test deleteMapping() method
- */
- public function testDeleteMapping()
- {
- $this->indicesMock->expects($this->once())
- ->method('deleteMapping')
- ->with(
- [
- 'index' => 'indexName',
- 'type' => 'product',
- ]
- );
- $this->model->deleteMapping(
- 'indexName',
- 'product'
- );
- }
-
- /**
- * Test deleteMapping() method
- */
- public function testDeleteMappingFailure()
- {
- $this->expectException('Exception');
- $this->indicesMock->expects($this->once())
- ->method('deleteMapping')
- ->with(
- [
- 'index' => 'indexName',
- 'type' => 'product',
- ]
- )
- ->willThrowException(new \Exception('Something went wrong'));
- $this->model->deleteMapping(
- 'indexName',
- 'product'
- );
- }
-
- /**
- * Test get Elasticsearch mapping process.
- *
- * @return void
- */
- public function testGetMapping(): void
- {
- $params = ['index' => 'indexName'];
- $this->indicesMock->expects($this->once())
- ->method('getMapping')
- ->with($params)
- ->willReturn([]);
-
- $this->model->getMapping($params);
- }
-
- /**
- * Test query() method
- * @return void
- */
- public function testQuery()
- {
- $query = ['test phrase query'];
- $this->elasticsearchClientMock->expects($this->once())
- ->method('search')
- ->with($query)
- ->willReturn([]);
- $this->assertEquals([], $this->model->query($query));
- }
-
- /**
- * Test suggest() method
- * @return void
- */
- public function testSuggest()
- {
- $query = 'query';
- $this->elasticsearchClientMock->expects($this->once())
- ->method('suggest')
- ->willReturn([]);
- $this->assertEquals([], $this->model->suggest($query));
- }
-
- /**
- * Get elasticsearch client options
- *
- * @return array
- */
- protected function getOptions()
- {
- return [
- 'hostname' => 'localhost',
- 'port' => '9200',
- 'timeout' => 15,
- 'index' => 'magento2',
- 'enableAuth' => 1,
- 'username' => 'user',
- 'password' => 'passwd',
- ];
- }
-
- /**
- * @return array
- */
- protected function getEmptyIndexOption()
- {
- return [
- 'hostname' => 'localhost',
- 'port' => '9200',
- 'index' => '',
- 'timeout' => 15,
- 'enableAuth' => 1,
- 'username' => 'user',
- 'password' => 'passwd',
- ];
- }
-}
diff --git a/app/code/Magento/Elasticsearch6/etc/adminhtml/system.xml b/app/code/Magento/Elasticsearch6/etc/adminhtml/system.xml
deleted file mode 100644
index 5907442d28a0b..0000000000000
--- a/app/code/Magento/Elasticsearch6/etc/adminhtml/system.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
-
-
-
-
-
-
- Elasticsearch Server Hostname
-
- elasticsearch6
-
-
-
-
- Elasticsearch Server Port
-
- elasticsearch6
-
-
-
-
- Elasticsearch Index Prefix
-
- elasticsearch6
-
-
-
-
- Enable Elasticsearch HTTP Auth
- Magento\Config\Model\Config\Source\Yesno
-
- elasticsearch6
-
-
-
-
- Elasticsearch HTTP Username
-
- elasticsearch6
- 1
-
-
-
-
- Elasticsearch HTTP Password
-
- elasticsearch6
- 1
-
-
-
-
- Elasticsearch Server Timeout
-
- elasticsearch6
-
-
-
-
-
- Test Connection
- Magento\Elasticsearch6\Block\Adminhtml\System\Config\TestConnection
-
- elasticsearch6
-
-
-
- Minimum Terms to Match
-
- elasticsearch6
-
- Learn more about valid syntax.]]>
- Magento\Elasticsearch\Model\Config\Backend\MinimumShouldMatch
-
-
-
-
-
diff --git a/app/code/Magento/Elasticsearch6/etc/config.xml b/app/code/Magento/Elasticsearch6/etc/config.xml
deleted file mode 100644
index 3b7dff55ec420..0000000000000
--- a/app/code/Magento/Elasticsearch6/etc/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
- elasticsearch6
- localhost
- 9200
- magento2
- 0
- 15
-
-
-
-
-
diff --git a/app/code/Magento/Elasticsearch6/etc/di.xml b/app/code/Magento/Elasticsearch6/etc/di.xml
deleted file mode 100644
index bb6fe436e6202..0000000000000
--- a/app/code/Magento/Elasticsearch6/etc/di.xml
+++ /dev/null
@@ -1,250 +0,0 @@
-
-
-
-
-
-
- - elasticsearch6
-
-
-
-
-
-
-
- - Elasticsearch 6.x (Deprecated)
-
-
-
-
-
-
-
- - Magento\Elasticsearch\Elasticsearch5\Model\Adapter\BatchDataMapper\CategoryFieldsProvider
-
-
-
-
-
-
- - \Magento\Elasticsearch6\Model\Client\ElasticsearchFactory
-
-
- - \Magento\Elasticsearch\Model\Config
-
-
-
-
-
-
-
- - Magento\Elasticsearch\Model\Indexer\IndexerHandler
-
-
-
-
-
-
-
- - Magento\Elasticsearch\Model\Indexer\IndexStructure
-
-
-
-
-
-
-
- - Magento\Elasticsearch\Model\ResourceModel\Engine
-
-
-
-
-
-
-
- - Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Adapter
-
-
-
-
-
-
-
- - elasticsearch6
-
- elasticsearch6
-
-
-
-
-
- Magento\Elasticsearch6\Model\Client\Elasticsearch
-
-
-
-
-
-
- - Magento\Elasticsearch6\Model\Client\ElasticsearchFactory
-
-
-
-
-
-
-
- - Magento\Elasticsearch\Model\Adapter\FieldMapper\CopySearchableFieldsToSearchField
- - Magento\Elasticsearch\Model\Adapter\FieldMapper\AddDefaultSearchField
-
-
-
-
-
-
-
- - Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Aggregation\Interval
-
-
-
-
-
-
-
- - Magento\Elasticsearch\SearchAdapter\Dynamic\DataProvider
-
-
-
-
-
-
-
-
- - Magento\Elasticsearch6\Model\DataProvider\Suggestions
-
-
-
-
-
-
- elasticsearch5FieldProvider
-
-
-
-
-
- - Magento\Elasticsearch6\Model\Adapter\FieldMapper\ProductFieldMapper
-
-
-
-
-
-
- - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\NotEavAttribute
- - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\SpecialAttribute
- - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\Price
- - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CategoryName
- - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\Position
- - \Magento\Elasticsearch6\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver
-
-
-
-
-
-
- elasticsearch5FieldProvider
- elasticsearch6FieldNameResolver
-
-
-
-
-
-
- - 10000
-
-
-
-
-
-
-
- - elasticsearchCategoryCollectionFactory
-
-
-
-
-
-
-
- - elasticsearchAdvancedCollectionFactory
-
-
-
-
-
-
-
- - Magento\Elasticsearch\Model\Advanced\ProductCollectionPrepareStrategy
-
-
-
-
-
-
-
- - elasticsearchFulltextSearchCollectionFactory
-
-
-
-
-
-
-
- - 1
- - 1
- - 1
-
-
- - 1
- - 1
- - 1
- - 1
- - 1
- - 1
- - 1
-
-
-
-
-
-
- - elasticsearch6_server_hostname
- - elasticsearch6_server_port
- - elasticsearch6_server_timeout
- - elasticsearch6_index_prefix
- - elasticsearch6_enable_auth
- - elasticsearch6_username
- - elasticsearch6_password
-
-
-
-
-
-
- - Magento\Elasticsearch6\Setup\InstallConfig
-
-
-
-
-
-
- - Magento\Elasticsearch\Setup\Validator
-
-
-
-
diff --git a/app/code/Magento/Elasticsearch6/etc/module.xml b/app/code/Magento/Elasticsearch6/etc/module.xml
deleted file mode 100644
index 4fde2394dfbdd..0000000000000
--- a/app/code/Magento/Elasticsearch6/etc/module.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/code/Magento/Elasticsearch6/registration.php b/app/code/Magento/Elasticsearch6/registration.php
deleted file mode 100644
index 7ab10e996eb8c..0000000000000
--- a/app/code/Magento/Elasticsearch6/registration.php
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
+
diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php b/app/code/Magento/Elasticsearch7/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php
similarity index 88%
rename from app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php
rename to app/code/Magento/Elasticsearch7/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php
index bf7e770580d54..4bcfd301e6b29 100644
--- a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php
+++ b/app/code/Magento/Elasticsearch7/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php
@@ -5,14 +5,16 @@
*/
declare(strict_types=1);
-namespace Magento\Elasticsearch6\Test\Unit\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver;
+namespace Magento\Elasticsearch7\Test\Unit\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver;
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver
+ as BaseDefaultResolver;
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ConverterInterface
as FieldTypeConverterInterface;
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface
as FieldTypeResolver;
-use Magento\Elasticsearch6\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver;
+use Magento\Elasticsearch7\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
use PHPUnit\Framework\TestCase;
@@ -53,13 +55,15 @@ protected function setUp(): void
->setMethods(['convert'])
->getMockForAbstractClass();
- $this->resolver = $objectManager->getObject(
- DefaultResolver::class,
+ $baseResolver = $objectManager->getObject(
+ BaseDefaultResolver::class,
[
'fieldTypeResolver' => $this->fieldTypeResolver,
'fieldTypeConverter' => $this->fieldTypeConverter
]
);
+
+ $this->resolver = $objectManager->getObject(DefaultResolver::class, ['baseResolver' => $baseResolver]);
}
/**
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/DataProvider/Base/SuggestionsTest.php b/app/code/Magento/Elasticsearch7/Test/Unit/Model/DataProvider/Base/SuggestionsTest.php
similarity index 96%
rename from app/code/Magento/Elasticsearch/Test/Unit/Model/DataProvider/Base/SuggestionsTest.php
rename to app/code/Magento/Elasticsearch7/Test/Unit/Model/DataProvider/Base/SuggestionsTest.php
index 9f1c5db60b3d8..71bd12ccc26c3 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/Model/DataProvider/Base/SuggestionsTest.php
+++ b/app/code/Magento/Elasticsearch7/Test/Unit/Model/DataProvider/Base/SuggestionsTest.php
@@ -5,7 +5,7 @@
*/
declare(strict_types=1);
-namespace Magento\Elasticsearch\Test\Unit\Model\DataProvider\Base;
+namespace Magento\Elasticsearch7\Test\Unit\Model\DataProvider\Base;
use Elasticsearch\Common\Exceptions\BadRequest400Exception;
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface;
@@ -14,7 +14,7 @@
use Magento\Elasticsearch\Model\DataProvider\Suggestions as SuggestionsDataProvider;
use Magento\Elasticsearch\SearchAdapter\ConnectionManager;
use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver;
-use Magento\Elasticsearch6\Model\Client\Elasticsearch;
+use Magento\Elasticsearch7\Model\Client\Elasticsearch;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
use Magento\Search\Model\QueryInterface;
@@ -95,17 +95,17 @@ protected function setUp(): void
{
$this->config = $this->getMockBuilder(Config::class)
->disableOriginalConstructor()
- ->setMethods(['isElasticsearchEnabled'])
+ ->onlyMethods(['isElasticsearchEnabled'])
->getMock();
$this->queryResultFactory = $this->getMockBuilder(QueryResultFactory::class)
->disableOriginalConstructor()
- ->setMethods(['create'])
+ ->onlyMethods(['create'])
->getMock();
$this->connectionManager = $this->getMockBuilder(ConnectionManager::class)
->disableOriginalConstructor()
- ->setMethods(['getConnection'])
+ ->onlyMethods(['getConnection'])
->getMock();
$this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class)
@@ -115,7 +115,7 @@ protected function setUp(): void
$this->searchIndexNameResolver = $this
->getMockBuilder(SearchIndexNameResolver::class)
->disableOriginalConstructor()
- ->setMethods(['getIndexName'])
+ ->onlyMethods(['getIndexName'])
->getMock();
$this->storeManager = $this->getMockBuilder(StoreManager::class)
diff --git a/app/code/Magento/Elasticsearch7/composer.json b/app/code/Magento/Elasticsearch7/composer.json
index 69690fcf2aff8..89f41bf14b0dc 100644
--- a/app/code/Magento/Elasticsearch7/composer.json
+++ b/app/code/Magento/Elasticsearch7/composer.json
@@ -2,16 +2,16 @@
"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-catalog-search": "*",
+ "magento/module-search": "*"
},
"suggest": {
- "magento/module-config": "*",
- "magento/module-search": "*"
+ "magento/module-config": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/Elasticsearch7/etc/di.xml b/app/code/Magento/Elasticsearch7/etc/di.xml
index 298d93b21af59..c2df175569491 100644
--- a/app/code/Magento/Elasticsearch7/etc/di.xml
+++ b/app/code/Magento/Elasticsearch7/etc/di.xml
@@ -224,7 +224,7 @@
-
+
- elasticsearch7_server_hostname
diff --git a/app/code/Magento/Elasticsearch7/etc/module.xml b/app/code/Magento/Elasticsearch7/etc/module.xml
index c88c15b866350..93dd7a2e49c66 100644
--- a/app/code/Magento/Elasticsearch7/etc/module.xml
+++ b/app/code/Magento/Elasticsearch7/etc/module.xml
@@ -8,8 +8,8 @@
+
-
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/Model/Config/Source/SmtpAuthType.php b/app/code/Magento/Email/Model/Config/Source/SmtpAuthType.php
new file mode 100644
index 0000000000000..76a69bbe12b01
--- /dev/null
+++ b/app/code/Magento/Email/Model/Config/Source/SmtpAuthType.php
@@ -0,0 +1,30 @@
+ 'none', 'label' => __('NONE')],
+ ['value' => 'plain', 'label' => __('PLAIN')],
+ ['value' => 'login', 'label' => __('LOGIN')],
+// ['value' => 'crammd5', 'label' => __('CRAM-MD5) '], // Requires laminas/laminas-crypt
+ ];
+ }
+}
diff --git a/app/code/Magento/Email/Model/Config/Source/SmtpSslType.php b/app/code/Magento/Email/Model/Config/Source/SmtpSslType.php
new file mode 100644
index 0000000000000..f9e04759f286c
--- /dev/null
+++ b/app/code/Magento/Email/Model/Config/Source/SmtpSslType.php
@@ -0,0 +1,29 @@
+ 'none', 'label' => __('None')],
+ ['value' => 'ssl', 'label' => __('SSL')],
+ ['value' => 'tls', 'label' => __('TLS')],
+ ];
+ }
+}
diff --git a/app/code/Magento/Email/Model/Config/Source/SmtpTransportType.php b/app/code/Magento/Email/Model/Config/Source/SmtpTransportType.php
new file mode 100644
index 0000000000000..51140f2d894cc
--- /dev/null
+++ b/app/code/Magento/Email/Model/Config/Source/SmtpTransportType.php
@@ -0,0 +1,29 @@
+ 'sendmail', 'label' => __('Sendmail')],
+ ['value' => 'smtp', 'label' => __('SMTP')],
+ ];
+ }
+}
diff --git a/app/code/Magento/Email/Model/Transport.php b/app/code/Magento/Email/Model/Transport.php
index db2fd2978c7a6..b906ea4b355ef 100644
--- a/app/code/Magento/Email/Model/Transport.php
+++ b/app/code/Magento/Email/Model/Transport.php
@@ -7,6 +7,8 @@
namespace Magento\Email\Model;
+use Laminas\Mail\Transport\Smtp;
+use Laminas\Mail\Transport\SmtpOptions;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\MailException;
@@ -16,11 +18,13 @@
use Magento\Store\Model\ScopeInterface;
use Laminas\Mail\Message;
use Laminas\Mail\Transport\Sendmail;
+use Laminas\Mail\Transport\TransportInterface as LaminasTransportInterface;
use Psr\Log\LoggerInterface;
/**
* Class that responsible for filling some message data before transporting it.
* @see \Laminas\Mail\Transport\Sendmail is used for transport
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Transport implements TransportInterface
{
@@ -28,12 +32,47 @@ class Transport implements TransportInterface
* Configuration path to source of Return-Path and whether it should be set at all
* @see \Magento\Config\Model\Config\Source\Yesnocustom to possible values
*/
- const XML_PATH_SENDING_SET_RETURN_PATH = 'system/smtp/set_return_path';
+ public const XML_PATH_SENDING_SET_RETURN_PATH = 'system/smtp/set_return_path';
/**
* Configuration path for custom Return-Path email
*/
- const XML_PATH_SENDING_RETURN_PATH_EMAIL = 'system/smtp/return_path_email';
+ public const XML_PATH_SENDING_RETURN_PATH_EMAIL = 'system/smtp/return_path_email';
+
+ /**
+ * Configuration path for custom Transport
+ */
+ private const XML_PATH_TRANSPORT = 'system/smtp/transport';
+
+ /**
+ * Configuration path for SMTP Host
+ */
+ private const XML_PATH_HOST = 'system/smtp/host';
+
+ /**
+ * Configuration path for SMTP Port
+ */
+ private const XML_PATH_PORT = 'system/smtp/port';
+
+ /**
+ * Configuration path for SMTP Username
+ */
+ private const XML_PATH_USERNAME = 'system/smtp/username';
+
+ /**
+ * Configuration path for SMTP Password
+ */
+ private const XML_PATH_PASSWORD = 'system/smtp/password';
+
+ /**
+ * Configuration path for SMTP Auth type
+ */
+ private const XML_PATH_AUTH = 'system/smtp/auth';
+
+ /**
+ * Configuration path for SMTP SSL value
+ */
+ private const XML_PATH_SSL = 'system/smtp/ssl';
/**
* Whether return path should be set or no.
@@ -53,10 +92,20 @@ class Transport implements TransportInterface
private $returnPathValue;
/**
- * @var Sendmail
+ * @var ScopeConfigInterface
+ */
+ private $scopeConfig;
+
+ /**
+ * @var LaminasTransportInterface|null
*/
private $laminasTransport;
+ /**
+ * @var null|string|array|\Traversable
+ */
+ private $parameters;
+
/**
* @var MessageInterface
*/
@@ -87,12 +136,35 @@ public function __construct(
self::XML_PATH_SENDING_RETURN_PATH_EMAIL,
ScopeInterface::SCOPE_STORE
);
-
- $this->laminasTransport = new Sendmail($parameters);
$this->message = $message;
+ $this->scopeConfig = $scopeConfig;
+ $this->parameters = $parameters;
$this->logger = $logger ?: ObjectManager::getInstance()->get(LoggerInterface::class);
}
+ /**
+ * Get the LaminasTransport based on the configuration.
+ *
+ * @return LaminasTransportInterface
+ */
+ public function getTransport(): LaminasTransportInterface
+ {
+ if ($this->laminasTransport === null) {
+ $transport = $this->scopeConfig->getValue(
+ self::XML_PATH_TRANSPORT,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ if ($transport === 'smtp') {
+ $this->laminasTransport = $this->createSmtpTransport();
+ } else {
+ $this->laminasTransport = $this->createSendmailTransport();
+ }
+ }
+
+ return $this->laminasTransport;
+ }
+
/**
* @inheritdoc
*/
@@ -108,7 +180,7 @@ public function sendMessage()
$laminasMessage->setSender($fromAddressList->current()->getEmail());
}
- $this->laminasTransport->send($laminasMessage);
+ $this->getTransport()->send($laminasMessage);
} catch (\Exception $e) {
$this->logger->error($e);
throw new MailException(new Phrase('Unable to send mail. Please try again later.'), $e);
@@ -122,4 +194,75 @@ public function getMessage()
{
return $this->message;
}
+
+ /**
+ * Create a Smtp LaminasTransport.
+ *
+ * @return Smtp
+ */
+ private function createSmtpTransport(): Smtp
+ {
+ $host = $this->scopeConfig->getValue(
+ self::XML_PATH_HOST,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ $port = $this->scopeConfig->getValue(
+ self::XML_PATH_PORT,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ $username = $this->scopeConfig->getValue(
+ self::XML_PATH_USERNAME,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ $password = $this->scopeConfig->getValue(
+ self::XML_PATH_PASSWORD,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ $auth = $this->scopeConfig->getValue(
+ self::XML_PATH_AUTH,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ $ssl = $this->scopeConfig->getValue(
+ self::XML_PATH_SSL,
+ ScopeInterface::SCOPE_STORE
+ );
+
+ $options = [
+ 'name' => 'localhost',
+ 'host' => $host,
+ 'port' => $port,
+ 'connection_config' => [
+ 'username' => $username,
+ 'password' => $password,
+ ]
+ ];
+
+ if ($auth && $auth !== 'none') {
+ $options['connection_class'] = $auth;
+ }
+
+ if ($ssl && $ssl !== 'none') {
+ $options['connection_config']['ssl'] = $ssl;
+ }
+
+ $transport = new Smtp();
+ $transport->setOptions(new SmtpOptions($options));
+
+ return $transport;
+ }
+
+ /**
+ * Create a Sendmail Laminas Transport
+ *
+ * @return Sendmail
+ */
+ private function createSendmailTransport(): Sendmail
+ {
+ return new Sendmail($this->parameters);
+ }
}
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $previewWindowSize
+ $parentWindowSize
+
+
+
diff --git a/app/code/Magento/Email/Test/Unit/Model/Template/Config/XsdTest.php b/app/code/Magento/Email/Test/Unit/Model/Template/Config/XsdTest.php
index 05f9d007e903b..a47660810cb4a 100644
--- a/app/code/Magento/Email/Test/Unit/Model/Template/Config/XsdTest.php
+++ b/app/code/Magento/Email/Test/Unit/Model/Template/Config/XsdTest.php
@@ -1,10 +1,12 @@
',
[
"Element 'template', attribute 'type': " .
- "[facet 'enumeration'] The value 'invalid' is not an element of the set {'html', 'text'}.",
- "Element 'template', attribute 'type': " .
- "'invalid' is not a valid value of the atomic type 'emailTemplateFormatType'."
+ "[facet 'enumeration'] The value 'invalid' is not an element of the set {'html', 'text'}."
],
],
'node "template" without attribute "area"' => [
@@ -102,8 +102,6 @@ public function mergedXmlDataProvider()
[
"Element 'template', attribute 'area': " .
"[facet 'enumeration'] The value 'invalid' is not an element of the set {'frontend', 'adminhtml'}.",
- "Element 'template', attribute 'area': " .
- "'invalid' is not a valid value of the atomic type 'areaType'."
],
],
'node "template" with unknown attribute' => [
@@ -131,6 +129,8 @@ protected function _testXmlAgainstXsd($fixtureXml, $schemaFile, array $expectedE
$dom = new Dom($fixtureXml, $validationStateMock, [], null, null, '%message%');
$actualResult = $dom->validate($schemaFile, $actualErrors);
$this->assertEquals(empty($expectedErrors), $actualResult);
- $this->assertEquals($expectedErrors, $actualErrors);
+ foreach ($expectedErrors as $error) {
+ $this->assertContains($error, $actualErrors);
+ }
}
}
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/Email/etc/config.xml b/app/code/Magento/Email/etc/config.xml
index a9837a4f2917c..6f486c15472c2 100644
--- a/app/code/Magento/Email/etc/config.xml
+++ b/app/code/Magento/Email/etc/config.xml
@@ -28,6 +28,8 @@
localhost
25
0
+ sendmail
+ none
diff --git a/app/code/Magento/Email/view/adminhtml/templates/preview/iframeswitcher.phtml b/app/code/Magento/Email/view/adminhtml/templates/preview/iframeswitcher.phtml
index fd66acb7aca95..ce097c9aa1b0f 100644
--- a/app/code/Magento/Email/view/adminhtml/templates/preview/iframeswitcher.phtml
+++ b/app/code/Magento/Email/view/adminhtml/templates/preview/iframeswitcher.phtml
@@ -34,7 +34,7 @@ require([
$('#preview_form').trigger('submit');
});
- $('#preview_iframe').load(function() {
+ $('#preview_iframe').on('load', function() {
$(this).height($(this).contents().height());
});
});
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/Model/Carrier.php b/app/code/Magento/Fedex/Model/Carrier.php
index 64b1c67c882af..d1f29cf2f8b55 100644
--- a/app/code/Magento/Fedex/Model/Carrier.php
+++ b/app/code/Magento/Fedex/Model/Carrier.php
@@ -9,6 +9,8 @@
use Magento\Framework\App\ObjectManager;
use Magento\Framework\DataObject;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Measure\Length;
+use Magento\Framework\Measure\Weight;
use Magento\Framework\Module\Dir;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Framework\Webapi\Soap\ClientFactory;
@@ -743,6 +745,7 @@ protected function _setFreeMethodRequest($freeMethod)
$r = $this->_rawRequest;
$weight = $this->getTotalNumOfBoxes($r->getFreeMethodWeight());
$r->setWeight($weight);
+ $r->setPackages($this->createPackages((float)$r->getFreeMethodWeight(), []));
$r->setService($freeMethod);
}
@@ -1307,7 +1310,7 @@ protected function _formShipmentRequest(\Magento\Framework\DataObject $request)
$height = $packageParams->getHeight();
$width = $packageParams->getWidth();
$length = $packageParams->getLength();
- $weightUnits = $packageParams->getWeightUnits() == \Zend_Measure_Weight::POUND ? 'LB' : 'KG';
+ $weightUnits = $packageParams->getWeightUnits() == Weight::POUND ? 'LB' : 'KG';
$unitPrice = 0;
$itemsQty = 0;
$itemsDesc = [];
@@ -1454,7 +1457,7 @@ protected function _formShipmentRequest(\Magento\Framework\DataObject $request)
'Length' => $length,
'Width' => $width,
'Height' => $height,
- 'Units' => $packageParams->getDimensionUnits() == \Zend_Measure_Length::INCH ? 'IN' : 'CM',
+ 'Units' => $packageParams->getDimensionUnits() == Length::INCH ? 'IN' : 'CM',
];
}
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/Test/Mftf/ActionGroup/AllowGiftMessageForProductActionGroup.xml b/app/code/Magento/GiftMessage/Test/Mftf/ActionGroup/AllowGiftMessageForProductActionGroup.xml
new file mode 100755
index 0000000000000..6ed667ec24f0e
--- /dev/null
+++ b/app/code/Magento/GiftMessage/Test/Mftf/ActionGroup/AllowGiftMessageForProductActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ Sets Allow Gift Message For a product
+
+
+
+
+
+
+
+
+
+
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/Model/Resolver/Order/GiftMessage.php b/app/code/Magento/GiftMessageGraphQl/Model/Resolver/Order/GiftMessage.php
index 0e43be7af8b4d..0d15df8323bc3 100644
--- a/app/code/Magento/GiftMessageGraphQl/Model/Resolver/Order/GiftMessage.php
+++ b/app/code/Magento/GiftMessageGraphQl/Model/Resolver/Order/GiftMessage.php
@@ -76,7 +76,8 @@ public function resolve(
throw new GraphQlInputException(__('"id" value should be specified'));
}
- $orderId = $this->uidEncoder->decode((string) $this->uidEncoder->encode((string) $value['id']));
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ $orderId = (int)base64_decode($value['id']) ?: (int)$value['id'];
try {
$orderGiftMessage = $this->orderRepository->get($orderId);
diff --git a/app/code/Magento/GiftMessageGraphQl/Model/Resolver/Order/Item/GiftMessage.php b/app/code/Magento/GiftMessageGraphQl/Model/Resolver/Order/Item/GiftMessage.php
new file mode 100644
index 0000000000000..d103255556c31
--- /dev/null
+++ b/app/code/Magento/GiftMessageGraphQl/Model/Resolver/Order/Item/GiftMessage.php
@@ -0,0 +1,97 @@
+orderItemRepository = $itemRepository;
+ $this->giftMessageHelper = $giftMessageHelper;
+ }
+
+ /**
+ * Return information about Gift message for order item
+ *
+ * @param Field $field
+ * @param ContextInterface $context
+ * @param ResolveInfo $info
+ * @param array|null $value
+ * @param array|null $args
+ *
+ * @return array|Value|mixed
+ * @throws GraphQlInputException
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ if (!isset($value['model'])) {
+ throw new GraphQlInputException(__('"model" value must be specified'));
+ }
+
+ $orderItem = $value['model'];
+
+ if (!$this->giftMessageHelper->isMessagesAllowed('items', $orderItem)) {
+ return null;
+ }
+
+ if (!$this->giftMessageHelper->isMessagesAllowed('item', $orderItem)) {
+ return null;
+ }
+
+ try {
+ $giftItemMessage = $this->orderItemRepository->get($orderItem->getOrderId(), $orderItem->getItemId());
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__('Can\'t load message for order item'));
+ }
+
+ if ($giftItemMessage === null) {
+ return null;
+ }
+
+ return [
+ 'to' => $giftItemMessage->getRecipient() ?? '',
+ 'from' => $giftItemMessage->getSender() ?? '',
+ 'message'=> $giftItemMessage->getMessage() ?? ''
+ ];
+ }
+}
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/GiftMessageGraphQl/etc/schema.graphqls b/app/code/Magento/GiftMessageGraphQl/etc/schema.graphqls
index ad18054abca13..74493911a3c02 100644
--- a/app/code/Magento/GiftMessageGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/GiftMessageGraphQl/etc/schema.graphqls
@@ -45,3 +45,7 @@ type SalesItemInterface {
type CustomerOrder {
gift_message: GiftMessage @resolver (class: "\\Magento\\GiftMessageGraphQl\\Model\\Resolver\\Order\\GiftMessage") @doc(description: "The entered gift message for the order")
}
+
+interface OrderItemInterface {
+ gift_message: GiftMessage @resolver(class: "\\Magento\\GiftMessageGraphQl\\Model\\Resolver\\Order\\Item\\GiftMessage") @doc(description: "The selected gift message for the order item")
+}
diff --git a/app/code/Magento/GoogleAdwords/Model/Config/Backend/Color.php b/app/code/Magento/GoogleAdwords/Model/Config/Backend/Color.php
index 7b96795da2192..912109ff03e57 100644
--- a/app/code/Magento/GoogleAdwords/Model/Config/Backend/Color.php
+++ b/app/code/Magento/GoogleAdwords/Model/Config/Backend/Color.php
@@ -1,22 +1,22 @@
-
-
-getHelper();
+
$scriptString = <<
+= /** @noEscape */ $secureRenderer->renderTag('script', [], $scriptString, false); ?>
+
-
+
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/Model/Import/Product/Type/Grouped.php b/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped.php
index 4907126b941fe..68125242263b1 100644
--- a/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped.php
+++ b/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped.php
@@ -143,7 +143,7 @@ public function saveData()
}
}
}
- $this->links->saveLinksData($linksData);
+ $this->links->saveLinksData($linksData, $this->_entityModel);
}
return $this;
}
diff --git a/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped/Links.php b/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped/Links.php
index 104e69f2f92e0..23c9d519987cd 100644
--- a/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped/Links.php
+++ b/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped/Links.php
@@ -5,6 +5,7 @@
*/
namespace Magento\GroupedImportExport\Model\Import\Product\Type\Grouped;
+use Magento\CatalogImportExport\Model\Import\Product as ProductImport;
use Magento\Framework\App\ResourceConnection;
/**
@@ -24,6 +25,8 @@ class Links
/**
* @var \Magento\ImportExport\Model\ImportFactory
+ * @deprecated
+ * @see no longer used
*/
protected $importFactory;
@@ -55,16 +58,19 @@ public function __construct(
}
/**
+ * Saves the linksData to database
+ *
* @param array $linksData
+ * @param ProductImport $productImport
* @return void
*/
- public function saveLinksData($linksData)
+ public function saveLinksData(array $linksData, ProductImport $productImport)
{
$mainTable = $this->productLink->getMainTable();
$relationTable = $this->productLink->getTable('catalog_product_relation');
// save links and relations
if ($linksData['product_ids']) {
- $this->deleteOldLinks(array_keys($linksData['product_ids']));
+ $this->deleteOldLinks(array_keys($linksData['product_ids']), $productImport);
$mainData = [];
foreach ($linksData['relation'] as $productData) {
$mainData[] = [
@@ -76,7 +82,6 @@ public function saveLinksData($linksData)
$this->connection->insertOnDuplicate($mainTable, $mainData);
$this->connection->insertOnDuplicate($relationTable, $linksData['relation']);
}
-
$attributes = $this->getAttributes();
// save positions and default quantity
if ($linksData['attr_product_ids']) {
@@ -107,13 +112,16 @@ public function saveLinksData($linksData)
}
/**
+ * Deletes all the product links in database that are linked to productIds
+ *
* @param array $productIds
+ * @param ProductImport $productImport
* @throws \Magento\Framework\Exception\LocalizedException
* @return void
*/
- protected function deleteOldLinks($productIds)
+ protected function deleteOldLinks($productIds, ProductImport $productImport)
{
- if ($this->getBehavior() != \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND) {
+ if ($this->getBehavior($productImport) != \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND) {
$this->connection->delete(
$this->productLink->getMainTable(),
$this->connection->quoteInto(
@@ -125,6 +133,8 @@ protected function deleteOldLinks($productIds)
}
/**
+ * Gets all the attributes from database for the Grouped Link Type
+ *
* @return array
*/
public function getAttributes()
@@ -145,6 +155,8 @@ public function getAttributes()
}
/**
+ * Returns the integer id for Link Type
+ *
* @return int
*/
protected function getLinkTypeId()
@@ -155,12 +167,15 @@ protected function getLinkTypeId()
/**
* Retrieve model behavior
*
+ * @param ProductImport $productImport
* @return string
*/
- protected function getBehavior()
+ protected function getBehavior(ProductImport $productImport)
{
if ($this->behavior === null) {
- $this->behavior = $this->importFactory->create()->getDataSourceModel()->getBehavior();
+ $ids = $productImport->getIds();
+ $dataSourceModel = $productImport->getDataSourceModel();
+ $this->behavior = $dataSourceModel->getBehavior($ids);
}
return $this->behavior;
}
diff --git a/app/code/Magento/GroupedImportExport/Test/Unit/Model/Import/Product/Type/Grouped/LinksTest.php b/app/code/Magento/GroupedImportExport/Test/Unit/Model/Import/Product/Type/Grouped/LinksTest.php
index 983e5b77e3e9a..4aa2d85aa53a2 100644
--- a/app/code/Magento/GroupedImportExport/Test/Unit/Model/Import/Product/Type/Grouped/LinksTest.php
+++ b/app/code/Magento/GroupedImportExport/Test/Unit/Model/Import/Product/Type/Grouped/LinksTest.php
@@ -9,12 +9,12 @@
namespace Magento\GroupedImportExport\Test\Unit\Model\Import\Product\Type\Grouped;
use Magento\Catalog\Model\ResourceModel\Product\Link;
+use Magento\CatalogImportExport\Model\Import\Product as ProductImport;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Adapter\Pdo\Mysql;
use Magento\Framework\DB\Select;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
use Magento\GroupedImportExport\Model\Import\Product\Type\Grouped\Links;
-use Magento\ImportExport\Model\Import;
use Magento\ImportExport\Model\ImportFactory;
use Magento\ImportExport\Model\ResourceModel\Import\Data;
use PHPUnit\Framework\MockObject\MockObject;
@@ -28,20 +28,20 @@ class LinksTest extends TestCase
/** @var ObjectManagerHelper */
protected $objectManagerHelper;
- /** @var Link|MockObject */
+ /** @var Link&MockObject */
protected $link;
- /** @var ResourceConnection|MockObject */
+ /** @var ResourceConnection&MockObject */
protected $resource;
/** @var Mysql */
protected $connection;
- /** @var ImportFactory|MockObject */
+ /** @var ImportFactory&MockObject */
protected $importFactory;
- /** @var Import|MockObject */
- protected $import;
+ /** @var ProductImport&MockObject */
+ protected $productImport;
protected function setUp(): void
{
@@ -52,11 +52,7 @@ protected function setUp(): void
->expects($this->once())
->method('getConnection')
->willReturn($this->connection);
-
- $this->import = $this->createMock(Import::class);
$this->importFactory = $this->createPartialMock(ImportFactory::class, ['create']);
- $this->importFactory->expects($this->any())->method('create')->willReturn($this->import);
-
$this->objectManagerHelper = new ObjectManagerHelper($this);
$this->links = $this->objectManagerHelper->getObject(
Links::class,
@@ -66,6 +62,8 @@ protected function setUp(): void
'importFactory' => $this->importFactory
]
);
+ $this->productImport = $this->createMock(ProductImport::class);
+ $this->productImport->expects($this->any())->method('getIds')->willReturn([]);
}
/**
@@ -95,7 +93,7 @@ public function testSaveLinksDataNoProductsAttrs($linksData)
$attributes = $this->attributesDataProvider();
$this->processAttributeGetter($attributes[2]['dbAttributes']);
$this->connection->expects($this->exactly(2))->method('insertOnDuplicate');
- $this->links->saveLinksData($linksData);
+ $this->links->saveLinksData($linksData, $this->productImport);
}
/**
@@ -125,8 +123,7 @@ public function testSaveLinksDataWithProductsAttrs($linksData)
$this->link->expects($this->exactly(2))->method('getAttributeTypeTable')->willReturn(
'table_name'
);
-
- $this->links->saveLinksData($linksData);
+ $this->links->saveLinksData($linksData, $this->productImport);
}
/**
@@ -197,6 +194,6 @@ protected function processBehaviorGetter($behavior)
{
$dataSource = $this->createMock(Data::class);
$dataSource->expects($this->once())->method('getBehavior')->willReturn($behavior);
- $this->import->expects($this->once())->method('getDataSourceModel')->willReturn($dataSource);
+ $this->productImport->expects($this->any())->method('getDataSourceModel')->willReturn($dataSource);
}
}
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/Test/Fixture/AddProductToCart.php b/app/code/Magento/GroupedProduct/Test/Fixture/AddProductToCart.php
new file mode 100644
index 0000000000000..555dc4f076c90
--- /dev/null
+++ b/app/code/Magento/GroupedProduct/Test/Fixture/AddProductToCart.php
@@ -0,0 +1,93 @@
+productRepository = $productRepository;
+ $this->productType = $productType;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @param array $data Parameters
+ * $data['child_products'] can be supplied in following formats:
+ * - ["$product1.id$", "$product2.id$"]
+ * - [{"product_id":"$product1.id$","qty":1}, {"product_id":"$product2.id$","qty":1}]
+ *
+ * $data = [
+ * 'cart_id' => (int) Cart ID. Required.
+ * 'product_id' => (int) Product ID. Required.
+ * 'child_products' => (array) array of associated products configuration. Required.
+ * 'qty' => (int) Quantity. Optional. Default: 1.
+ * ]
+ *
+ */
+ public function apply(array $data = []): ?DataObject
+ {
+ $buyRequest = [
+ 'super_group' => [],
+ 'qty' => $data['qty'] ?? 1,
+ ];
+ $bundleProduct = $this->productRepository->getById($data['product_id']);
+ $default = [];
+ foreach ($this->productType->getAssociatedProducts($bundleProduct) as $childProduct) {
+ $default[$childProduct->getId()] = $childProduct->getQty();
+ }
+ foreach ($data['child_products'] as $item) {
+ if (is_array($item)) {
+ $productId = (int) $item['product_id'];
+ $qty = $item['qty'] ?? null;
+ } else {
+ $productId = (int) $item;
+ $qty = null;
+ }
+ $qty ??= $default[$productId];
+ $buyRequest['super_group'][$productId] = $qty;
+ }
+ $buyRequest['super_group'] = empty($data['child_products'])
+ ? $default
+ : array_intersect_key($buyRequest['super_group'], $default);
+ return parent::apply(
+ [
+ 'cart_id' => $data['cart_id'],
+ 'product_id' => $data['product_id'],
+ 'buy_request' => $buyRequest
+ ]
+ );
+ }
+}
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByDescriptionTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByDescriptionTest.xml
index 313ea27f00d5c..c8f70e3e439e6 100644
--- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByDescriptionTest.xml
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByDescriptionTest.xml
@@ -16,7 +16,7 @@
-
+
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByNameTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByNameTest.xml
index 42cd5372723c3..1764bf1e101ac 100644
--- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByNameTest.xml
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByNameTest.xml
@@ -16,7 +16,7 @@
-
+
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByPriceTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByPriceTest.xml
index 56de757cf36ae..4aa53b6b644e9 100644
--- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByPriceTest.xml
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByPriceTest.xml
@@ -16,7 +16,7 @@
-
+
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByShortDescriptionTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByShortDescriptionTest.xml
index a374f7ea7bb69..3699e3d84d311 100644
--- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByShortDescriptionTest.xml
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest/AdvanceCatalogSearchGroupedProductByShortDescriptionTest.xml
@@ -16,7 +16,7 @@
-
+
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/StorefrontAdvanceCatalogSearchGroupedProductBySkuWithHyphenTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/StorefrontAdvanceCatalogSearchGroupedProductBySkuWithHyphenTest.xml
index 545b9c9ec7f06..584851170e9e1 100644
--- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/StorefrontAdvanceCatalogSearchGroupedProductBySkuWithHyphenTest.xml
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/StorefrontAdvanceCatalogSearchGroupedProductBySkuWithHyphenTest.xml
@@ -17,7 +17,7 @@
-
+
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/GroupedProductGraphQl/etc/graphql/di.xml b/app/code/Magento/GroupedProductGraphQl/etc/graphql/di.xml
index 84eab3bf132a2..86940a401f105 100644
--- a/app/code/Magento/GroupedProductGraphQl/etc/graphql/di.xml
+++ b/app/code/Magento/GroupedProductGraphQl/etc/graphql/di.xml
@@ -53,4 +53,17 @@
+
+
+ Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Deferred\ChildProduct
+
+
+
+
+
+ Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Deferred\ChildProduct
+
+
+
diff --git a/app/code/Magento/GroupedProductGraphQl/etc/schema.graphqls b/app/code/Magento/GroupedProductGraphQl/etc/schema.graphqls
index 1df309fe105e7..6af830556edb8 100644
--- a/app/code/Magento/GroupedProductGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/GroupedProductGraphQl/etc/schema.graphqls
@@ -8,7 +8,7 @@ type GroupedProduct implements ProductInterface, RoutableInterface, PhysicalProd
type GroupedProductItem @doc(description: "Contains information about an individual grouped product item."){
qty: Float @doc(description: "The quantity of this grouped product item.")
position: Int @doc(description: "The relative position of this item compared to the other group items.")
- product: ProductInterface @doc(description: "Details about this product option.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product")
+ product: ProductInterface @doc(description: "Details about this product option.") @resolver(class: "Magento\\GroupedProductGraphQl\\Model\\Resolver\\GroupedItem\\Product")
}
type GroupedProductWishlistItem implements WishlistItemInterface @doc(description: "A grouped product wish list item.") {
diff --git a/app/code/Magento/ImportExport/Api/Data/LocalizedExportInfoInterface.php b/app/code/Magento/ImportExport/Api/Data/LocalizedExportInfoInterface.php
new file mode 100644
index 0000000000000..ad3aef51a9ea3
--- /dev/null
+++ b/app/code/Magento/ImportExport/Api/Data/LocalizedExportInfoInterface.php
@@ -0,0 +1,29 @@
+
*/
namespace Magento\ImportExport\Block\Adminhtml\Export;
diff --git a/app/code/Magento/ImportExport/Block/Adminhtml/Export/Edit/Form.php b/app/code/Magento/ImportExport/Block/Adminhtml/Export/Edit/Form.php
index de9c80d0c9dcd..8ebd23774e971 100644
--- a/app/code/Magento/ImportExport/Block/Adminhtml/Export/Edit/Form.php
+++ b/app/code/Magento/ImportExport/Block/Adminhtml/Export/Edit/Form.php
@@ -6,8 +6,6 @@
/**
* Export edit form block
- *
- * @author Magento Core Team
*/
namespace Magento\ImportExport\Block\Adminhtml\Export\Edit;
diff --git a/app/code/Magento/ImportExport/Block/Adminhtml/History.php b/app/code/Magento/ImportExport/Block/Adminhtml/History.php
index e17a59b2f5339..f3bc5f8e2f06a 100644
--- a/app/code/Magento/ImportExport/Block/Adminhtml/History.php
+++ b/app/code/Magento/ImportExport/Block/Adminhtml/History.php
@@ -9,7 +9,6 @@
* Adminhtml import history page content block
*
* @api
- * @author Magento Core Team
* @since 100.0.2
*/
class History extends \Magento\Backend\Block\Widget\Grid\Container
diff --git a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit.php b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit.php
index 23725879e0904..3fabd57cd78eb 100644
--- a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit.php
+++ b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit.php
@@ -6,8 +6,6 @@
/**
* Import edit block
- *
- * @author Magento Core Team
*/
namespace Magento\ImportExport\Block\Adminhtml\Import;
diff --git a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Before.php b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Before.php
index f121a84c67699..cf337289b8634 100644
--- a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Before.php
+++ b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Before.php
@@ -6,8 +6,6 @@
/**
* Block before edit form
- *
- * @author Magento Core Team
*/
namespace Magento\ImportExport\Block\Adminhtml\Import\Edit;
diff --git a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php
index b24cad16897ac..f4ef5ec432bac 100644
--- a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php
+++ b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php
@@ -11,8 +11,6 @@
/**
* Import edit form block
- *
- * @author Magento Core Team
*/
class Form extends \Magento\Backend\Block\Widget\Form\Generic
{
@@ -154,7 +152,7 @@ protected function _prepareForm()
'required' => true,
'disabled' => true,
'value' => 10,
- 'class' => $behaviorCode . ' validate-number validate-greater-than-zero input-text',
+ 'class' => $behaviorCode . ' validate-number validate-zero-or-greater input-text',
'note' => __(
'Please specify number of errors to halt import process'
),
@@ -211,7 +209,6 @@ protected function _prepareForm()
);
$fieldsets[$behaviorCode] = $fieldset;
}
-
// fieldset for file uploading
$fieldset = $form->addFieldset(
'upload_file_fieldset',
@@ -257,11 +254,19 @@ protected function _prepareForm()
),
]
);
+ $fieldset->addField(
+ Import::FIELD_IMPORT_IDS,
+ 'hidden',
+ [
+ 'name' => Import::FIELD_IMPORT_IDS,
+ 'label' => __('Import id'),
+ 'title' => __('Import id'),
+ 'value' => '',
+ ]
+ );
$fieldsets['upload'] = $fieldset;
-
$form->setUseContainer(true);
$this->setForm($form);
-
return parent::_prepareForm();
}
diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php
index 18b319f7a25c9..be0aa6a1426b4 100644
--- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php
+++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php
@@ -9,9 +9,12 @@
use Magento\Backend\App\Action\Context;
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\Response\Http\FileFactory;
use Magento\Framework\Controller\ResultFactory;
+use Magento\Framework\Locale\ResolverInterface;
use Magento\Framework\MessageQueue\PublisherInterface;
+use Magento\Framework\Session\SessionManagerInterface;
use Magento\ImportExport\Controller\Adminhtml\Export as ExportController;
use Magento\ImportExport\Model\Export as ExportModel;
use Magento\ImportExport\Model\Export\Entity\ExportInfoFactory;
@@ -27,7 +30,7 @@ class Export extends ExportController implements HttpPostActionInterface
protected $fileFactory;
/**
- * @var \Magento\Framework\Session\SessionManagerInterface
+ * @var SessionManagerInterface
*/
private $sessionManager;
@@ -41,29 +44,37 @@ class Export extends ExportController implements HttpPostActionInterface
*/
private $exportInfoFactory;
+ /**
+ * @var ResolverInterface
+ */
+ private $localeResolver;
+
/**
* @param Context $context
* @param FileFactory $fileFactory
- * @param \Magento\Framework\Session\SessionManagerInterface|null $sessionManager
+ * @param SessionManagerInterface|null $sessionManager
* @param PublisherInterface|null $publisher
* @param ExportInfoFactory|null $exportInfoFactory
+ * @param ResolverInterface|null $localeResolver
*/
public function __construct(
Context $context,
FileFactory $fileFactory,
- \Magento\Framework\Session\SessionManagerInterface $sessionManager = null,
+ SessionManagerInterface $sessionManager = null,
PublisherInterface $publisher = null,
- ExportInfoFactory $exportInfoFactory = null
+ ExportInfoFactory $exportInfoFactory = null,
+ ResolverInterface $localeResolver = null
) {
$this->fileFactory = $fileFactory;
- $this->sessionManager = $sessionManager ?: \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Framework\Session\SessionManagerInterface::class);
- $this->messagePublisher = $publisher ?: \Magento\Framework\App\ObjectManager::getInstance()
- ->get(PublisherInterface::class);
- $this->exportInfoFactory = $exportInfoFactory ?:
- \Magento\Framework\App\ObjectManager::getInstance()->get(
- ExportInfoFactory::class
- );
+ $this->sessionManager = $sessionManager
+ ?? ObjectManager::getInstance()->get(SessionManagerInterface::class);
+ $this->messagePublisher = $publisher
+ ?? ObjectManager::getInstance()->get(PublisherInterface::class);
+ $this->exportInfoFactory = $exportInfoFactory
+ ?? ObjectManager::getInstance()->get(ExportInfoFactory::class);
+ $this->localeResolver = $localeResolver
+ ?? ObjectManager::getInstance()->get(ResolverInterface::class);
+
parent::__construct($context);
}
@@ -87,7 +98,8 @@ public function execute()
$params['file_format'],
$params['entity'],
$params['export_filter'],
- $params['skip_attr']
+ $params['skip_attr'],
+ $this->localeResolver->getLocale()
);
$this->messagePublisher->publish('import_export.export', $dataObject);
diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php
index 4be73fe384ae0..c388851edcbe4 100644
--- a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php
+++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php
@@ -42,15 +42,15 @@ public function execute()
//phpcs:disable Magento2.Security.Superglobal
if ($data) {
// common actions
- $resultBlock->addAction(
- 'show',
- 'import_validation_container'
- );
-
+ $resultBlock->addAction('show', 'import_validation_container');
$import = $this->getImport()->setData($data);
try {
$source = $import->uploadFileAndGetSource();
$this->processValidationResult($import->validateSource($source), $resultBlock);
+ $ids = $import->getValidatedIds();
+ if (count($ids) > 0) {
+ $resultBlock->addAction('value', Import::FIELD_IMPORT_IDS, $ids);
+ }
} catch (\Magento\Framework\Exception\LocalizedException $e) {
$resultBlock->addError($e->getMessage());
} catch (\Exception $e) {
@@ -117,7 +117,6 @@ private function processValidationResult($validationResult, $resultBlock)
* Provides import model.
*
* @return Import
- * @deprecated 100.1.0
*/
private function getImport()
{
diff --git a/app/code/Magento/ImportExport/Cron/ImportDataTableCleanup.php b/app/code/Magento/ImportExport/Cron/ImportDataTableCleanup.php
new file mode 100644
index 0000000000000..d563e68ddbf8f
--- /dev/null
+++ b/app/code/Magento/ImportExport/Cron/ImportDataTableCleanup.php
@@ -0,0 +1,37 @@
+dataSourceModel = $importData;
+ }
+
+ /**
+ * Remove all rows from importexport_importdata table
+ *
+ * @return void
+ */
+ public function execute()
+ {
+ $this->dataSourceModel->cleanProcessedBunches();
+ }
+}
diff --git a/app/code/Magento/ImportExport/Model/AbstractModel.php b/app/code/Magento/ImportExport/Model/AbstractModel.php
index 4010f3bfcae4c..ea263f219837d 100644
--- a/app/code/Magento/ImportExport/Model/AbstractModel.php
+++ b/app/code/Magento/ImportExport/Model/AbstractModel.php
@@ -9,8 +9,6 @@
/**
* Operation abstract class
- *
- * @author Magento Core Team
*/
abstract class AbstractModel extends \Magento\Framework\DataObject
{
@@ -62,9 +60,11 @@ public function __construct(
/**
* Log debug data to file.
+ *
* Log file dir: var/log/import_export/%Y/%m/%d/%time%_%operation_type%_%entity_type%.log
*
* @param mixed $debugData
+ *
* @return $this
*/
public function addLogComment($debugData)
@@ -91,9 +91,11 @@ public function getFormatedLogTrace()
{
$trace = '';
$lineNumber = 1;
+
foreach ($this->_logTrace as &$info) {
- $trace .= $lineNumber++ . ': ' . $info . "\n";
+ $trace .= ($lineNumber++) . ': ' . $info . "\n";
}
+
return $trace;
}
}
diff --git a/app/code/Magento/ImportExport/Model/Export/Consumer.php b/app/code/Magento/ImportExport/Model/Export/Consumer.php
index 55ffbb9ab213d..e83f508037da1 100644
--- a/app/code/Magento/ImportExport/Model/Export/Consumer.php
+++ b/app/code/Magento/ImportExport/Model/Export/Consumer.php
@@ -11,8 +11,9 @@
use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem;
+use Magento\Framework\Locale\ResolverInterface;
+use Magento\ImportExport\Api\Data\LocalizedExportInfoInterface;
use Magento\ImportExport\Api\ExportManagementInterface;
-use Magento\ImportExport\Api\Data\ExportInfoInterface;
use Magento\Framework\Notification\NotifierInterface;
/**
@@ -40,33 +41,46 @@ class Consumer
*/
private $filesystem;
+ /**
+ * @var ResolverInterface
+ */
+ private $localeResolver;
+
/**
* Consumer constructor.
* @param \Psr\Log\LoggerInterface $logger
* @param ExportManagementInterface $exportManager
* @param Filesystem $filesystem
* @param NotifierInterface $notifier
+ * @param ResolverInterface $localeResolver
*/
public function __construct(
\Psr\Log\LoggerInterface $logger,
ExportManagementInterface $exportManager,
Filesystem $filesystem,
- NotifierInterface $notifier
+ NotifierInterface $notifier,
+ ResolverInterface $localeResolver
) {
$this->logger = $logger;
$this->exportManager = $exportManager;
$this->filesystem = $filesystem;
$this->notifier = $notifier;
+ $this->localeResolver = $localeResolver;
}
/**
* Consumer logic.
*
- * @param ExportInfoInterface $exportInfo
+ * @param LocalizedExportInfoInterface $exportInfo
* @return void
*/
- public function process(ExportInfoInterface $exportInfo)
+ public function process(LocalizedExportInfoInterface $exportInfo)
{
+ $currentLocale = $this->localeResolver->getLocale();
+ if ($exportInfo->getLocale()) {
+ $this->localeResolver->setLocale($exportInfo->getLocale());
+ }
+
try {
$data = $this->exportManager->export($exportInfo);
$fileName = $exportInfo->getFileName();
@@ -83,6 +97,8 @@ public function process(ExportInfoInterface $exportInfo)
__('Error during export process occurred. Please check logs for detail')
);
$this->logger->critical('Something went wrong while export process. ' . $exception->getMessage());
+ } finally {
+ $this->localeResolver->setLocale($currentLocale);
}
}
}
diff --git a/app/code/Magento/ImportExport/Model/Export/Entity/AbstractEav.php b/app/code/Magento/ImportExport/Model/Export/Entity/AbstractEav.php
index 97c3e0b81bdbd..d9dd98bc54cd6 100644
--- a/app/code/Magento/ImportExport/Model/Export/Entity/AbstractEav.php
+++ b/app/code/Magento/ImportExport/Model/Export/Entity/AbstractEav.php
@@ -14,7 +14,7 @@
/**
* Export EAV entity abstract model
*
- * phpcs:disable Magento2.Classes.AbstractApi
+ * phpcs:ignore Magento2.Classes.AbstractApi
* @api
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -130,7 +130,6 @@ protected function _prepareEntityCollection(AbstractCollection $collection)
* @param AbstractCollection $collection
* @return AbstractCollection
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * phpcs:disable Generic.Metrics.NestingLevel
*/
public function filterEntityCollection(AbstractCollection $collection)
{
@@ -167,13 +166,11 @@ public function filterEntityCollection(AbstractCollection $collection)
} elseif (Export::FILTER_TYPE_MULTISELECT == $attributeFilterType) {
if (is_array($exportFilter[$attributeCode])) {
array_filter($exportFilter[$attributeCode]);
- if (!empty($exportFilter[$attributeCode])) {
- foreach ($exportFilter[$attributeCode] as $val) {
- $collection->addAttributeToFilter(
- $attributeCode,
- ['finset' => $val]
- );
- }
+ foreach ($exportFilter[$attributeCode] as $val) {
+ $collection->addAttributeToFilter(
+ $attributeCode,
+ ['finset' => $val]
+ );
}
}
} elseif (Export::FILTER_TYPE_INPUT == $attributeFilterType) {
@@ -189,11 +186,11 @@ public function filterEntityCollection(AbstractCollection $collection)
$to = array_shift($exportFilter[$attributeCode]);
if (is_scalar($from) && !empty($from)) {
- $date = (new \DateTime($from))->format('m/d/Y');
+ $date = $this->_localeDate->date($from);
$collection->addAttributeToFilter($attributeCode, ['from' => $date, 'date' => true]);
}
if (is_scalar($to) && !empty($to)) {
- $date = (new \DateTime($to))->format('m/d/Y');
+ $date = $this->_localeDate->date($to);
$collection->addAttributeToFilter($attributeCode, ['to' => $date, 'date' => true]);
}
}
@@ -214,7 +211,6 @@ public function filterEntityCollection(AbstractCollection $collection)
}
return $collection;
}
- // phpcs:enable Generic.Metrics.NestingLevel
/**
* Add not skipped attributes to select
diff --git a/app/code/Magento/ImportExport/Model/Export/Entity/ExportInfo.php b/app/code/Magento/ImportExport/Model/Export/Entity/ExportInfo.php
index a5d5d63e4f8da..b2b6fe01dc1be 100644
--- a/app/code/Magento/ImportExport/Model/Export/Entity/ExportInfo.php
+++ b/app/code/Magento/ImportExport/Model/Export/Entity/ExportInfo.php
@@ -7,12 +7,12 @@
namespace Magento\ImportExport\Model\Export\Entity;
-use Magento\ImportExport\Api\Data\ExtendedExportInfoInterface;
+use Magento\ImportExport\Api\Data\LocalizedExportInfoInterface;
/**
* Class ExportInfo implementation for ExportInfoInterface.
*/
-class ExportInfo implements ExtendedExportInfoInterface
+class ExportInfo implements LocalizedExportInfoInterface
{
/**
* @var string
@@ -44,6 +44,11 @@ class ExportInfo implements ExtendedExportInfoInterface
*/
private $skipAttr;
+ /**
+ * @var string
+ */
+ private $locale;
+
/**
* @inheritdoc
*/
@@ -133,10 +138,29 @@ public function getSkipAttr()
}
/**
- * @inheritdoc
+ * Set skipped attributes
+ *
+ * @param string $skipAttr
+ * @return void
*/
public function setSkipAttr($skipAttr)
{
$this->skipAttr = $skipAttr;
}
+
+ /**
+ * @inheritdoc
+ */
+ public function getLocale(): ?string
+ {
+ return $this->locale;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setLocale(string $locale): void
+ {
+ $this->locale = $locale;
+ }
}
diff --git a/app/code/Magento/ImportExport/Model/Export/Entity/ExportInfoFactory.php b/app/code/Magento/ImportExport/Model/Export/Entity/ExportInfoFactory.php
index 32c989acb661c..93251136f7510 100644
--- a/app/code/Magento/ImportExport/Model/Export/Entity/ExportInfoFactory.php
+++ b/app/code/Magento/ImportExport/Model/Export/Entity/ExportInfoFactory.php
@@ -22,8 +22,6 @@
class ExportInfoFactory
{
/**
- * Object Manager
- *
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;
@@ -54,7 +52,6 @@ class ExportInfoFactory
private $logger;
/**
- * ExportInfoFactory constructor.
* @param ObjectManagerInterface $objectManager
* @param ConfigInterface $exportConfig
* @param Factory $entityFactory
@@ -85,10 +82,11 @@ public function __construct(
* @param string $entity
* @param string $exportFilter
* @param array $skipAttr
+ * @param string|null $locale
* @return ExportInfoInterface
* @throws \Magento\Framework\Exception\LocalizedException
*/
- public function create($fileFormat, $entity, $exportFilter, $skipAttr)
+ public function create($fileFormat, $entity, $exportFilter, $skipAttr = [], ?string $locale = null)
{
$writer = $this->getWriter($fileFormat);
$entityAdapter = $this->getEntityAdapter(
@@ -107,6 +105,9 @@ public function create($fileFormat, $entity, $exportFilter, $skipAttr)
$exportInfo->setEntity($entity);
$exportInfo->setFileFormat($fileFormat);
$exportInfo->setContentType($writer->getContentType());
+ if ($locale) {
+ $exportInfo->setLocale($locale);
+ }
return $exportInfo;
}
@@ -137,7 +138,7 @@ private function generateFileName($entity, $entityAdapter, $fileExtensions)
*
* @param string $entity
* @param string $fileFormat
- * @param string $exportFilter
+ * @param array $exportFilter
* @param array $skipAttr
* @param string $contentType
* @return \Magento\ImportExport\Model\Export\AbstractEntity|AbstractEntity
diff --git a/app/code/Magento/ImportExport/Model/Import.php b/app/code/Magento/ImportExport/Model/Import.php
index d087ef724788b..aa3af449237f9 100644
--- a/app/code/Magento/ImportExport/Model/Import.php
+++ b/app/code/Magento/ImportExport/Model/Import.php
@@ -18,6 +18,7 @@
use Magento\Framework\HTTP\Adapter\FileTransferFactory;
use Magento\Framework\Indexer\IndexerRegistry;
use Magento\Framework\Math\Random;
+use Magento\Framework\Message\ManagerInterface;
use Magento\Framework\Stdlib\DateTime\DateTime;
use Magento\ImportExport\Helper\Data as DataHelper;
use Magento\ImportExport\Model\Export\Adapter\CsvFactory;
@@ -27,13 +28,13 @@
use Magento\ImportExport\Model\Import\ConfigInterface;
use Magento\ImportExport\Model\Import\Entity\AbstractEntity;
use Magento\ImportExport\Model\Import\Entity\Factory;
+use Magento\ImportExport\Model\Import\EntityInterface;
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingError;
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
-use Magento\Framework\Message\ManagerInterface;
use Magento\ImportExport\Model\ResourceModel\Import\Data;
use Magento\ImportExport\Model\Source\Import\AbstractBehavior;
use Magento\ImportExport\Model\Source\Import\Behavior\Factory as BehaviorFactory;
-use Magento\MediaStorage\Model\File\Uploader;
+use Magento\ImportExport\Model\Source\Upload;
use Magento\MediaStorage\Model\File\UploaderFactory;
use Psr\Log\LoggerInterface;
@@ -97,6 +98,11 @@ class Import extends AbstractModel
*/
public const FIELD_EMPTY_ATTRIBUTE_VALUE_CONSTANT = '_import_empty_attribute_value_constant';
+ /**
+ * Id of the `importexport_importdata` row after validation.
+ */
+ public const FIELD_IMPORT_IDS = '_import_ids';
+
/**
* Allow multiple values wrapping in double quotes for additional attributes.
*/
@@ -117,11 +123,12 @@ class Import extends AbstractModel
public const IMPORT_DIR = 'import/';
/**
- * @var AbstractEntity|ImportAbstractEntity
+ * @var EntityInterface
*/
protected $_entityAdapter;
/**
+ * @Deprecated Property isn't used
* @var DataHelper
*/
protected $_importExportData = null;
@@ -152,6 +159,7 @@ class Import extends AbstractModel
protected $_csvFactory;
/**
+ * @Deprecated Property isn't used
* @var FileTransferFactory
*/
protected $_httpFactory;
@@ -192,10 +200,16 @@ class Import extends AbstractModel
private $messageManager;
/**
+ * @Deprecated Property isn't used
* @var Random
*/
private $random;
+ /**
+ * @var Upload
+ */
+ private $upload;
+
/**
* @param LoggerInterface $logger
* @param Filesystem $filesystem
@@ -214,6 +228,7 @@ class Import extends AbstractModel
* @param array $data
* @param ManagerInterface|null $messageManager
* @param Random|null $random
+ * @param Upload|null $upload
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -233,7 +248,8 @@ public function __construct(
DateTime $localeDate,
array $data = [],
ManagerInterface $messageManager = null,
- Random $random = null
+ Random $random = null,
+ Upload $upload = null
) {
$this->_importExportData = $importExportData;
$this->_coreConfig = $coreConfig;
@@ -252,6 +268,8 @@ public function __construct(
->get(ManagerInterface::class);
$this->random = $random ?: ObjectManager::getInstance()
->get(Random::class);
+ $this->upload = $upload ?: ObjectManager::getInstance()
+ ->get(Upload::class);
parent::__construct($logger, $filesystem, $data);
}
@@ -259,7 +277,7 @@ public function __construct(
* Create instance of entity adapter and return it
*
* @throws LocalizedException
- * @return AbstractEntity|ImportAbstractEntity
+ * @return EntityInterface
*/
protected function _getEntityAdapter()
{
@@ -303,6 +321,8 @@ protected function _getEntityAdapter()
/**
* Returns source adapter object.
*
+ * @Deprecated
+ * @see \Magento\ImportExport\Model\Import\Source\Factory::create()
* @param string $sourceFile Full path to source file
* @return AbstractSource
* @throws FileSystemException
@@ -460,8 +480,17 @@ public function getWorkingDir()
*/
public function importSource()
{
- $this->setData('entity', $this->getDataSourceModel()->getEntityTypeCode());
- $this->setData('behavior', $this->getDataSourceModel()->getBehavior());
+ $ids = $this->_getEntityAdapter()->getIds();
+ if (empty($ids)) {
+ $idsFromPostData = $this->getData(self::FIELD_IMPORT_IDS);
+ if (null !== $idsFromPostData && '' !== $idsFromPostData) {
+ $ids = explode(",", $idsFromPostData);
+ $this->_getEntityAdapter()->setIds($ids);
+ }
+ }
+ $this->setData('entity', $this->getDataSourceModel()->getEntityTypeCode($ids));
+ $this->setData('behavior', $this->getDataSourceModel()->getBehavior($ids));
+
//Validating images temporary directory path if the constraint has been provided
if ($this->hasData('images_base_directory')
&& $this->getData('images_base_directory') instanceof Filesystem\Directory\ReadInterface
@@ -487,6 +516,7 @@ public function importSource()
$this->addLogComment(__('Begin import of "%1" with "%2" behavior', $this->getEntity(), $this->getBehavior()));
$result = $this->processImport();
+ $this->getDataSourceModel()->markProcessedBunches($ids);
if ($result) {
$this->addLogComment(
@@ -550,61 +580,12 @@ public function getErrorAggregator()
*/
public function uploadSource()
{
- /** @var $adapter \Zend_File_Transfer_Adapter_Http */
- $adapter = $this->_httpFactory->create();
- if (!$adapter->isValid(self::FIELD_NAME_SOURCE_FILE)) {
- $errors = $adapter->getErrors();
- if ($errors[0] == \Zend_Validate_File_Upload::INI_SIZE) {
- $errorMessage = $this->_importExportData->getMaxUploadSizeMessage();
- } else {
- $errorMessage = __('The file was not uploaded.');
- }
- throw new LocalizedException($errorMessage);
- }
-
$entity = $this->getEntity();
- /** @var $uploader Uploader */
- $uploader = $this->_uploaderFactory->create(['fileId' => self::FIELD_NAME_SOURCE_FILE]);
- $uploader->setAllowedExtensions(['csv', 'zip']);
- $uploader->skipDbProcessing(true);
- $fileName = $this->random->getRandomString(32) . '.' . $uploader->getFileExtension();
- try {
- $result = $uploader->save($this->getWorkingDir(), $fileName);
- } catch (\Exception $e) {
- throw new LocalizedException(__('The file cannot be uploaded.'));
- }
-
- $extension = '';
- $uploadedFile = '';
- if ($result !== false) {
- // phpcs:ignore Magento2.Functions.DiscouragedFunction
- $extension = pathinfo($result['file'], PATHINFO_EXTENSION);
- $uploadedFile = $result['path'] . $result['file'];
- }
-
- if (!$extension) {
- $this->_varDirectory->delete($uploadedFile);
- throw new LocalizedException(__('The file you uploaded has no extension.'));
- }
- $sourceFile = $this->getWorkingDir() . $entity;
-
- $sourceFile .= '.' . $extension;
+ $result = $this->upload->uploadSource($entity);
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ $extension = pathinfo($result['file'], PATHINFO_EXTENSION);
+ $sourceFile = $this->getWorkingDir() . $entity . '.' . $extension;
$sourceFileRelative = $this->_varDirectory->getRelativePath($sourceFile);
-
- if (strtolower($uploadedFile) != strtolower($sourceFile)) {
- if ($this->_varDirectory->isExist($sourceFileRelative)) {
- $this->_varDirectory->delete($sourceFileRelative);
- }
-
- try {
- $this->_varDirectory->renameFile(
- $this->_varDirectory->getRelativePath($uploadedFile),
- $sourceFileRelative
- );
- } catch (FileSystemException $e) {
- throw new LocalizedException(__('The source file moving process failed.'));
- }
- }
$this->_removeBom($sourceFile);
$this->createHistoryReport($sourceFileRelative, $entity, $extension, $result);
return $sourceFile;
@@ -883,4 +864,14 @@ public function getDeletedItemsCount()
{
return $this->_getEntityAdapter()->getDeletedItemsCount();
}
+
+ /**
+ * Retrieve Ids of Validated Rows
+ *
+ * @return int[]
+ */
+ public function getValidatedIds() : array
+ {
+ return $this->_getEntityAdapter()->getIds() ?? [];
+ }
}
diff --git a/app/code/Magento/ImportExport/Model/Import/AbstractEntity.php b/app/code/Magento/ImportExport/Model/Import/AbstractEntity.php
index e4d58446b930e..568d78b7fda1c 100644
--- a/app/code/Magento/ImportExport/Model/Import/AbstractEntity.php
+++ b/app/code/Magento/ImportExport/Model/Import/AbstractEntity.php
@@ -16,6 +16,7 @@
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
use Magento\ImportExport\Model\ImportFactory;
use Magento\ImportExport\Model\ResourceModel\Helper;
+use Magento\ImportExport\Model\ResourceModel\Import\Data as DataSourceModel;
use Magento\Store\Model\ScopeInterface;
/**
@@ -27,7 +28,7 @@
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
*/
-abstract class AbstractEntity
+abstract class AbstractEntity implements EntityInterface
{
/**
* Custom row import behavior column name
@@ -120,7 +121,7 @@ abstract class AbstractEntity
/**
* DB data source model
*
- * @var \Magento\ImportExport\Model\ResourceModel\Import\Data
+ * @var DataSourceModel
*/
protected $_dataSourceModel;
@@ -291,6 +292,13 @@ abstract class AbstractEntity
*/
private $serializer;
+ /**
+ * Ids of saved data in DB
+ *
+ * @var array
+ */
+ private array $ids = [];
+
/**
* @param StringUtils $string
* @param ScopeConfigInterface $scopeConfig
@@ -413,7 +421,7 @@ protected function _saveValidatedBunches()
$startNewBunch = false;
$source->rewind();
- $this->_dataSourceModel->cleanBunches();
+ $this->_dataSourceModel->cleanProcessedBunches();
$mainAttributeCode = $this->getMasterAttributeCode();
while ($source->valid() || count($bunchRows) || isset($entityGroup)) {
@@ -425,7 +433,8 @@ protected function _saveValidatedBunches()
}
unset($entityGroup);
}
- $this->_dataSourceModel->saveBunch($this->getEntityTypeCode(), $this->getBehavior(), $bunchRows);
+ $this->ids[] =
+ $this->_dataSourceModel->saveBunch($this->getEntityTypeCode(), $this->getBehavior(), $bunchRows);
$bunchRows = [];
$startNewBunch = false;
@@ -882,9 +891,9 @@ public function getDeletedItemsCount()
*/
protected function updateItemsCounterStats(array $created = [], array $updated = [], array $deleted = [])
{
- $this->countItemsCreated = count($created);
- $this->countItemsUpdated = count($updated);
- $this->countItemsDeleted = count($deleted);
+ $this->countItemsCreated += count($created);
+ $this->countItemsUpdated += count($updated);
+ $this->countItemsDeleted += count($deleted);
return $this;
}
@@ -897,4 +906,35 @@ public function getValidColumnNames()
{
return $this->validColumnNames;
}
+
+ /**
+ * Retrieve Ids of Validated Rows
+ *
+ * @return array
+ */
+ public function getIds() : array
+ {
+ return $this->ids;
+ }
+
+ /**
+ * Set Ids of Validated Rows
+ *
+ * @param array $ids
+ * @return void
+ */
+ public function setIds(array $ids)
+ {
+ $this->ids = $ids;
+ }
+
+ /**
+ * Gets the currently used DataSourceModel
+ *
+ * @return DataSourceModel
+ */
+ public function getDataSourceModel() : DataSourceModel
+ {
+ return $this->_dataSourceModel;
+ }
}
diff --git a/app/code/Magento/ImportExport/Model/Import/Adapter.php b/app/code/Magento/ImportExport/Model/Import/Adapter.php
index 0242ae6096316..6a438b1fc1e88 100644
--- a/app/code/Magento/ImportExport/Model/Import/Adapter.php
+++ b/app/code/Magento/ImportExport/Model/Import/Adapter.php
@@ -9,8 +9,6 @@
/**
* Import adapter model
- *
- * @author Magento Core Team
*/
class Adapter
{
@@ -23,7 +21,7 @@ class Adapter
* @param mixed $options OPTIONAL Adapter constructor options
*
* @return AbstractSource
- *
+ * phpcs:disable Magento2.Functions.StaticFunction
* @throws \Magento\Framework\Exception\LocalizedException
*/
public static function factory($type, $directory, $source, $options = null)
@@ -56,11 +54,11 @@ public static function factory($type, $directory, $source, $options = null)
* @param string $source Source file path.
* @param Write $directory
* @param mixed $options OPTIONAL Adapter constructor options
- *
+ * phpcs:disable Magento2.Functions.StaticFunction
* @return AbstractSource
*/
public static function findAdapterFor($source, $directory, $options = null)
{
- return self::factory(pathinfo($source, PATHINFO_EXTENSION), $directory, $source, $options);
+ return self::factory(pathinfo($source, PATHINFO_EXTENSION), $directory, $source, $options); // phpcs:ignore
}
}
diff --git a/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php b/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php
index 9188f38390a2a..eb2d56018b31b 100644
--- a/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php
+++ b/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php
@@ -9,10 +9,12 @@
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Serialize\Serializer\Json;
-use Magento\ImportExport\Model\Import\AbstractSource;
use Magento\ImportExport\Model\Import as ImportExport;
+use Magento\ImportExport\Model\Import\AbstractSource;
+use Magento\ImportExport\Model\Import\EntityInterface;
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingError;
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
+use Magento\ImportExport\Model\ResourceModel\Import\Data as DataSourceModel;
/**
* Import entity abstract model
@@ -24,7 +26,7 @@
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
*/
-abstract class AbstractEntity
+abstract class AbstractEntity implements EntityInterface
{
/**
* Database constants
@@ -101,7 +103,7 @@ abstract class AbstractEntity
/**
* DB data source model.
*
- * @var \Magento\ImportExport\Model\ResourceModel\Import\Data
+ * @var DataSourceModel
*/
protected $_dataSourceModel;
@@ -257,6 +259,13 @@ abstract class AbstractEntity
*/
private $serializer;
+ /**
+ * Ids of saved data in DB
+ *
+ * @var array
+ */
+ private array $ids = [];
+
/**
* @param \Magento\Framework\Json\Helper\Data $jsonHelper
* @param \Magento\ImportExport\Helper\Data $importExportData
@@ -387,11 +396,11 @@ protected function _saveValidatedBunches()
$skuSet = [];
$source->rewind();
- $this->_dataSourceModel->cleanBunches();
-
+ $this->_dataSourceModel->cleanProcessedBunches();
while ($source->valid() || $bunchRows) {
if ($startNewBunch || !$source->valid()) {
- $this->_dataSourceModel->saveBunch($this->getEntityTypeCode(), $this->getBehavior(), $bunchRows);
+ $this->ids[] =
+ $this->_dataSourceModel->saveBunch($this->getEntityTypeCode(), $this->getBehavior(), $bunchRows);
$bunchRows = $nextRowBackup;
$currentDataSize = strlen($this->getSerializer()->serialize($bunchRows));
@@ -442,7 +451,6 @@ protected function _saveValidatedBunches()
* Workaround. Only way to implement dependency and not to break inherited child classes
*
* @return Json
- * @deprecated 100.2.0
*/
private function getSerializer()
{
@@ -515,7 +523,7 @@ public function getAttributeOptions(
// merge global entity index value attributes
$indexValAttrs = array_merge($indexValAttrs, $this->_indexValueAttributes);
- // should attribute has index (option value) instead of a label?
+ // should attribute have index (option value) instead of a label?
$index = in_array($attribute->getAttributeCode(), $indexValAttrs) ? 'value' : 'label';
// only default (admin) store values used
@@ -889,4 +897,35 @@ protected function getMetadataPool()
}
return $this->metadataPool;
}
+
+ /**
+ * Retrieve Ids of Validated Rows
+ *
+ * @return array
+ */
+ public function getIds() : array
+ {
+ return $this->ids;
+ }
+
+ /**
+ * Set Ids of Validated Rows
+ *
+ * @param array $ids
+ * @return void
+ */
+ public function setIds(array $ids)
+ {
+ $this->ids = $ids;
+ }
+
+ /**
+ * Gets the currently used DataSourceModel
+ *
+ * @return DataSourceModel
+ */
+ public function getDataSourceModel() : DataSourceModel
+ {
+ return $this->_dataSourceModel;
+ }
}
diff --git a/app/code/Magento/ImportExport/Model/Import/EntityInterface.php b/app/code/Magento/ImportExport/Model/Import/EntityInterface.php
new file mode 100644
index 0000000000000..12ed773824975
--- /dev/null
+++ b/app/code/Magento/ImportExport/Model/Import/EntityInterface.php
@@ -0,0 +1,209 @@
+processInvalidRow($rowNumber);
}
$errorMessage = $this->getErrorMessage($errorCode, $errorMessage, $columnName);
-
/** @var ProcessingError $newError */
$newError = $this->errorFactory->create();
$newError->init($errorCode, $errorLevel, $rowNumber, $columnName, $errorMessage, $errorDescription);
$this->items['rows'][$rowNumber][] = $newError;
$this->items['codes'][$errorCode][] = $newError;
$this->items['messages'][$errorMessage][] = $newError;
+ $this->itemsByRowColumnAndCode[$rowNumber][$columnName][$errorCode] = $newError;
return $this;
}
@@ -356,7 +361,7 @@ public function clear()
$this->errorStatistics = [];
$this->invalidRows = [];
$this->skippedRows = [];
-
+ $this->itemsByRowColumnAndCode = [];
return $this;
}
@@ -370,13 +375,7 @@ public function clear()
*/
protected function isErrorAlreadyAdded($rowNum, $errorCode, $columnName = null)
{
- $errors = $this->getErrorsByCode([$errorCode]);
- foreach ($errors as $error) {
- if ($rowNum == $error->getRowNumber() && $columnName == $error->getColumnName()) {
- return true;
- }
- }
- return false;
+ return isset($this->itemsByRowColumnAndCode[$rowNum][$columnName][$errorCode]);
}
/**
diff --git a/app/code/Magento/ImportExport/Model/Import/Source/Csv.php b/app/code/Magento/ImportExport/Model/Import/Source/Csv.php
index e04ef9f9537bf..71780d8ae8b0e 100644
--- a/app/code/Magento/ImportExport/Model/Import/Source/Csv.php
+++ b/app/code/Magento/ImportExport/Model/Import/Source/Csv.php
@@ -5,7 +5,8 @@
*/
namespace Magento\ImportExport\Model\Import\Source;
-use Magento\Framework\Filesystem\Directory\Read;
+use Magento\Framework\Filesystem\Directory\Read as DirectoryRead;
+use Magento\Framework\Filesystem\File\ReadInterface as FileReadInterface;
/**
* CSV import adapter
@@ -13,7 +14,7 @@
class Csv extends \Magento\ImportExport\Model\Import\AbstractSource
{
/**
- * @var \Magento\Framework\Filesystem\File\Write
+ * @var FileReadInterface
*/
protected $_file;
@@ -42,27 +43,33 @@ class Csv extends \Magento\ImportExport\Model\Import\AbstractSource
*
* There must be column names in the first line
*
- * @param string $file
- * @param Read $directory
+ * @param string|FileReadInterface $file
+ * @param DirectoryRead $directory
* @param string $delimiter
* @param string $enclosure
* @throws \LogicException
*/
public function __construct(
$file,
- Read $directory,
+ DirectoryRead $directory,
$delimiter = ',',
$enclosure = '"'
) {
// phpcs:ignore Magento2.Functions.DiscouragedFunction
register_shutdown_function([$this, 'destruct']);
- try {
- $this->filePath = $directory->getRelativePath($file);
- $this->_file = $directory->openFile($this->filePath, 'r');
+ if ($file instanceof FileReadInterface) {
+ $this->filePath = '';
+ $this->_file = $file;
$this->_file->seek(0);
- self::$openFiles[$this->filePath] = true;
- } catch (\Magento\Framework\Exception\FileSystemException $e) {
- throw new \LogicException("Unable to open file: '{$file}'");
+ } else {
+ try {
+ $this->filePath = $directory->getRelativePath($file);
+ $this->_file = $directory->openFile($this->filePath, 'r');
+ $this->_file->seek(0);
+ self::$openFiles[$this->filePath] = true;
+ } catch (\Magento\Framework\Exception\FileSystemException $e) {
+ throw new \LogicException("Unable to open file: '{$file}'");
+ }
}
if ($delimiter) {
$this->_delimiter = $delimiter;
diff --git a/app/code/Magento/ImportExport/Model/ResourceModel/Import/Data.php b/app/code/Magento/ImportExport/Model/ResourceModel/Import/Data.php
index 254050bfb1dc1..81cd041c2731d 100644
--- a/app/code/Magento/ImportExport/Model/ResourceModel/Import/Data.php
+++ b/app/code/Magento/ImportExport/Model/ResourceModel/Import/Data.php
@@ -54,6 +54,8 @@ protected function _construct()
/**
* Retrieve an external iterator
*
+ * @Deprecated
+ * @see getIteratorForCustomQuery
* @return \Iterator
*/
#[\ReturnTypeWillChange]
@@ -75,6 +77,35 @@ public function getIterator()
return $iterator;
}
+ /**
+ * Retrieve an external iterator for Custom Query
+ *
+ * @param array $ids
+ * @return \Iterator
+ */
+ #[\ReturnTypeWillChange]
+ public function getIteratorForCustomQuery($ids)
+ {
+ $connection = $this->getConnection();
+ $select = $connection->select()
+ ->from($this->getMainTable(), ['data'])->order('id ASC');
+ if ($ids) {
+ $select = $select->where('id IN (?)', $ids);
+ }
+ $stmt = $connection->query($select);
+
+ $stmt->setFetchMode(\Zend_Db::FETCH_NUM);
+ if ($stmt instanceof \IteratorAggregate) {
+ $iterator = $stmt->getIterator();
+ } else {
+ // Statement doesn't support iterating, so fetch all records and create iterator ourself
+ $rows = $stmt->fetchAll();
+ $iterator = new \ArrayIterator($rows);
+ }
+
+ return $iterator;
+ }
+
/**
* Clean all bunches from table.
*
@@ -85,24 +116,56 @@ public function cleanBunches()
return $this->getConnection()->delete($this->getMainTable());
}
+ /**
+ * Clean all processed bunches from table.
+ *
+ * @return void
+ */
+ public function cleanProcessedBunches()
+ {
+ $this->getConnection()->delete(
+ $this->getMainTable(),
+ 'is_processed = 1 OR TIMESTAMPADD(DAY, 1, updated_at) < CURRENT_TIMESTAMP() '
+ );
+ }
+
+ /**
+ * Set Flag for Imported Rows
+ *
+ * @param array $ids
+ * @return void
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ public function markProcessedBunches(array $ids): void
+ {
+ $where = $ids ? ['id IN (?)' => $ids] : '';
+ $this->getConnection()->update(
+ $this->getMainTable(),
+ ['is_processed' => '1'],
+ $where
+ );
+ }
+
/**
* Return behavior from import data table.
*
+ * @param array|null $ids
* @return string
*/
- public function getBehavior()
+ public function getBehavior($ids = null)
{
- return $this->getUniqueColumnData('behavior');
+ return $this->getUniqueColumnDataWithIds('behavior', $ids);
}
/**
* Return entity type code from import data table.
*
+ * @param array $ids
* @return string
*/
- public function getEntityTypeCode()
+ public function getEntityTypeCode($ids = null)
{
- return $this->getUniqueColumnData('entity');
+ return $this->getUniqueColumnDataWithIds('entity', $ids);
}
/**
@@ -125,6 +188,31 @@ public function getUniqueColumnData($code)
return $values[0];
}
+ /**
+ * Retrieve Unique Column Data for specific Ids
+ *
+ * @param string $code
+ * @param array $ids
+ * @return mixed
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ public function getUniqueColumnDataWithIds($code, $ids = null)
+ {
+ $connection = $this->getConnection();
+ $select = $connection->select()->from($this->getMainTable(), [$code]);
+ if ($ids) {
+ $select = $select->where('id IN (?)', $ids);
+ }
+ $values = array_unique($connection->fetchCol($select));
+
+ if (count($values) != 1) {
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __('Error in data structure: %1 values are mixed', $code)
+ );
+ }
+ return $values[0];
+ }
+
/**
* Get next bunch of validated rows.
*
@@ -139,7 +227,33 @@ public function getNextBunch()
$dataRow = null;
if ($this->_iterator->valid()) {
$encodedData = $this->_iterator->current();
- if (array_key_exists(0, $encodedData) && $encodedData[0]) {
+ if (!empty($encodedData[0])) {
+ $dataRow = $this->jsonHelper->jsonDecode($encodedData[0]);
+ $this->_iterator->next();
+ }
+ }
+ if (!$dataRow) {
+ $this->_iterator = null;
+ }
+ return $dataRow;
+ }
+
+ /**
+ * Get next unique bunch of validated rows.
+ *
+ * @param array|null $ids
+ * @return array|null
+ */
+ public function getNextUniqueBunch($ids = null)
+ {
+ if (null === $this->_iterator) {
+ $this->_iterator = $this->getIteratorForCustomQuery($ids);
+ $this->_iterator->rewind();
+ }
+ $dataRow = null;
+ if ($this->_iterator->valid()) {
+ $encodedData = $this->_iterator->current();
+ if (!empty($encodedData[0])) {
$dataRow = $this->jsonHelper->jsonDecode($encodedData[0]);
$this->_iterator->next();
}
@@ -168,9 +282,10 @@ public function saveBunch($entity, $behavior, array $data)
);
}
- return $this->getConnection()->insert(
+ $this->getConnection()->insert(
$this->getMainTable(),
- ['behavior' => $behavior, 'entity' => $entity, 'data' => $encodedData]
+ ['behavior' => $behavior, 'entity' => $entity, 'data' => $encodedData, 'is_processed' => '0']
);
+ return $this->getConnection()->lastInsertId($this->getMainTable());
}
}
diff --git a/app/code/Magento/ImportExport/Model/Source/Upload.php b/app/code/Magento/ImportExport/Model/Source/Upload.php
new file mode 100644
index 0000000000000..fdddaaf1a4abe
--- /dev/null
+++ b/app/code/Magento/ImportExport/Model/Source/Upload.php
@@ -0,0 +1,102 @@
+httpFactory = $httpFactory;
+ $this->importExportData = $importExportData;
+ $this->uploaderFactory = $uploaderFactory;
+ $this->random = $random;
+ $this->varDirectory = $filesystem->getDirectoryWrite(DirectoryList::VAR_IMPORT_EXPORT);
+ }
+ /**
+ * Move uploaded file.
+ *
+ * @param string $entity
+ * @throws LocalizedException
+ * @return array
+ */
+ public function uploadSource(string $entity)
+ {
+ /** @var $adapter \Zend_File_Transfer_Adapter_Http */
+ $adapter = $this->httpFactory->create();
+ if (!$adapter->isValid(Import::FIELD_NAME_SOURCE_FILE)) {
+ $errors = $adapter->getErrors();
+ if ($errors[0] == \Zend_Validate_File_Upload::INI_SIZE) {
+ $errorMessage = $this->importExportData->getMaxUploadSizeMessage();
+ } else {
+ $errorMessage = __('The file was not uploaded.');
+ }
+ throw new LocalizedException($errorMessage);
+ }
+
+ /** @var $uploader Uploader */
+ $uploader = $this->uploaderFactory->create(['fileId' => Import::FIELD_NAME_SOURCE_FILE]);
+ $uploader->setAllowedExtensions(['csv', 'zip']);
+ $uploader->skipDbProcessing(true);
+ $fileName = $this->random->getRandomString(32) . '.' . $uploader->getFileExtension();
+ try {
+ $result = $uploader->save($this->varDirectory->getAbsolutePath('importexport/'), $fileName);
+ } catch (\Exception $e) {
+ throw new LocalizedException(__('The file cannot be uploaded.'));
+ }
+ $uploader->renameFile($entity);
+ return $result;
+ }
+}
diff --git a/app/code/Magento/ImportExport/Test/Unit/Helper/ReportTest.php b/app/code/Magento/ImportExport/Test/Unit/Helper/ReportTest.php
index f7f576476246b..2f10ce42f84d4 100644
--- a/app/code/Magento/ImportExport/Test/Unit/Helper/ReportTest.php
+++ b/app/code/Magento/ImportExport/Test/Unit/Helper/ReportTest.php
@@ -27,6 +27,7 @@
use Magento\ImportExport\Model\Import;
use Magento\ImportExport\Model\Import\Config;
use Magento\ImportExport\Model\Import\Entity\Factory;
+use Magento\ImportExport\Model\Source\Upload;
use Magento\MediaStorage\Model\File\UploaderFactory;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -202,6 +203,7 @@ public function testGetSummaryStats()
$indexerRegistry = $this->createMock(IndexerRegistry::class);
$importHistoryModel = $this->createMock(History::class);
$localeDate = $this->createMock(\Magento\Framework\Stdlib\DateTime\DateTime::class);
+ $upload = $this->createMock(Upload::class);
$import = new Import(
$logger,
$filesystem,
@@ -216,7 +218,11 @@ public function testGetSummaryStats()
$behaviorFactory,
$indexerRegistry,
$importHistoryModel,
- $localeDate
+ $localeDate,
+ [],
+ null,
+ null,
+ $upload
);
$import->setData('entity', 'catalog_product');
$message = $this->report->getSummaryStats($import);
diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Export/Config/XsdTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Export/Config/XsdTest.php
index b2b14f343683c..537e26de8c8bf 100644
--- a/app/code/Magento/ImportExport/Test/Unit/Model/Export/Config/XsdTest.php
+++ b/app/code/Magento/ImportExport/Test/Unit/Model/Export/Config/XsdTest.php
@@ -42,7 +42,10 @@ protected function setUp(): void
protected function _loadDataForTest($schemaName, $xmlString, $expectedError)
{
$actualError = $this->_xsdValidator->validate($this->_xsdSchemaPath . $schemaName, $xmlString);
- $this->assertEquals($expectedError, $actualError);
+ $this->assertEquals(false, empty($actualError));
+ foreach ($expectedError as $error) {
+ $this->assertContains($error, $actualError);
+ }
}
/**
diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Export/Config/_files/invalidExportXmlArray.php b/app/code/Magento/ImportExport/Test/Unit/Model/Export/Config/_files/invalidExportXmlArray.php
index 12180ccc16ceb..8a3621cc9ff1f 100644
--- a/app/code/Magento/ImportExport/Test/Unit/Model/Export/Config/_files/invalidExportXmlArray.php
+++ b/app/code/Magento/ImportExport/Test/Unit/Model/Export/Config/_files/invalidExportXmlArray.php
@@ -28,12 +28,8 @@
[
"Element 'entityType', attribute 'model': [facet 'pattern'] The value '1' is not accepted by the " .
"pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
- "Element 'entityType', attribute 'model': '1' is not a valid value of the atomic type" .
- " 'modelName'.\nLine: 1\n",
"Element 'fileFormat', attribute 'model': [facet 'pattern'] The value '1model' is not " .
- "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
- "Element 'fileFormat', attribute 'model': '1model' is not a valid " .
- "value of the atomic type 'modelName'.\nLine: 1\n"
+ "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n"
],
],
'productType_node_with_required_attribute' => [
diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Export/ConsumerTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Export/ConsumerTest.php
new file mode 100644
index 0000000000000..abef693c9acad
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Unit/Model/Export/ConsumerTest.php
@@ -0,0 +1,112 @@
+loggerMock = $this->createMock(LoggerInterface::class);
+ $this->exportManagementMock = $this->createMock(ExportManagementInterface::class);
+ $this->filesystemMock = $this->createMock(Filesystem::class);
+ $this->notifierMock = $this->createMock(NotifierInterface::class);
+ $this->localeResolver = $this->createMock(ResolverInterface::class);
+ $this->consumer = new Consumer(
+ $this->loggerMock,
+ $this->exportManagementMock,
+ $this->filesystemMock,
+ $this->notifierMock,
+ $this->localeResolver
+ );
+ }
+
+ public function testProcess()
+ {
+ $adminLocale = 'de_DE';
+ $exportInfoMock = $this->createMock(LocalizedExportInfoInterface::class);
+ $exportInfoMock->expects($this->atLeastOnce())
+ ->method('getLocale')
+ ->willReturn($adminLocale);
+ $exportInfoMock->expects($this->atLeastOnce())
+ ->method('getFileName')
+ ->willReturn('file_name.csv');
+
+ $defaultLocale = 'en_US';
+ $this->localeResolver->expects($this->once())
+ ->method('getLocale')
+ ->willReturn($defaultLocale);
+ $this->localeResolver->expects($this->exactly(2))
+ ->method('setLocale')
+ ->withConsecutive([$adminLocale], [$defaultLocale])
+ ->willReturn($this->localeResolver);
+
+ $data = '1,2,3';
+ $this->exportManagementMock->expects($this->once())
+ ->method('export')
+ ->with($exportInfoMock)
+ ->willReturn($data);
+
+ $directoryMock = $this->createMock(WriteInterface::class);
+ $this->filesystemMock->expects($this->once())
+ ->method('getDirectoryWrite')
+ ->with(DirectoryList::VAR_IMPORT_EXPORT)
+ ->willReturn($directoryMock);
+ $directoryMock->expects($this->once())
+ ->method('writeFile')
+ ->with('export/file_name.csv', $data)
+ ->willReturn(5);
+
+ $this->notifierMock->expects($this->once())
+ ->method('addMajor')
+ ->willReturn($this->notifierMock);
+
+ $this->consumer->process($exportInfoMock);
+ }
+}
diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/XsdMergedTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/XsdMergedTest.php
index 6bc34957631cf..b1df610187316 100644
--- a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/XsdMergedTest.php
+++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/XsdMergedTest.php
@@ -42,7 +42,10 @@ protected function setUp(): void
public function testSchemaCorrectlyIdentifiesInvalidXml($xmlString, $expectedError)
{
$actualError = $this->_xsdValidator->validate($this->_xsdSchema, $xmlString);
- $this->assertEquals($expectedError, $actualError);
+ $this->assertEquals(false, empty($actualError));
+ foreach ($expectedError as $error) {
+ $this->assertContains($error, $actualError);
+ }
}
public function testSchemaCorrectlyIdentifiesValidXml()
diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/XsdTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/XsdTest.php
index 8732ef7777510..0cd83152d9adb 100644
--- a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/XsdTest.php
+++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/XsdTest.php
@@ -42,7 +42,10 @@ protected function setUp(): void
public function testSchemaCorrectlyIdentifiesInvalidXml($xmlString, $expectedError)
{
$actualError = $this->_xsdValidator->validate($this->_xsdSchema, $xmlString);
- $this->assertEquals($expectedError, $actualError);
+ $this->assertEquals(false, empty($actualError));
+ foreach ($expectedError as $error) {
+ $this->assertContains($error, $actualError);
+ }
}
public function testSchemaCorrectlyIdentifiesValidXml()
diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportMergedXmlArray.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportMergedXmlArray.php
index d5533ea4ccb13..bd3bf6711ceda 100644
--- a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportMergedXmlArray.php
+++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportMergedXmlArray.php
@@ -32,9 +32,7 @@
'behaviorModel="test" />',
[
"Element 'entity', attribute 'model': [facet 'pattern'] The value '34afwer' is not " .
- "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
- "Element 'entity', attribute 'model': '34afwer' is not a valid value of the atomic type" .
- " 'modelName'.\nLine: 1\n"
+ "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n"
],
],
'entity_behaviorModel_with_invalid_value' => [
@@ -42,9 +40,7 @@
'',
[
"Element 'entity', attribute 'behaviorModel': [facet 'pattern'] The value '666' is not accepted by " .
- "the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
- "Element 'entity', attribute 'behaviorModel': '666' is not a valid value of the atomic type" .
- " 'modelName'.\nLine: 1\n"
+ "the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n"
],
]
];
diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportXmlArray.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportXmlArray.php
index 47d9afbb6144a..ed9c74b92dbea 100644
--- a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportXmlArray.php
+++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportXmlArray.php
@@ -21,18 +21,14 @@
' ',
[
"Element 'entity', attribute 'model': [facet 'pattern'] The value '12345' is not accepted by " .
- "the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
- "Element 'entity', attribute 'model': '12345' is not a valid value of the atomic type" .
- " 'modelName'.\nLine: 1\n"
+ "the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n"
],
],
'entity_with_invalid_behaviormodel_value' => [
' ',
[
"Element 'entity', attribute 'behaviorModel': [facet 'pattern'] The value '=--09' is not " .
- "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
- "Element 'entity', attribute 'behaviorModel': '=--09' is not a valid value of the atomic type" .
- " 'modelName'.\nLine: 1\n"
+ "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n"
],
],
'entity_with_notallowed_attribute' => [
@@ -51,9 +47,7 @@
' ',
[
"Element 'entityType', attribute 'model': [facet 'pattern'] The value '1test' is not " .
- "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
- "Element 'entityType', attribute 'model': '1test' is not a valid value of the atomic type" .
- " 'modelName'.\nLine: 1\n"
+ "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n"
],
],
'entitytype_with_notallowed' => [
diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/ImportTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/ImportTest.php
index fe3bf212ad77e..3f5b40cef7982 100644
--- a/app/code/Magento/ImportExport/Test/Unit/Model/ImportTest.php
+++ b/app/code/Magento/ImportExport/Test/Unit/Model/ImportTest.php
@@ -30,6 +30,7 @@
use Magento\ImportExport\Model\Import\Entity\Factory;
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
use Magento\ImportExport\Model\Import\Source\Csv;
+use Magento\ImportExport\Model\Source\Upload;
use Magento\ImportExport\Test\Unit\Model\Import\AbstractImportTestCase;
use Magento\MediaStorage\Model\File\UploaderFactory;
use PHPUnit\Framework\MockObject\MockObject;
@@ -132,6 +133,11 @@ class ImportTest extends AbstractImportTestCase
*/
private $errorAggregatorMock;
+ /**
+ * @var Upload
+ */
+ private $upload;
+
/**
* Set up
*
@@ -225,6 +231,7 @@ protected function setUp(): void
->expects($this->any())
->method('getDriver')
->willReturn($this->_driver);
+ $this->upload = $this->createMock(Upload::class);
$this->import = $this->getMockBuilder(Import::class)
->setConstructorArgs(
[
@@ -241,7 +248,11 @@ protected function setUp(): void
$this->_behaviorFactory,
$this->indexerRegistry,
$this->historyModel,
- $this->dateTime
+ $this->dateTime,
+ [],
+ null,
+ null,
+ $this->upload
]
)
->setMethods(
@@ -555,7 +566,11 @@ public function testInvalidateIndex()
$this->_behaviorFactory,
$this->indexerRegistry,
$this->historyModel,
- $this->dateTime
+ $this->dateTime,
+ [],
+ null,
+ null,
+ $this->upload
);
$import->setEntity('test');
@@ -588,7 +603,11 @@ public function testInvalidateIndexWithoutIndexers()
$this->_behaviorFactory,
$this->indexerRegistry,
$this->historyModel,
- $this->dateTime
+ $this->dateTime,
+ [],
+ null,
+ null,
+ $this->upload
);
$import->setEntity('test');
@@ -618,7 +637,11 @@ public function testGetKnownEntity()
$this->_behaviorFactory,
$this->indexerRegistry,
$this->historyModel,
- $this->dateTime
+ $this->dateTime,
+ [],
+ null,
+ null,
+ $this->upload
);
$import->setEntity('test');
@@ -654,7 +677,11 @@ public function testGetUnknownEntity($entity)
$this->_behaviorFactory,
$this->indexerRegistry,
$this->historyModel,
- $this->dateTime
+ $this->dateTime,
+ [],
+ null,
+ null,
+ $this->upload
);
$import->setEntity($entity);
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/ImportExport/etc/communication.xml b/app/code/Magento/ImportExport/etc/communication.xml
index 3f87eef1ddbd4..de0907f3831fc 100644
--- a/app/code/Magento/ImportExport/etc/communication.xml
+++ b/app/code/Magento/ImportExport/etc/communication.xml
@@ -6,7 +6,7 @@
*/
-->
-
+
diff --git a/app/code/Magento/ImportExport/etc/crontab.xml b/app/code/Magento/ImportExport/etc/crontab.xml
new file mode 100644
index 0000000000000..57780dfe95678
--- /dev/null
+++ b/app/code/Magento/ImportExport/etc/crontab.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ 0 0 * * *
+
+
+
diff --git a/app/code/Magento/ImportExport/etc/db_schema.xml b/app/code/Magento/ImportExport/etc/db_schema.xml
index 12242364fbf18..f31b3499f0dc6 100644
--- a/app/code/Magento/ImportExport/etc/db_schema.xml
+++ b/app/code/Magento/ImportExport/etc/db_schema.xml
@@ -12,6 +12,9 @@
+
+
diff --git a/app/code/Magento/ImportExport/etc/db_schema_whitelist.json b/app/code/Magento/ImportExport/etc/db_schema_whitelist.json
index e78535d2c7585..768b3ed6ef96b 100644
--- a/app/code/Magento/ImportExport/etc/db_schema_whitelist.json
+++ b/app/code/Magento/ImportExport/etc/db_schema_whitelist.json
@@ -4,7 +4,9 @@
"id": true,
"entity": true,
"behavior": true,
- "data": true
+ "data": true,
+ "is_processed": true,
+ "updated_at": true
},
"constraint": {
"PRIMARY": true
@@ -24,4 +26,4 @@
"PRIMARY": true
}
}
-}
\ No newline at end of file
+}
diff --git a/app/code/Magento/ImportExport/etc/di.xml b/app/code/Magento/ImportExport/etc/di.xml
index 66d7fc94ee175..b4c65aaf5ef11 100644
--- a/app/code/Magento/ImportExport/etc/di.xml
+++ b/app/code/Magento/ImportExport/etc/di.xml
@@ -11,7 +11,7 @@
-
+
diff --git a/app/code/Magento/Indexer/Console/Command/IndexerInfoCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerInfoCommand.php
index af3d42d9a670e..ee71a60e1ab77 100644
--- a/app/code/Magento/Indexer/Console/Command/IndexerInfoCommand.php
+++ b/app/code/Magento/Indexer/Console/Command/IndexerInfoCommand.php
@@ -5,6 +5,7 @@
*/
namespace Magento\Indexer\Console\Command;
+use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -14,7 +15,7 @@
class IndexerInfoCommand extends AbstractIndexerCommand
{
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function configure()
{
@@ -22,7 +23,7 @@ protected function configure()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -30,5 +31,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
foreach ($indexers as $indexer) {
$output->writeln(sprintf('%-40s %s', $indexer->getId(), $indexer->getTitle()));
}
+
+ return Cli::RETURN_SUCCESS;
}
}
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/Console/Command/IndexerResetStateCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerResetStateCommand.php
index 5cfe0205429fe..be1b46625f8d2 100644
--- a/app/code/Magento/Indexer/Console/Command/IndexerResetStateCommand.php
+++ b/app/code/Magento/Indexer/Console/Command/IndexerResetStateCommand.php
@@ -5,12 +5,11 @@
*/
namespace Magento\Indexer\Console\Command;
+use Exception;
+use Magento\Framework\Console\Cli;
use Magento\Framework\Exception\LocalizedException;
-use Magento\Framework\Indexer\StateInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use Magento\Framework\Indexer\ConfigInterface;
-use Magento\Framework\Console\Cli;
/**
* Command for invalidating indexers.
@@ -45,11 +44,14 @@ protected function execute(InputInterface $input, OutputInterface $output)
$output->writeln($indexer->getTitle() . ' indexer has been invalidated.');
} catch (LocalizedException $e) {
$output->writeln($e->getMessage());
- } catch (\Exception $e) {
+ return Cli::RETURN_FAILURE;
+ } catch (Exception $e) {
$output->writeln($indexer->getTitle() . ' indexer process unknown error:');
$output->writeln($e->getMessage());
+ return Cli::RETURN_FAILURE;
}
}
+
return Cli::RETURN_SUCCESS;
}
}
diff --git a/app/code/Magento/Indexer/Console/Command/IndexerShowModeCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerShowModeCommand.php
index 4d063e86fcb90..60c6878be5f62 100644
--- a/app/code/Magento/Indexer/Console/Command/IndexerShowModeCommand.php
+++ b/app/code/Magento/Indexer/Console/Command/IndexerShowModeCommand.php
@@ -5,6 +5,7 @@
*/
namespace Magento\Indexer\Console\Command;
+use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -14,7 +15,7 @@
class IndexerShowModeCommand extends AbstractIndexerManageCommand
{
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function configure()
{
@@ -26,7 +27,7 @@ protected function configure()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -35,5 +36,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$status = $indexer->isScheduled() ? 'Update by Schedule' : 'Update on Save';
$output->writeln(sprintf('%-50s ', $indexer->getTitle() . ':') . $status);
}
+
+ return Cli::RETURN_SUCCESS;
}
}
diff --git a/app/code/Magento/Indexer/Console/Command/IndexerStatusCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerStatusCommand.php
index 26feb38392e5f..cc3ec26e1fb79 100644
--- a/app/code/Magento/Indexer/Console/Command/IndexerStatusCommand.php
+++ b/app/code/Magento/Indexer/Console/Command/IndexerStatusCommand.php
@@ -5,6 +5,7 @@
*/
namespace Magento\Indexer\Console\Command;
+use Magento\Framework\Console\Cli;
use Magento\Framework\Indexer;
use Magento\Framework\Mview;
use Symfony\Component\Console\Helper\Table;
@@ -71,6 +72,8 @@ function (array $comp1, array $comp2) {
$table->addRows($rows);
$table->render();
+
+ return Cli::RETURN_SUCCESS;
}
/**
diff --git a/app/code/Magento/Indexer/Test/Fixture/ScheduleMode.php b/app/code/Magento/Indexer/Test/Fixture/ScheduleMode.php
new file mode 100644
index 0000000000000..6e026d3f89b56
--- /dev/null
+++ b/app/code/Magento/Indexer/Test/Fixture/ScheduleMode.php
@@ -0,0 +1,62 @@
+indexerRegistry = $indexerRegistry;
+ $this->dataObjectFactory = $dataObjectFactory;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @param array $data Parameters
+ *
+ * $data = [
+ * 'indexer' => (string) Indexer code. Required.
+ * ]
+ *
+ */
+ public function apply(array $data = []): ?DataObject
+ {
+ $this->indexerRegistry->get($data['indexer'])->setScheduled(true);
+
+ return $this->dataObjectFactory->create(['data' => $data]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function revert(DataObject $data): void
+ {
+ $this->indexerRegistry->get($data['indexer'])->setScheduled(false);
+ }
+}
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/Test/Unit/Console/Command/IndexerSetDimensionsModeCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerSetDimensionsModeCommandTest.php
index 4fb56b95c304a..057471ccd4e46 100644
--- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerSetDimensionsModeCommandTest.php
+++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerSetDimensionsModeCommandTest.php
@@ -98,7 +98,7 @@ protected function getObjectManagerReturnValueMap()
*
* @param string $indexerTitle
* @param string $previousMode
- * @param string $command
+ * @param array $command
* @param string $consoleOutput
* @dataProvider dimensionModesDataProvider
* @return void
diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerShowDimensionsModeCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerShowDimensionsModeCommandTest.php
index 4eb89959cabd9..92171428ef39b 100644
--- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerShowDimensionsModeCommandTest.php
+++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerShowDimensionsModeCommandTest.php
@@ -87,7 +87,7 @@ protected function getObjectManagerReturnValueMap(): array
/**
* Tests method \Magento\Indexer\Console\Command\IndexerDimensionsModeCommand::execute
*
- * @param string $command
+ * @param array $command
* @param string $consoleOutput
* @dataProvider dimensionModesDataProvider
*/
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/Model/Oauth/Consumer.php b/app/code/Magento/Integration/Model/Oauth/Consumer.php
index fca5224f94bed..7b004d73edff6 100644
--- a/app/code/Magento/Integration/Model/Oauth/Consumer.php
+++ b/app/code/Magento/Integration/Model/Oauth/Consumer.php
@@ -23,6 +23,7 @@
* @method string getRejectedCallbackUrl()
* @method Consumer setRejectedCallbackUrl(string $rejectedCallbackUrl)
* @since 100.0.2
+ * @SuppressWarnings(PHPMD.NPathComplexity)
*/
class Consumer extends \Magento\Framework\Model\AbstractModel implements ConsumerInterface
{
@@ -89,6 +90,7 @@ protected function _construct()
* @return \Magento\Framework\Stdlib\DateTime\DateTime
*
* @deprecated 100.0.6
+ * @see we don't recommend this approach anymore
*/
private function getDateHelper()
{
diff --git a/app/code/Magento/Integration/Model/Oauth/Consumer/Validator/KeyLength.php b/app/code/Magento/Integration/Model/Oauth/Consumer/Validator/KeyLength.php
index e697d2594bded..36042f1787da1 100644
--- a/app/code/Magento/Integration/Model/Oauth/Consumer/Validator/KeyLength.php
+++ b/app/code/Magento/Integration/Model/Oauth/Consumer/Validator/KeyLength.php
@@ -5,11 +5,18 @@
*/
namespace Magento\Integration\Model\Oauth\Consumer\Validator;
+use Laminas\Validator\StringLength;
+
/**
* Validate OAuth keys
*/
-class KeyLength extends \Zend_Validate_StringLength
+class KeyLength extends StringLength
{
+ /**
+ * @var array
+ */
+ public $messages = [];
+
/**
* Default key name
*
@@ -20,7 +27,7 @@ class KeyLength extends \Zend_Validate_StringLength
/**
* @var array
*/
- protected $_messageTemplates = [
+ protected $messageTemplates = [
self::INVALID => "Invalid type given for %name%. String expected",
self::TOO_SHORT => "%name% '%value%' is less than %min% characters long",
self::TOO_LONG => "%name% '%value%' is more than %max% characters long",
@@ -31,7 +38,10 @@ class KeyLength extends \Zend_Validate_StringLength
*
* @var array
*/
- protected $_messageVariables = ['min' => '_min', 'max' => '_max', 'name' => '_name'];
+ protected $messageVariables = [
+ 'min' => ['options' => 'min'],
+ 'max' => ['options' => 'max'],
+ ];
/**
* Sets KeyLength validator options
@@ -44,6 +54,7 @@ class KeyLength extends \Zend_Validate_StringLength
public function __construct($options = [])
{
if (!is_array($options)) {
+ // phpcs:ignore
$options = func_get_args();
if (!isset($options[1])) {
$options[1] = 'utf-8';
@@ -85,7 +96,7 @@ public function getLength()
}
/**
- * Defined by \Zend_Validate_Interface
+ * Defined by \Laminas\Validator\ValidatorInterface
*
* Returns true if and only if the string length of $value is at least the min option and
* no greater than the max option (when the max option is not null).
@@ -97,8 +108,17 @@ public function getLength()
public function isValid($value)
{
$result = parent::isValid($value);
- if (!$result && isset($this->_messages[self::INVALID])) {
- throw new \Exception($this->_messages[self::INVALID]);
+ $messages = $this->getMessages();
+ $newMessages = [];
+
+ foreach ($messages as $key => $message) {
+ $newMessages[$key] = str_replace('%name%', $this->getName(), $message);
+ }
+ $this->abstractOptions['messages'] = $newMessages;
+
+ if (!$result && isset($newMessages[self::INVALID])) {
+ // phpcs:ignore Magento2.Exceptions.DirectThrow
+ throw new \Exception($newMessages[self::INVALID]);
}
return $result;
}
diff --git a/app/code/Magento/Integration/Model/OauthService.php b/app/code/Magento/Integration/Model/OauthService.php
index 03aaf27319c0c..cbd19e18dbdd6 100644
--- a/app/code/Magento/Integration/Model/OauthService.php
+++ b/app/code/Magento/Integration/Model/OauthService.php
@@ -6,19 +6,22 @@
namespace Magento\Integration\Model;
+use Laminas\Http\Request;
+use Magento\Framework\Exception\IntegrationException;
+use Magento\Framework\HTTP\LaminasClient;
use Magento\Framework\Oauth\Helper\Oauth as OauthHelper;
use Magento\Integration\Helper\Oauth\Data as IntegrationOauthHelper;
use Magento\Integration\Model\Oauth\Consumer as ConsumerModel;
use Magento\Integration\Model\Oauth\ConsumerFactory;
use Magento\Integration\Model\Oauth\Token as OauthTokenModel;
-use Magento\Integration\Model\Oauth\TokenFactory as TokenFactory;
use Magento\Integration\Model\Oauth\Token\Provider as TokenProvider;
-use Magento\Framework\Exception\IntegrationException;
+use Magento\Integration\Model\Oauth\TokenFactory as TokenFactory;
/**
* Integration oAuth service.
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * phpcs:disable Magento2.Annotation.MethodAnnotationStructure
*/
class OauthService implements \Magento\Integration\Api\OauthServiceInterface
{
@@ -43,7 +46,7 @@ class OauthService implements \Magento\Integration\Api\OauthServiceInterface
protected $_dataHelper;
/**
- * @var \Magento\Framework\HTTP\ZendClient
+ * @var LaminasClient
*/
protected $_httpClient;
@@ -74,7 +77,7 @@ class OauthService implements \Magento\Integration\Api\OauthServiceInterface
* @param ConsumerFactory $consumerFactory
* @param TokenFactory $tokenFactory
* @param IntegrationOauthHelper $dataHelper
- * @param \Magento\Framework\HTTP\ZendClient $httpClient
+ * @param LaminasClient $httpClient
* @param \Psr\Log\LoggerInterface $logger
* @param OauthHelper $oauthHelper
* @param TokenProvider $tokenProvider
@@ -84,7 +87,7 @@ public function __construct(
ConsumerFactory $consumerFactory,
TokenFactory $tokenFactory,
IntegrationOauthHelper $dataHelper,
- \Magento\Framework\HTTP\ZendClient $httpClient,
+ LaminasClient $httpClient,
\Psr\Log\LoggerInterface $logger,
OauthHelper $oauthHelper,
TokenProvider $tokenProvider
@@ -103,7 +106,6 @@ public function __construct(
* The getter function to get the new DateTime dependency
*
* @return \Magento\Framework\Stdlib\DateTime\DateTime
- *
* @deprecated 100.0.6
*/
private function getDateHelper()
@@ -116,7 +118,7 @@ private function getDateHelper()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function createConsumer($consumerData)
{
@@ -138,7 +140,7 @@ public function createConsumer($consumerData)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function createAccessToken($consumerId, $clearExistingToken = false)
{
@@ -149,7 +151,7 @@ public function createAccessToken($consumerId, $clearExistingToken = false)
$existingToken->delete();
unset($existingToken);
}
- } catch (\Exception $e) {
+ } catch (\Exception $e) { // phpcs:ignore
}
if (!isset($existingToken)) {
$consumer = $this->_consumerFactory->create()->load($consumerId);
@@ -162,7 +164,7 @@ public function createAccessToken($consumerId, $clearExistingToken = false)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAccessToken($consumerId)
{
@@ -179,7 +181,7 @@ public function getAccessToken($consumerId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function loadConsumer($consumerId)
{
@@ -195,7 +197,7 @@ public function loadConsumer($consumerId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function loadConsumerByKey($key)
{
@@ -211,7 +213,7 @@ public function loadConsumerByKey($key)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function postToConsumer($consumerId, $endpointUrl)
{
@@ -238,8 +240,9 @@ public function postToConsumer($consumerId, $endpointUrl)
);
$maxredirects = $this->_dataHelper->getConsumerPostMaxRedirects();
$timeout = $this->_dataHelper->getConsumerPostTimeout();
- $this->_httpClient->setConfig(['maxredirects' => $maxredirects, 'timeout' => $timeout]);
- $this->_httpClient->request(\Magento\Framework\HTTP\ZendClient::POST);
+ $this->_httpClient->setOptions(['maxredirects' => $maxredirects, 'timeout' => $timeout]);
+ $this->_httpClient->setMethod(Request::METHOD_POST);
+ $this->_httpClient->send();
return $verifier->getVerifier();
} catch (\Magento\Framework\Exception\LocalizedException $exception) {
throw $exception;
@@ -254,7 +257,7 @@ public function postToConsumer($consumerId, $endpointUrl)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function deleteConsumer($consumerId)
{
@@ -265,7 +268,7 @@ public function deleteConsumer($consumerId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function deleteIntegrationToken($consumerId)
{
diff --git a/app/code/Magento/Integration/Test/Unit/Helper/Oauth/ConsumerTest.php b/app/code/Magento/Integration/Test/Unit/Helper/Oauth/ConsumerTest.php
index 5bbfc8394dafa..df4e27fbb4196 100644
--- a/app/code/Magento/Integration/Test/Unit/Helper/Oauth/ConsumerTest.php
+++ b/app/code/Magento/Integration/Test/Unit/Helper/Oauth/ConsumerTest.php
@@ -7,7 +7,7 @@
namespace Magento\Integration\Test\Unit\Helper\Oauth;
-use Magento\Framework\HTTP\ZendClient;
+use Magento\Framework\HTTP\LaminasClient;
use Magento\Framework\Oauth\Helper\Oauth;
use Magento\Framework\Stdlib\DateTime\DateTime;
use Magento\Integration\Api\OauthServiceInterface;
@@ -39,7 +39,7 @@ class ConsumerTest extends TestCase
/** @var Consumer */
protected $_consumerMock;
- /** @var ZendClient */
+ /** @var LaminasClient */
protected $_httpClientMock;
/** @var TokenFactory */
@@ -120,7 +120,7 @@ protected function setUp(): void
->getMock();
$this->_httpClientMock = $this->getMockBuilder(
- ZendClient::class
+ LaminasClient::class
)->disableOriginalConstructor()
->getMock();
$this->_loggerMock = $this->getMockBuilder(
diff --git a/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/XsdTest.php b/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/XsdTest.php
index 0b2599718f592..5333a312e0187 100644
--- a/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/XsdTest.php
+++ b/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/XsdTest.php
@@ -47,7 +47,9 @@ public function testExemplarXml($fixtureXml, array $expectedErrors)
$dom = new Dom($fixtureXml, $validationStateMock, [], null, null, $messageFormat);
$actualResult = $dom->validate($this->schemaFile, $actualErrors);
$this->assertEquals(empty($expectedErrors), $actualResult, "Validation result is invalid.");
- $this->assertEquals($expectedErrors, $actualErrors, "Validation errors does not match.");
+ foreach ($expectedErrors as $error) {
+ $this->assertContains($error, $actualErrors, "Validation errors does not match.");
+ }
}
/**
@@ -144,8 +146,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'email': [facet 'pattern'] The value '' is not " .
- "accepted by the pattern '[^@]+@[^\.]+\..+'.",
- "Element 'email': '' is not a valid value of the atomic type 'emailType'."
+ "accepted by the pattern '[^@]+@[^\.]+\..+'."
],
],
'endpoint_url is empty' => [
@@ -161,8 +162,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'endpoint_url': [facet 'minLength'] The value has a length of '0'; this underruns" .
- " the allowed minimum length of '4'.",
- "Element 'endpoint_url': '' is not a valid value of the atomic type 'urlType'."
+ " the allowed minimum length of '4'."
],
],
'identity_link_url is empty' => [
@@ -179,8 +179,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'identity_link_url': [facet 'minLength'] The value has a length of '0'; this underruns" .
- " the allowed minimum length of '4'.",
- "Element 'identity_link_url': '' is not a valid value of the atomic type 'urlType'."
+ " the allowed minimum length of '4'."
],
],
/** Invalid structure */
@@ -380,9 +379,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'integration', attribute 'name': [facet 'minLength'] The value '' has a length of '0'; " .
- "this underruns the allowed minimum length of '2'.",
- "Element 'integration', attribute 'name': " .
- "'' is not a valid value of the atomic type 'integrationNameType'."
+ "this underruns the allowed minimum length of '2'."
],
],
'resource without name' => [
@@ -413,9 +410,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'resource', attribute 'name': [facet 'pattern'] " .
- "The value '' is not accepted by the pattern '.+_.+::.+'.",
- "Element 'resource', attribute 'name': '' " .
- "is not a valid value of the atomic type 'resourceNameType'."
+ "The value '' is not accepted by the pattern '.+_.+::.+'."
],
],
/** Invalid values */
@@ -433,8 +428,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'email': [facet 'pattern'] The value 'invalid' " .
- "is not accepted by the pattern '[^@]+@[^\.]+\..+'.",
- "Element 'email': 'invalid' is not a valid value of the atomic type 'emailType'."
+ "is not accepted by the pattern '[^@]+@[^\.]+\..+'."
],
],
/** Invalid values */
@@ -452,9 +446,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'resource', attribute 'name': [facet 'pattern'] " .
- "The value 'customer_manage' is not accepted by the pattern '.+_.+::.+'.",
- "Element 'resource', attribute 'name': 'customer_manage' " .
- "is not a valid value of the atomic type 'resourceNameType'."
+ "The value 'customer_manage' is not accepted by the pattern '.+_.+::.+'."
],
]
];
diff --git a/app/code/Magento/Integration/Test/Unit/Model/Config/Integration/XsdTest.php b/app/code/Magento/Integration/Test/Unit/Model/Config/Integration/XsdTest.php
index a76d42e728c08..284e3bad1aa6b 100644
--- a/app/code/Magento/Integration/Test/Unit/Model/Config/Integration/XsdTest.php
+++ b/app/code/Magento/Integration/Test/Unit/Model/Config/Integration/XsdTest.php
@@ -47,7 +47,9 @@ public function testExemplarXml($fixtureXml, array $expectedErrors)
$dom = new Dom($fixtureXml, $validationStateMock, [], null, null, $messageFormat);
$actualResult = $dom->validate($this->schemaFile, $actualErrors);
$this->assertEquals(empty($expectedErrors), $actualResult, "Validation result is invalid.");
- $this->assertEquals($expectedErrors, $actualErrors, "Validation errors does not match.");
+ foreach ($expectedErrors as $error) {
+ $this->assertContains($error, $actualErrors, "Validation errors does not match.");
+ }
}
/**
@@ -230,9 +232,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'integration', attribute 'name': [facet 'minLength'] The value '' has a length of '0'; " .
- "this underruns the allowed minimum length of '2'.",
- "Element 'integration', attribute 'name': " .
- "'' is not a valid value of the atomic type 'integrationNameType'."
+ "this underruns the allowed minimum length of '2'."
],
],
'resource without name' => [
@@ -257,9 +257,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'resource', attribute 'name': [facet 'pattern'] " .
- "The value '' is not accepted by the pattern '.+_.+::.+'.",
- "Element 'resource', attribute 'name': '' " .
- "is not a valid value of the atomic type 'resourceNameType'."
+ "The value '' is not accepted by the pattern '.+_.+::.+'."
],
],
/** Invalid values */
@@ -274,9 +272,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'resource', attribute 'name': [facet 'pattern'] " .
- "The value 'customer_manage' is not accepted by the pattern '.+_.+::.+'.",
- "Element 'resource', attribute 'name': 'customer_manage' " .
- "is not a valid value of the atomic type 'resourceNameType'."
+ "The value 'customer_manage' is not accepted by the pattern '.+_.+::.+'."
],
]
];
diff --git a/app/code/Magento/Integration/Test/Unit/Model/Config/XsdTest.php b/app/code/Magento/Integration/Test/Unit/Model/Config/XsdTest.php
index b42cd568fed7a..72ae3dd18e0a3 100644
--- a/app/code/Magento/Integration/Test/Unit/Model/Config/XsdTest.php
+++ b/app/code/Magento/Integration/Test/Unit/Model/Config/XsdTest.php
@@ -47,7 +47,9 @@ public function testExemplarXml($fixtureXml, array $expectedErrors)
$dom = new Dom($fixtureXml, $validationStateMock, [], null, null, $messageFormat);
$actualResult = $dom->validate($this->schemaFile, $actualErrors);
$this->assertEquals(empty($expectedErrors), $actualResult, "Validation result is invalid.");
- $this->assertEquals($expectedErrors, $actualErrors, "Validation errors does not match.");
+ foreach ($expectedErrors as $error) {
+ $this->assertContains($error, $actualErrors, "Validation errors does not match.");
+ }
}
/**
@@ -112,8 +114,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'email': [facet 'pattern'] The value '' is not " .
- "accepted by the pattern '[^@]+@[^\.]+\..+'.",
- "Element 'email': '' is not a valid value of the atomic type 'emailType'."
+ "accepted by the pattern '[^@]+@[^\.]+\..+'."
],
],
'endpoint_url is empty' => [
@@ -125,8 +126,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'endpoint_url': [facet 'minLength'] The value has a length of '0'; this underruns" .
- " the allowed minimum length of '4'.",
- "Element 'endpoint_url': '' is not a valid value of the atomic type 'urlType'."
+ " the allowed minimum length of '4'."
],
],
'identity_link_url is empty' => [
@@ -139,8 +139,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'identity_link_url': [facet 'minLength'] The value has a length of '0'; this underruns" .
- " the allowed minimum length of '4'.",
- "Element 'identity_link_url': '' is not a valid value of the atomic type 'urlType'."
+ " the allowed minimum length of '4'."
],
],
/** Invalid structure */
@@ -253,9 +252,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'integration', attribute 'name': [facet 'minLength'] The value '' has a length of '0'; " .
- "this underruns the allowed minimum length of '2'.",
- "Element 'integration', attribute 'name': " .
- "'' is not a valid value of the atomic type 'integrationNameType'."
+ "this underruns the allowed minimum length of '2'."
],
],
/** Invalid values */
@@ -269,8 +266,7 @@ public function exemplarXmlDataProvider()
',
[
"Element 'email': [facet 'pattern'] The value 'invalid' " .
- "is not accepted by the pattern '[^@]+@[^\.]+\..+'.",
- "Element 'email': 'invalid' is not a valid value of the atomic type 'emailType'."
+ "is not accepted by the pattern '[^@]+@[^\.]+\..+'."
],
]
];
diff --git a/app/code/Magento/Integration/Test/Unit/Model/OauthServiceTest.php b/app/code/Magento/Integration/Test/Unit/Model/OauthServiceTest.php
index ab96755bd2625..75801d5a76875 100644
--- a/app/code/Magento/Integration/Test/Unit/Model/OauthServiceTest.php
+++ b/app/code/Magento/Integration/Test/Unit/Model/OauthServiceTest.php
@@ -1,14 +1,12 @@
_consumerFactory,
$this->_tokenFactoryMock,
$this->createMock(Data::class),
- $this->createMock(ZendClient::class),
+ $this->createMock(LaminasClient::class),
$this->getMockForAbstractClass(LoggerInterface::class),
$this->createMock(Oauth::class),
$this->_tokenProviderMock
diff --git a/app/code/Magento/Integration/Test/Unit/Oauth/OauthTest.php b/app/code/Magento/Integration/Test/Unit/Oauth/OauthTest.php
index d3bea0dba77e8..e4f582c17b8b9 100644
--- a/app/code/Magento/Integration/Test/Unit/Oauth/OauthTest.php
+++ b/app/code/Magento/Integration/Test/Unit/Oauth/OauthTest.php
@@ -7,6 +7,7 @@
namespace Magento\Integration\Test\Unit\Oauth;
+use Laminas\OAuth\Http\Utility;
use Magento\Framework\DataObject;
use Magento\Framework\Math\Random;
use Magento\Framework\Oauth\Helper\Oauth;
@@ -31,6 +32,8 @@
*/
class OauthTest extends TestCase
{
+ private const TIMESTAMP_STUB = 1657789046;
+
/** @var ConsumerFactory */
private $_consumerFactory;
@@ -145,11 +148,11 @@ protected function setUp(): void
)
->addMethods(
[
- 'getType',
- 'getToken',
- 'getSecret',
- 'getConsumerId',
- 'getRevoked'
+ 'getType',
+ 'getToken',
+ 'getSecret',
+ 'getConsumerId',
+ 'getRevoked'
]
)
->getMock();
@@ -157,12 +160,14 @@ protected function setUp(): void
$this->_oauthHelperMock = $this->getMockBuilder(Oauth::class)
->setConstructorArgs([new Random()])
->getMock();
- $this->_httpUtilityMock = $this->getMockBuilder(\Zend_Oauth_Http_Utility::class)
+ $this->_httpUtilityMock = $this->getMockBuilder(Utility::class)
->onlyMethods(['sign'])
->getMock();
$this->_dateMock = $this->getMockBuilder(DateTime::class)
->disableOriginalConstructor()
->getMock();
+ $this->_dateMock->method('timestamp')
+ ->willReturn(self::TIMESTAMP_STUB);
$this->_loggerMock = $this->getMockBuilder(LoggerInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
@@ -821,7 +826,7 @@ public function testBuildAuthorizationHeader()
$oauthHeader = $this->_oauth->buildAuthorizationHeader($request, $requestUrl);
$expectedHeader = 'OAuth oauth_nonce="tyukmnjhgfdcvxstyuioplkmnhtfvert",' .
- 'oauth_timestamp="",' .
+ 'oauth_timestamp="' . self::TIMESTAMP_STUB . '",' .
'oauth_version="1.0",oauth_consumer_key="edf957ef88492f0a32eb7e1731e85da2",' .
'oauth_consumer_secret="asdawwewefrtyh2f0a32eb7e1731e85d",' .
'oauth_token="7c0709f789e1f38a17aa4b9a28e1b06c",' .
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/acl.xml b/app/code/Magento/Integration/etc/acl.xml
index 51eb078bd1df7..7a9bd084c9878 100644
--- a/app/code/Magento/Integration/etc/acl.xml
+++ b/app/code/Magento/Integration/etc/acl.xml
@@ -14,6 +14,14 @@
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ %customer_id%
+
+
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/Test/Mftf/Section/LayeredNavigationSection/StorefrontLayeredNavigationSection.xml b/app/code/Magento/LayeredNavigation/Test/Mftf/Section/LayeredNavigationSection/StorefrontLayeredNavigationSection.xml
index d8a103116ef06..5f74a0c044672 100644
--- a/app/code/Magento/LayeredNavigation/Test/Mftf/Section/LayeredNavigationSection/StorefrontLayeredNavigationSection.xml
+++ b/app/code/Magento/LayeredNavigation/Test/Mftf/Section/LayeredNavigationSection/StorefrontLayeredNavigationSection.xml
@@ -11,5 +11,11 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobileTest.xml b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobileTest.xml
index 2443af2575018..e113a4cda82e7 100644
--- a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobileTest.xml
+++ b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobileTest.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/StorefrontAllAttributeOptionsAreShownInLayeredNavigationTest.xml b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/StorefrontAllAttributeOptionsAreShownInLayeredNavigationTest.xml
index 59cd3dc83e72d..e7da263a64776 100644
--- a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/StorefrontAllAttributeOptionsAreShownInLayeredNavigationTest.xml
+++ b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/StorefrontAllAttributeOptionsAreShownInLayeredNavigationTest.xml
@@ -18,7 +18,7 @@
-
+
diff --git a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/StorefrontDropdownAttributeInLayeredNavigationTest.xml b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/StorefrontDropdownAttributeInLayeredNavigationTest.xml
index db4bb6b02e01a..6e256d3b2c7df 100644
--- a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/StorefrontDropdownAttributeInLayeredNavigationTest.xml
+++ b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/StorefrontDropdownAttributeInLayeredNavigationTest.xml
@@ -18,7 +18,7 @@
-
+
diff --git a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/StorefrontFilterableProductAttributeInLayeredNavigationWithoutReindexTest.xml b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/StorefrontFilterableProductAttributeInLayeredNavigationWithoutReindexTest.xml
index 6f746696ebe76..8be75b811b84f 100644
--- a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/StorefrontFilterableProductAttributeInLayeredNavigationWithoutReindexTest.xml
+++ b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/StorefrontFilterableProductAttributeInLayeredNavigationWithoutReindexTest.xml
@@ -17,7 +17,7 @@
-
+
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/Test/Mftf/Test/AdminLoginAsCustomerReorderTest.xml b/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminLoginAsCustomerReorderTest.xml
index e570b833b3cdf..37d9932ec1b71 100644
--- a/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminLoginAsCustomerReorderTest.xml
+++ b/app/code/Magento/LoginAsCustomer/Test/Mftf/Test/AdminLoginAsCustomerReorderTest.xml
@@ -19,6 +19,7 @@
+
+
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/Test/_files/empty_iptc.png b/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_iptc.png
index 129c49a1b7e64..66ca9d85b4f4f 100644
Binary files a/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_iptc.png and b/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_iptc.png differ
diff --git a/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_xmp_image.png b/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_xmp_image.png
index 7e81891ebc0ee..66ca9d85b4f4f 100644
Binary files a/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_xmp_image.png and b/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_xmp_image.png differ
diff --git a/app/code/Magento/MediaGalleryMetadata/Test/_files/itxt_with_empty_xmp_image.png b/app/code/Magento/MediaGalleryMetadata/Test/_files/itxt_with_empty_xmp_image.png
index 62d5d4347cb38..66ca9d85b4f4f 100644
Binary files a/app/code/Magento/MediaGalleryMetadata/Test/_files/itxt_with_empty_xmp_image.png and b/app/code/Magento/MediaGalleryMetadata/Test/_files/itxt_with_empty_xmp_image.png differ
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/Test/_files/magento_large_image.jpg b/app/code/Magento/MediaGalleryRenditions/Test/_files/magento_large_image.jpg
index c377daf8fb0b3..5d8db2098e007 100644
Binary files a/app/code/Magento/MediaGalleryRenditions/Test/_files/magento_large_image.jpg and b/app/code/Magento/MediaGalleryRenditions/Test/_files/magento_large_image.jpg differ
diff --git a/app/code/Magento/MediaGalleryRenditions/Test/_files/magento_medium_image.jpg b/app/code/Magento/MediaGalleryRenditions/Test/_files/magento_medium_image.jpg
index 6dc8cd69e41c1..b5d7968c1f1ae 100644
Binary files a/app/code/Magento/MediaGalleryRenditions/Test/_files/magento_medium_image.jpg and b/app/code/Magento/MediaGalleryRenditions/Test/_files/magento_medium_image.jpg differ
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/Test/Integration/_files/magento.jpg b/app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento.jpg
index c377daf8fb0b3..5d8db2098e007 100644
Binary files a/app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento.jpg and b/app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento.jpg differ
diff --git a/app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento_2.jpg b/app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento_2.jpg
index c377daf8fb0b3..5d8db2098e007 100644
Binary files a/app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento_2.jpg and b/app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento_2.jpg differ
diff --git a/app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento_3.png b/app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento_3.png
index 366b1b8b9c3f7..c3eb1be7d37ab 100644
Binary files a/app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento_3.png and b/app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento_3.png differ
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/Setup/Patch/Data/AddMediaGalleryPermissions.php b/app/code/Magento/MediaGalleryUi/Setup/Patch/Data/AddMediaGalleryPermissions.php
index e72017e20a7f6..7fa26ad7b74e8 100644
--- a/app/code/Magento/MediaGalleryUi/Setup/Patch/Data/AddMediaGalleryPermissions.php
+++ b/app/code/Magento/MediaGalleryUi/Setup/Patch/Data/AddMediaGalleryPermissions.php
@@ -7,12 +7,12 @@
namespace Magento\MediaGalleryUi\Setup\Patch\Data;
+use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\PatchVersionInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
-use Magento\Framework\Setup\ModuleDataSetupInterface;
/**
- * Patch is mechanism, that allows to do atomic upgrade data changes
+ * Add child resources permissions for user roles with Magento_Cms::media_gallery permission
*/
class AddMediaGalleryPermissions implements
DataPatchInterface,
@@ -32,7 +32,7 @@ public function __construct(ModuleDataSetupInterface $moduleDataSetup)
}
/**
- * Add child resources permissions for user roles with Magento_Cms::media_gallery permission
+ * @inheritDoc
*/
public function apply(): void
{
diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminOpenMediaGalleryFromPageNoEditorActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminOpenMediaGalleryFromPageNoEditorActionGroup.xml
index 06096e6856016..b1ca72c5161a1 100644
--- a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminOpenMediaGalleryFromPageNoEditorActionGroup.xml
+++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminOpenMediaGalleryFromPageNoEditorActionGroup.xml
@@ -11,7 +11,7 @@
-
+
diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminMediaGalleryFolderSection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminMediaGalleryFolderSection.xml
index a49ab74ecd198..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 @@
+
@@ -22,5 +23,13 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySwitchingBetweenViewsTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySwitchingBetweenViewsTest.xml
index 383952f44ef47..25d6c1f74acb3 100644
--- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySwitchingBetweenViewsTest.xml
+++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySwitchingBetweenViewsTest.xml
@@ -18,6 +18,9 @@
+
+ Skipped
+
diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/UserDeletesFolderFromMediaGalleryTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/UserDeletesFolderFromMediaGalleryTest.xml
new file mode 100755
index 0000000000000..91478877cfe50
--- /dev/null
+++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/UserDeletesFolderFromMediaGalleryTest.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+ [
+ '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/MediaGalleryUi/view/adminhtml/web/images/3-dots.png b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/images/3-dots.png
index 601ba415f2446..a4ff6c843404f 100644
Binary files a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/images/3-dots.png and b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/images/3-dots.png differ
diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/images/Astock.png b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/images/Astock.png
index db5cda9c5512b..ac48d574def9e 100644
Binary files a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/images/Astock.png and b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/images/Astock.png differ
diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/images/d.png b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/images/d.png
index 6516e915624c3..da885c9f15417 100644
Binary files a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/images/d.png and b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/images/d.png differ
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/Model/File/Uploader.php b/app/code/Magento/MediaStorage/Model/File/Uploader.php
index 173211dfac011..31fd9554139b1 100644
--- a/app/code/Magento/MediaStorage/Model/File/Uploader.php
+++ b/app/code/Magento/MediaStorage/Model/File/Uploader.php
@@ -6,7 +6,11 @@
namespace Magento\MediaStorage\Model\File;
+use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Exception\FileSystemException;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Filesystem;
use Magento\Framework\Validation\ValidationException;
use Magento\MediaStorage\Model\File\Validator\Image;
@@ -26,7 +30,7 @@ class Uploader extends \Magento\Framework\File\Uploader
protected $_skipDbProcessing = false;
/**
- * Core file storage
+ * File storage
*
* @var \Magento\MediaStorage\Helper\File\Storage
*/
@@ -49,21 +53,31 @@ class Uploader extends \Magento\Framework\File\Uploader
*/
private $imageValidator;
+ /**
+ * @var \Magento\Framework\Filesystem\Directory\WriteInterface
+ */
+ private $varDirectory;
+
/**
* @param string $fileId
* @param \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDb
* @param \Magento\MediaStorage\Helper\File\Storage $coreFileStorage
* @param \Magento\MediaStorage\Model\File\Validator\NotProtectedExtension $validator
+ * @param \Magento\Framework\Filesystem|null $filesystem
*/
public function __construct(
$fileId,
\Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDb,
\Magento\MediaStorage\Helper\File\Storage $coreFileStorage,
- \Magento\MediaStorage\Model\File\Validator\NotProtectedExtension $validator
+ \Magento\MediaStorage\Model\File\Validator\NotProtectedExtension $validator,
+ \Magento\Framework\Filesystem $filesystem = null
) {
$this->_coreFileStorageDb = $coreFileStorageDb;
$this->_coreFileStorage = $coreFileStorage;
$this->_validator = $validator;
+ $filesystem = $filesystem ?: ObjectManager::getInstance()
+ ->get(\Magento\Framework\Filesystem::class);
+ $this->varDirectory = $filesystem->getDirectoryWrite(DirectoryList::VAR_IMPORT_EXPORT);
parent::__construct($fileId);
}
@@ -140,6 +154,48 @@ public function validateFile()
return $this->_file;
}
+ /**
+ * Rename Uploaded File
+ *
+ * @param string $entity
+ * @return void
+ * @throws LocalizedException
+ */
+ public function renameFile(string $entity)
+ {
+ $extension = '';
+ $uploadedFile = '';
+ if ($this->_result !== false) {
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ $extension = pathinfo($this->_result['file'], PATHINFO_EXTENSION);
+ $uploadedFile = $this->_result['path'] . $this->_result['file'];
+ }
+
+ if (!$extension) {
+ $this->varDirectory->delete($uploadedFile);
+ throw new LocalizedException(__('The file you uploaded has no extension.'));
+ }
+ $sourceFile = $this->varDirectory->getAbsolutePath('importexport/') . $entity;
+
+ $sourceFile .= '.' . $extension;
+ $sourceFileRelative = $this->varDirectory->getRelativePath($sourceFile);
+
+ if (strtolower($uploadedFile) != strtolower($sourceFile)) {
+ if ($this->varDirectory->isExist($sourceFileRelative)) {
+ $this->varDirectory->delete($sourceFileRelative);
+ }
+
+ try {
+ $this->varDirectory->renameFile(
+ $this->varDirectory->getRelativePath($uploadedFile),
+ $sourceFileRelative
+ );
+ } catch (FileSystemException $e) {
+ throw new LocalizedException(__('The source file moving process failed.'));
+ }
+ }
+ }
+
/**
* @inheritDoc
* @since 100.4.0
diff --git a/app/code/Magento/MediaStorage/Model/File/Validator/AvailablePath.php b/app/code/Magento/MediaStorage/Model/File/Validator/AvailablePath.php
index c920beb070521..f931c1ab6791e 100644
--- a/app/code/Magento/MediaStorage/Model/File/Validator/AvailablePath.php
+++ b/app/code/Magento/MediaStorage/Model/File/Validator/AvailablePath.php
@@ -26,7 +26,9 @@
*/
namespace Magento\MediaStorage\Model\File\Validator;
-class AvailablePath extends \Zend_Validate_Abstract
+use Laminas\Validator\AbstractValidator;
+
+class AvailablePath extends AbstractValidator
{
public const PROTECTED_PATH = 'protectedPath';
@@ -39,7 +41,7 @@ class AvailablePath extends \Zend_Validate_Abstract
*
* @var string
*/
- protected $_value;
+ protected $value;
/**
* @var string[]
@@ -58,12 +60,18 @@ class AvailablePath extends \Zend_Validate_Abstract
*/
protected $_pathsData;
+ /**
+ * @var array
+ */
+ protected $messageTemplates;
+
/**
* Construct
*/
public function __construct()
{
$this->_initMessageTemplates();
+ parent::__construct();
}
/**
@@ -73,8 +81,8 @@ public function __construct()
*/
protected function _initMessageTemplates()
{
- if (!$this->_messageTemplates) {
- $this->_messageTemplates = [
+ if (!$this->messageTemplates) {
+ $this->messageTemplates = [
self::PROTECTED_PATH => __('Path "%value%" is protected and cannot be used.'),
self::NOT_AVAILABLE_PATH => __('Path "%value%" is not available and cannot be used.'),
self::PROTECTED_LFI => __('Path "%value%" may not include parent directory traversal ("../", "..\\").'),
@@ -193,20 +201,20 @@ public function getAvailablePaths()
public function isValid($value)
{
$value = $value !== null ? trim($value) : '';
- $this->_setValue($value);
+ $this->setValue($value);
if (!$this->_availablePaths && !$this->_protectedPaths) {
// phpcs:ignore Magento2.Exceptions.DirectThrow
throw new \Exception(__('Please set available and/or protected paths list(s) before validation.'));
}
- if ($this->_value && preg_match('#\.\.[\\\/]#', $this->_value)) {
- $this->_error(self::PROTECTED_LFI, $this->_value);
+ if ($this->value && preg_match('#\.\.[\\\/]#', $this->value)) {
+ $this->error(self::PROTECTED_LFI, $this->value);
return false;
}
//validation
- $value = str_replace('\\', '/', $this->_value ?? '');
+ $value = str_replace('\\', '/', $this->value ?? '');
// phpcs:disable Magento2.Functions.DiscouragedFunction
$valuePathInfo = pathinfo(ltrim($value, '\\/'));
if ($valuePathInfo['dirname'] == '.' || $valuePathInfo['dirname'] == '/') {
@@ -214,11 +222,11 @@ public function isValid($value)
}
if ($this->_protectedPaths && !$this->_isValidByPaths($valuePathInfo, $this->_protectedPaths, true)) {
- $this->_error(self::PROTECTED_PATH, $this->_value);
+ $this->error(self::PROTECTED_PATH, $this->value);
return false;
}
if ($this->_availablePaths && !$this->_isValidByPaths($valuePathInfo, $this->_availablePaths, false)) {
- $this->_error(self::NOT_AVAILABLE_PATH, $this->_value);
+ $this->error(self::NOT_AVAILABLE_PATH, $this->value);
return false;
}
diff --git a/app/code/Magento/MediaStorage/Model/File/Validator/Image.php b/app/code/Magento/MediaStorage/Model/File/Validator/Image.php
index aa66010647d7b..6b022e18a7960 100644
--- a/app/code/Magento/MediaStorage/Model/File/Validator/Image.php
+++ b/app/code/Magento/MediaStorage/Model/File/Validator/Image.php
@@ -7,6 +7,7 @@
namespace Magento\MediaStorage\Model\File\Validator;
+use Laminas\Validator\AbstractValidator;
use Magento\Framework\File\Mime;
use Magento\Framework\Filesystem\Driver\File;
use Magento\Framework\Image\Factory;
@@ -14,7 +15,7 @@
/**
* Image validator
*/
-class Image extends \Zend_Validate_Abstract
+class Image extends AbstractValidator
{
/**
* @var array
@@ -57,6 +58,8 @@ public function __construct(
$this->fileMime = $fileMime;
$this->imageFactory = $imageFactory;
$this->file = $file;
+
+ parent::__construct();
}
/**
diff --git a/app/code/Magento/MediaStorage/Model/File/Validator/NotProtectedExtension.php b/app/code/Magento/MediaStorage/Model/File/Validator/NotProtectedExtension.php
index 18a2e5e6de54c..608151ff0a41a 100644
--- a/app/code/Magento/MediaStorage/Model/File/Validator/NotProtectedExtension.php
+++ b/app/code/Magento/MediaStorage/Model/File/Validator/NotProtectedExtension.php
@@ -6,27 +6,29 @@
namespace Magento\MediaStorage\Model\File\Validator;
+use Laminas\Validator\AbstractValidator;
+
/**
* Validator for check not protected file extensions
*/
-class NotProtectedExtension extends \Zend_Validate_Abstract
+class NotProtectedExtension extends AbstractValidator
{
/**
* Protected extension message key
*/
- const PROTECTED_EXTENSION = 'protectedExtension';
+ public const PROTECTED_EXTENSION = 'protectedExtension';
/**
* Protected files config path
*/
- const XML_PATH_PROTECTED_FILE_EXTENSIONS = 'general/file/protected_extensions';
+ public const XML_PATH_PROTECTED_FILE_EXTENSIONS = 'general/file/protected_extensions';
/**
* The file extension
*
* @var string
*/
- protected $_value;
+ protected $value;
/**
* Protected file types
@@ -41,6 +43,10 @@ class NotProtectedExtension extends \Zend_Validate_Abstract
* @var \Magento\Framework\App\Config\ScopeConfigInterface
*/
protected $_scopeConfig;
+ /**
+ * @var array
+ */
+ protected $messageTemplates;
/**
* Init validator
@@ -52,6 +58,7 @@ public function __construct(\Magento\Framework\App\Config\ScopeConfigInterface $
$this->_scopeConfig = $scopeConfig;
$this->_initMessageTemplates();
$this->_initProtectedFileExtensions();
+ parent::__construct();
}
/**
@@ -61,8 +68,8 @@ public function __construct(\Magento\Framework\App\Config\ScopeConfigInterface $
*/
protected function _initMessageTemplates()
{
- if (!$this->_messageTemplates) {
- $this->_messageTemplates = [
+ if (!$this->messageTemplates) {
+ $this->messageTemplates = [
self::PROTECTED_EXTENSION => __('File with an extension "%value%" is protected and cannot be uploaded'),
];
}
@@ -117,10 +124,10 @@ public function getProtectedFileExtensions($store = null)
public function isValid($value)
{
$value = strtolower(trim($value));
- $this->_setValue($value);
+ $this->setValue($value);
- if (in_array($this->_value, $this->_protectedFileExtensions)) {
- $this->_error(self::PROTECTED_EXTENSION, $this->_value);
+ if (in_array($this->value, $this->_protectedFileExtensions)) {
+ $this->error(self::PROTECTED_EXTENSION, $this->value);
return false;
}
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/Model/Cart/Controller/MiniCartPlugin.php b/app/code/Magento/Multishipping/Model/Cart/Controller/MiniCartPlugin.php
index 6c88231540c82..dccb19482646b 100644
--- a/app/code/Magento/Multishipping/Model/Cart/Controller/MiniCartPlugin.php
+++ b/app/code/Magento/Multishipping/Model/Cart/Controller/MiniCartPlugin.php
@@ -8,6 +8,7 @@
namespace Magento\Multishipping\Model\Cart\Controller;
use Magento\Checkout\Controller\Sidebar\UpdateItemQty;
+use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Multishipping\Model\Cart\MultishippingClearItemAddress;
@@ -34,13 +35,13 @@ public function __construct(
/**
* Cleans shipping addresses and item assignments after MultiShipping flow
*
- * @param UpdateItemQty $subject
+ * @param HttpPostActionInterface $subject
* @param RequestInterface $request
* @return void
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @throws LocalizedException
*/
- public function beforeDispatch(UpdateItemQty $subject, RequestInterface $request)
+ public function beforeDispatch(HttpPostActionInterface $subject, RequestInterface $request)
{
$this->multishippingClearItemAddress->clearAddressItem($subject, $request);
}
diff --git a/app/code/Magento/Multishipping/Model/Cart/MultishippingClearItemAddress.php b/app/code/Magento/Multishipping/Model/Cart/MultishippingClearItemAddress.php
index 114ae81b69f76..5863a17012b96 100644
--- a/app/code/Magento/Multishipping/Model/Cart/MultishippingClearItemAddress.php
+++ b/app/code/Magento/Multishipping/Model/Cart/MultishippingClearItemAddress.php
@@ -104,6 +104,7 @@ public function clearAddressItem($subject, $request)
$quote = $this->cartRepository->get($quote->getId());
$this->cartmodel->setQuote($quote);
}
+ $this->checkoutSession->clearQuote();
} elseif ($this->disableMultishipping->execute($quote) && $this->isVirtualItemInQuote($quote)) {
$quote->setTotalsCollectedFlag(false);
$this->cartRepository->save($quote);
diff --git a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping/Plugin.php b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping/Plugin.php
index ce2321add3524..514ac82e0c82f 100644
--- a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping/Plugin.php
+++ b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping/Plugin.php
@@ -9,6 +9,7 @@
use Magento\Checkout\Model\Cart\RequestQuantityProcessor;
use Magento\Checkout\Model\Session;
use Magento\Framework\App\RequestInterface;
+use Magento\Framework\Filter\LocalizedToNormalized;
use Magento\Framework\Locale\ResolverInterface;
use Magento\Quote\Api\CartRepositoryInterface;
@@ -111,7 +112,7 @@ private function recollectCartSummary($quantity, CustomerCart $cart): void
{
$quote = $cart->getQuote();
- $filter = new \Zend_Filter_LocalizedToNormalized(
+ $filter = new LocalizedToNormalized(
['locale' => $this->locale->getLocale()]
);
$newQty = $this->quantityProcessor->prepareQuantity($quantity);
diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/StoreFrontAssertCustomerShippingAddressOrderViewPageActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/StoreFrontAssertCustomerShippingAddressOrderViewPageActionGroup.xml
new file mode 100644
index 0000000000000..e2063843000f4
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/StoreFrontAssertCustomerShippingAddressOrderViewPageActionGroup.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+ Assert that shipping Address block contains provided Address data.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/PaymentMethodSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/PaymentMethodSection.xml
index 6bdca5de480c3..d34603ca07136 100644
--- a/app/code/Magento/Multishipping/Test/Mftf/Section/PaymentMethodSection.xml
+++ b/app/code/Magento/Multishipping/Test/Mftf/Section/PaymentMethodSection.xml
@@ -12,5 +12,6 @@
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutAddressesToolbarSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutAddressesToolbarSection.xml
index f238afe672156..cf6bd10b0e8df 100644
--- a/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutAddressesToolbarSection.xml
+++ b/app/code/Magento/Multishipping/Test/Mftf/Section/StorefrontMultishippingCheckoutAddressesToolbarSection.xml
@@ -10,5 +10,13 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/AdminInvoiceCheckingWithMultishipmentWithMultipleTaxTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/AdminInvoiceCheckingWithMultishipmentWithMultipleTaxTest.xml
new file mode 100644
index 0000000000000..e7058304f8604
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/Test/AdminInvoiceCheckingWithMultishipmentWithMultipleTaxTest.xml
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontVerifyMultishippingCheckoutForVirtualProductTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontVerifyMultishippingCheckoutForVirtualProductTest.xml
new file mode 100644
index 0000000000000..8108de8f9e2de
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontVerifyMultishippingCheckoutForVirtualProductTest.xml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A shipping selection is not applicable.
+ $grabshippingtext
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontCheckoutMiniCartSubtotalMatchesAfterRemoveOneProductFromCartTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontCheckoutMiniCartSubtotalMatchesAfterRemoveOneProductFromCartTest.xml
new file mode 100644
index 0000000000000..a0f000b6abd58
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontCheckoutMiniCartSubtotalMatchesAfterRemoveOneProductFromCartTest.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontCheckoutWithMultishippingIfMaximumQtyLimitWasReachedTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontCheckoutWithMultishippingIfMaximumQtyLimitWasReachedTest.xml
new file mode 100644
index 0000000000000..065d435b9e438
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontCheckoutWithMultishippingIfMaximumQtyLimitWasReachedTest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontMultishippingWithCartPriceRuleMatchingTotalItemsQtyTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontMultishippingWithCartPriceRuleMatchingTotalItemsQtyTest.xml
new file mode 100644
index 0000000000000..d072cafd8aa59
--- /dev/null
+++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontMultishippingWithCartPriceRuleMatchingTotalItemsQtyTest.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Multishipping/Test/Unit/Model/Cart/MultishippingClearItemAddressTest.php b/app/code/Magento/Multishipping/Test/Unit/Model/Cart/MultishippingClearItemAddressTest.php
index 45d9f958d5e51..309c92054446e 100644
--- a/app/code/Magento/Multishipping/Test/Unit/Model/Cart/MultishippingClearItemAddressTest.php
+++ b/app/code/Magento/Multishipping/Test/Unit/Model/Cart/MultishippingClearItemAddressTest.php
@@ -100,7 +100,8 @@ public function testClearAddressItem(
->willReturn($actionName);
$this->checkoutSessionMock->method('getQuote')
->willReturn($quoteMock);
-
+ $this->checkoutSessionMock->method('clearQuote')
+ ->willReturnSelf();
$addressMock = $this->createMock(Address::class);
$addressMock->method('getId')
->willReturn($addressId);
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/Multishipping/etc/frontend/di.xml b/app/code/Magento/Multishipping/etc/frontend/di.xml
index d4b5e1654c436..30437d508668c 100644
--- a/app/code/Magento/Multishipping/etc/frontend/di.xml
+++ b/app/code/Magento/Multishipping/etc/frontend/di.xml
@@ -46,7 +46,10 @@
-
+
+
+
+
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/Model/Apm/Deployments.php b/app/code/Magento/NewRelicReporting/Model/Apm/Deployments.php
index ce37f4411dcca..2b9013d594709 100644
--- a/app/code/Magento/NewRelicReporting/Model/Apm/Deployments.php
+++ b/app/code/Magento/NewRelicReporting/Model/Apm/Deployments.php
@@ -5,7 +5,12 @@
*/
namespace Magento\NewRelicReporting\Model\Apm;
-use \Magento\Framework\HTTP\ZendClient;
+use Laminas\Http\Exception\RuntimeException;
+use Laminas\Http\Request;
+use Magento\Framework\HTTP\LaminasClient;
+use Magento\Framework\HTTP\LaminasClientFactory;
+use Magento\NewRelicReporting\Model\Config;
+use Psr\Log\LoggerInterface;
/**
* Performs the request to make the deployment
@@ -18,31 +23,31 @@ class Deployments
private const API_URL = 'https://api.newrelic.com/v2/applications/%s/deployments.json';
/**
- * @var \Magento\NewRelicReporting\Model\Config
+ * @var Config
*/
protected $config;
/**
- * @var \Psr\Log\LoggerInterface
+ * @var LoggerInterface
*/
protected $logger;
/**
- * @var \Magento\Framework\HTTP\ZendClientFactory $clientFactory
+ * @var LaminasClientFactory $clientFactory
*/
protected $clientFactory;
/**
* Constructor
*
- * @param \Magento\NewRelicReporting\Model\Config $config
- * @param \Psr\Log\LoggerInterface $logger
- * @param \Magento\Framework\HTTP\ZendClientFactory $clientFactory
+ * @param Config $config
+ * @param LoggerInterface $logger
+ * @param LaminasClientFactory $clientFactory
*/
public function __construct(
- \Magento\NewRelicReporting\Model\Config $config,
- \Psr\Log\LoggerInterface $logger,
- \Magento\Framework\HTTP\ZendClientFactory $clientFactory
+ Config $config,
+ LoggerInterface $logger,
+ LaminasClientFactory $clientFactory
) {
$this->config = $config;
$this->logger = $logger;
@@ -69,11 +74,10 @@ public function setDeployment($description, $change = false, $user = false, $rev
$apiUrl = sprintf($apiUrl, $this->config->getNewRelicAppId());
- /** @var \Magento\Framework\HTTP\ZendClient $client */
+ /** @var LaminasClient $client */
$client = $this->clientFactory->create();
$client->setUri($apiUrl);
- $client->setMethod(ZendClient::POST);
-
+ $client->setMethod(Request::METHOD_POST);
$client->setHeaders(
[
'Api-Key' => $this->config->getNewRelicApiKey(),
@@ -97,13 +101,13 @@ public function setDeployment($description, $change = false, $user = false, $rev
$client->setParameterPost($params);
try {
- $response = $client->request();
- } catch (\Zend_Http_Client_Exception $e) {
+ $response = $client->send();
+ } catch (RuntimeException $e) {
$this->logger->critical($e);
return false;
}
- if ($response->getStatus() < 200 || $response->getStatus() > 210) {
+ if ($response->getStatusCode() < 200 || $response->getStatusCode() > 210) {
$this->logger->warning('Deployment marker request did not send a 200 status code.');
return false;
}
diff --git a/app/code/Magento/NewRelicReporting/Model/CronEvent.php b/app/code/Magento/NewRelicReporting/Model/CronEvent.php
index 696c7132c5e49..1231e5b9324a7 100644
--- a/app/code/Magento/NewRelicReporting/Model/CronEvent.php
+++ b/app/code/Magento/NewRelicReporting/Model/CronEvent.php
@@ -5,12 +5,17 @@
*/
namespace Magento\NewRelicReporting\Model;
-use \Magento\Framework\HTTP\ZendClient;
+use Laminas\Http\Request;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\HTTP\LaminasClient;
+use Magento\Framework\HTTP\LaminasClientFactory;
+use Magento\Framework\Json\EncoderInterface;
+use Magento\Framework\Json\Helper\Data;
class CronEvent
{
/**
- * @var \Magento\Framework\HTTP\ZendClient
+ * @var LaminasClient
*/
protected $request;
@@ -29,31 +34,31 @@ class CronEvent
protected $customParameters = [];
/**
- * @var \Magento\NewRelicReporting\Model\Config
+ * @var Config
*/
protected $config;
/**
- * @var \Magento\Framework\Json\Helper\Data
+ * @var Data
*/
protected $jsonEncoder;
/**
- * @var \Magento\Framework\HTTP\ZendClientFactory $clientFactory
+ * @var LaminasClientFactory $clientFactory
*/
protected $clientFactory;
/**
* Constructor
*
- * @param \Magento\NewRelicReporting\Model\Config $config
- * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder
- * @param \Magento\Framework\HTTP\ZendClientFactory $clientFactory
+ * @param Config $config
+ * @param EncoderInterface $jsonEncoder
+ * @param LaminasClientFactory $clientFactory
*/
public function __construct(
- \Magento\NewRelicReporting\Model\Config $config,
- \Magento\Framework\Json\EncoderInterface $jsonEncoder,
- \Magento\Framework\HTTP\ZendClientFactory $clientFactory
+ Config $config,
+ EncoderInterface $jsonEncoder,
+ LaminasClientFactory $clientFactory
) {
$this->config = $config;
$this->jsonEncoder = $jsonEncoder;
@@ -64,14 +69,14 @@ public function __construct(
* Returns Insights API url with account id
*
* @return string
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException
*/
protected function getEventsUrl()
{
if (empty($this->eventsUrl)) {
$accountId = $this->config->getNewRelicAccountId();
if (empty($accountId)) {
- throw new \Magento\Framework\Exception\LocalizedException(__(
+ throw new LocalizedException(__(
'No New Relic Application ID configured, cannot continue with Cron Event reporting'
));
}
@@ -86,7 +91,7 @@ protected function getEventsUrl()
/**
* Returns HTTP request to events url
*
- * @return \Magento\Framework\HTTP\ZendClient
+ * @return LaminasClient
*/
protected function getRequest()
{
@@ -95,7 +100,7 @@ protected function getRequest()
$this->request->setUri($this->getEventsUrl());
$insertKey = $this->config->getInsightsInsertKey();
- $this->request->setMethod(ZendClient::POST);
+ $this->request->setMethod(Request::METHOD_POST);
$this->request->setHeaders(
[
'X-Insert-Key' => $insertKey,
@@ -135,7 +140,7 @@ protected function getJsonForResponse()
* Add custom parameters to send with API request
*
* @param array $data
- * @return \Magento\NewRelicReporting\Model\CronEvent $this
+ * @return CronEvent $this
*/
public function addData(array $data)
{
@@ -150,11 +155,9 @@ public function addData(array $data)
*/
public function sendRequest()
{
- $response = $this->getRequest()
- ->setRawData($this->getJsonForResponse())
- ->request();
-
- if ($response->getStatus() >= 200 && $response->getStatus() < 300) {
+ $this->getRequest()->setRawBody($this->getJsonForResponse());
+ $response = $this->getRequest()->send();
+ if ($response->getStatusCode() >= 200 && $response->getStatusCode() < 300) {
return true;
}
return false;
diff --git a/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php b/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php
index fa7f2f1090629..20cee6087e6e5 100644
--- a/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php
+++ b/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php
@@ -5,7 +5,7 @@
*/
namespace Magento\NewRelicReporting\Model;
-use Exception;
+use Throwable;
/**
* Wrapper for New Relic functions
@@ -33,10 +33,10 @@ public function addCustomParameter($param, $value)
/**
* Wrapper for 'newrelic_notice_error' function
*
- * @param Exception $exception
+ * @param Throwable $exception
* @return void
*/
- public function reportError(Exception $exception)
+ public function reportError(Throwable $exception)
{
if ($this->isExtensionInstalled()) {
newrelic_notice_error($exception->getMessage(), $exception);
diff --git a/app/code/Magento/NewRelicReporting/Test/Unit/Model/Apm/DeploymentsTest.php b/app/code/Magento/NewRelicReporting/Test/Unit/Model/Apm/DeploymentsTest.php
index 7744a01021e78..ae632441f1074 100644
--- a/app/code/Magento/NewRelicReporting/Test/Unit/Model/Apm/DeploymentsTest.php
+++ b/app/code/Magento/NewRelicReporting/Test/Unit/Model/Apm/DeploymentsTest.php
@@ -7,8 +7,11 @@
namespace Magento\NewRelicReporting\Test\Unit\Model\Apm;
-use Magento\Framework\HTTP\ZendClient;
-use Magento\Framework\HTTP\ZendClientFactory;
+use Laminas\Http\Exception\RuntimeException;
+use Laminas\Http\Request;
+use Laminas\Http\Response;
+use Magento\Framework\HTTP\LaminasClient;
+use Magento\Framework\HTTP\LaminasClientFactory;
use Magento\NewRelicReporting\Model\Apm\Deployments;
use Magento\NewRelicReporting\Model\Config;
use PHPUnit\Framework\MockObject\MockObject;
@@ -28,14 +31,14 @@ class DeploymentsTest extends TestCase
protected $configMock;
/**
- * @var ZendClientFactory|MockObject
+ * @var LaminasClientFactory|MockObject
*/
- protected $zendClientFactoryMock;
+ protected $httpClientFactoryMock;
/**
- * @var ZendClient|MockObject
+ * @var LaminasClient|MockObject
*/
- protected $zendClientMock;
+ protected $httpClientMock;
/**
* @var LoggerInterface|MockObject
@@ -44,13 +47,13 @@ class DeploymentsTest extends TestCase
protected function setUp(): void
{
- $this->zendClientFactoryMock = $this->getMockBuilder(ZendClientFactory::class)
+ $this->httpClientFactoryMock = $this->getMockBuilder(LaminasClientFactory::class)
->setMethods(['create'])
->disableOriginalConstructor()
->getMock();
- $this->zendClientMock = $this->getMockBuilder(ZendClient::class)
- ->setMethods(['request', 'setUri', 'setMethod', 'setHeaders', 'setParameterPost'])
+ $this->httpClientMock = $this->getMockBuilder(LaminasClient::class)
+ ->setMethods(['send', 'setUri', 'setMethod', 'setHeaders', 'setParameterPost'])
->disableOriginalConstructor()
->getMock();
@@ -66,7 +69,7 @@ protected function setUp(): void
$this->model = new Deployments(
$this->configMock,
$this->loggerMock,
- $this->zendClientFactoryMock
+ $this->httpClientFactoryMock
);
}
@@ -79,22 +82,22 @@ public function testSetDeploymentRequestOk()
{
$data = $this->getDataVariables();
- $this->zendClientMock->expects($this->once())
+ $this->httpClientMock->expects($this->once())
->method('setUri')
->with($data['self_uri'])
->willReturnSelf();
- $this->zendClientMock->expects($this->once())
+ $this->httpClientMock->expects($this->once())
->method('setMethod')
->with($data['method'])
->willReturnSelf();
- $this->zendClientMock->expects($this->once())
+ $this->httpClientMock->expects($this->once())
->method('setHeaders')
->with($data['headers'])
->willReturnSelf();
- $this->zendClientMock->expects($this->once())
+ $this->httpClientMock->expects($this->once())
->method('setParameterPost')
->with($data['params'])
->willReturnSelf();
@@ -113,18 +116,18 @@ public function testSetDeploymentRequestOk()
->method('getNewRelicAppId')
->willReturn($data['app_id']);
- $zendHttpResponseMock = $this->getMockBuilder(
- \Zend_Http_Response::class
+ $httpResponseMock = $this->getMockBuilder(
+ Response::class
)->disableOriginalConstructor()
->getMock();
- $zendHttpResponseMock->expects($this->any())->method('getStatus')->willReturn($data['status_ok']);
- $zendHttpResponseMock->expects($this->once())->method('getBody')->willReturn($data['response_body']);
+ $httpResponseMock->expects($this->any())->method('getStatusCode')->willReturn($data['status_ok']);
+ $httpResponseMock->expects($this->once())->method('getBody')->willReturn($data['response_body']);
- $this->zendClientMock->expects($this->once())->method('request')->willReturn($zendHttpResponseMock);
+ $this->httpClientMock->expects($this->once())->method('send')->willReturn($httpResponseMock);
- $this->zendClientFactoryMock->expects($this->once())
+ $this->httpClientFactoryMock->expects($this->once())
->method('create')
- ->willReturn($this->zendClientMock);
+ ->willReturn($this->httpClientMock);
$this->assertIsString(
$this->model->setDeployment(
@@ -145,22 +148,22 @@ public function testSetDeploymentBadStatus()
{
$data = $this->getDataVariables();
- $this->zendClientMock->expects($this->once())
+ $this->httpClientMock->expects($this->once())
->method('setUri')
->with($data['uri'])
->willReturnSelf();
- $this->zendClientMock->expects($this->once())
+ $this->httpClientMock->expects($this->once())
->method('setMethod')
->with($data['method'])
->willReturnSelf();
- $this->zendClientMock->expects($this->once())
+ $this->httpClientMock->expects($this->once())
->method('setHeaders')
->with($data['headers'])
->willReturnSelf();
- $this->zendClientMock->expects($this->once())
+ $this->httpClientMock->expects($this->once())
->method('setParameterPost')
->with($data['params'])
->willReturnSelf();
@@ -177,18 +180,18 @@ public function testSetDeploymentBadStatus()
->method('getNewRelicAppId')
->willReturn($data['app_id']);
- $zendHttpResponseMock = $this->getMockBuilder(
- \Zend_Http_Response::class
+ $httpResponseMock = $this->getMockBuilder(
+ Response::class
)->disableOriginalConstructor()
->getMock();
- $zendHttpResponseMock->expects($this->any())->method('getStatus')->willReturn($data['status_bad']);
+ $httpResponseMock->expects($this->any())->method('getStatusCode')->willReturn($data['status_bad']);
- $this->zendClientMock->expects($this->once())->method('request')->willReturn($zendHttpResponseMock);
+ $this->httpClientMock->expects($this->once())->method('send')->willReturn($httpResponseMock);
$this->loggerMock->expects($this->once())->method('warning');
- $this->zendClientFactoryMock->expects($this->once())
+ $this->httpClientFactoryMock->expects($this->once())
->method('create')
- ->willReturn($this->zendClientMock);
+ ->willReturn($this->httpClientMock);
$this->assertIsBool(
$this->model->setDeployment(
@@ -207,22 +210,22 @@ public function testSetDeploymentRequestFail()
{
$data = $this->getDataVariables();
- $this->zendClientMock->expects($this->once())
+ $this->httpClientMock->expects($this->once())
->method('setUri')
->with($data['uri'])
->willReturnSelf();
- $this->zendClientMock->expects($this->once())
+ $this->httpClientMock->expects($this->once())
->method('setMethod')
->with($data['method'])
->willReturnSelf();
- $this->zendClientMock->expects($this->once())
+ $this->httpClientMock->expects($this->once())
->method('setHeaders')
->with($data['headers'])
->willReturnSelf();
- $this->zendClientMock->expects($this->once())
+ $this->httpClientMock->expects($this->once())
->method('setParameterPost')
->with($data['params'])
->willReturnSelf();
@@ -239,14 +242,14 @@ public function testSetDeploymentRequestFail()
->method('getNewRelicAppId')
->willReturn($data['app_id']);
- $this->zendClientMock->expects($this->once())->method('request')->willThrowException(
- new \Zend_Http_Client_Exception()
+ $this->httpClientMock->expects($this->once())->method('send')->willThrowException(
+ new RuntimeException()
);
$this->loggerMock->expects($this->once())->method('critical');
- $this->zendClientFactoryMock->expects($this->once())
+ $this->httpClientFactoryMock->expects($this->once())
->method('create')
- ->willReturn($this->zendClientMock);
+ ->willReturn($this->httpClientMock);
$this->assertIsBool(
$this->model->setDeployment(
@@ -261,7 +264,7 @@ public function testSetDeploymentRequestFail()
/**
* @return array
*/
- private function getDataVariables()
+ private function getDataVariables(): array
{
$description = 'Event description';
$change = 'flush the cache username';
@@ -271,7 +274,7 @@ private function getDataVariables()
$apiKey = '1234';
$appName = 'app_name';
$appId = 'application_id';
- $method = ZendClient::POST;
+ $method = Request::METHOD_POST;
$headers = ['Api-Key' => $apiKey, 'Content-Type' => 'application/json'];
$responseBody = 'Response body content';
$statusOk = '200';
diff --git a/app/code/Magento/NewRelicReporting/Test/Unit/Model/CronEventTest.php b/app/code/Magento/NewRelicReporting/Test/Unit/Model/CronEventTest.php
index 564c69203b104..31bb76c61ac87 100644
--- a/app/code/Magento/NewRelicReporting/Test/Unit/Model/CronEventTest.php
+++ b/app/code/Magento/NewRelicReporting/Test/Unit/Model/CronEventTest.php
@@ -7,9 +7,11 @@
namespace Magento\NewRelicReporting\Test\Unit\Model;
+use Laminas\Http\Request;
+use Laminas\Http\Response;
use Magento\Framework\Exception\LocalizedException;
-use Magento\Framework\HTTP\ZendClient;
-use Magento\Framework\HTTP\ZendClientFactory;
+use Magento\Framework\HTTP\LaminasClient;
+use Magento\Framework\HTTP\LaminasClientFactory;
use Magento\Framework\Json\EncoderInterface;
use Magento\NewRelicReporting\Model\Config;
use Magento\NewRelicReporting\Model\CronEvent;
@@ -29,14 +31,14 @@ class CronEventTest extends TestCase
protected $configMock;
/**
- * @var ZendClientFactory|MockObject
+ * @var LaminasClientFactory|MockObject
*/
- protected $zendClientFactoryMock;
+ protected $httpClientFactoryMock;
/**
- * @var ZendClient|MockObject
+ * @var LaminasClient|MockObject
*/
- protected $zendClientMock;
+ protected $httpClientMock;
/**
* @var EncoderInterface|MockObject
@@ -45,13 +47,13 @@ class CronEventTest extends TestCase
protected function setUp(): void
{
- $this->zendClientFactoryMock = $this->getMockBuilder(ZendClientFactory::class)
+ $this->httpClientFactoryMock = $this->getMockBuilder(LaminasClientFactory::class)
->setMethods(['create'])
->disableOriginalConstructor()
->getMock();
- $this->zendClientMock = $this->getMockBuilder(ZendClient::class)
- ->setMethods(['request', 'setUri', 'setMethod', 'setHeaders', 'setRawData'])
+ $this->httpClientMock = $this->getMockBuilder(LaminasClient::class)
+ ->setMethods(['send', 'setUri', 'setMethod', 'setHeaders', 'setRawBody'])
->disableOriginalConstructor()
->getMock();
@@ -73,7 +75,7 @@ protected function setUp(): void
$this->model = new CronEvent(
$this->configMock,
$this->jsonEncoderMock,
- $this->zendClientFactoryMock
+ $this->httpClientFactoryMock
);
}
@@ -87,7 +89,7 @@ public function testSendRequestStatusOk()
$json = '{"eventType":"Cron","appName":"app_name","appId":"app_id"}';
$statusOk = '200';
$uri = 'https://example.com/listener';
- $method = ZendClient::POST;
+ $method = Request::METHOD_POST;
$headers = ['X-Insert-Key' => 'insert_key_value', 'Content-Type' => 'application/json'];
$accId = 'acc_id';
$appId = 'app_id';
@@ -96,10 +98,10 @@ public function testSendRequestStatusOk()
$this->model->addData(['eventType'=>'Cron']);
- $this->zendClientMock->expects($this->once())->method('setUri')->with($uri)->willReturnSelf();
- $this->zendClientMock->expects($this->once())->method('setMethod')->with($method)->willReturnSelf();
- $this->zendClientMock->expects($this->once())->method('setHeaders')->with($headers)->willReturnSelf();
- $this->zendClientMock->expects($this->once())->method('setRawData')->with($json)->willReturnSelf();
+ $this->httpClientMock->expects($this->once())->method('setUri')->with($uri)->willReturnSelf();
+ $this->httpClientMock->expects($this->once())->method('setMethod')->with($method)->willReturnSelf();
+ $this->httpClientMock->expects($this->once())->method('setHeaders')->with($headers)->willReturnSelf();
+ $this->httpClientMock->expects($this->once())->method('setRawBody')->with($json)->willReturnSelf();
$this->configMock->expects($this->once())
->method('getNewRelicAccountId')
@@ -123,17 +125,17 @@ public function testSendRequestStatusOk()
$this->jsonEncoderMock->expects($this->once())->method('encode')->willReturn($json);
- $zendHttpResponseMock = $this->getMockBuilder(
- \Zend_Http_Response::class
+ $httpResponseMock = $this->getMockBuilder(
+ Response::class
)->disableOriginalConstructor()
->getMock();
- $zendHttpResponseMock->expects($this->any())->method('getStatus')->willReturn($statusOk);
+ $httpResponseMock->expects($this->any())->method('getStatusCode')->willReturn($statusOk);
- $this->zendClientMock->expects($this->once())->method('request')->willReturn($zendHttpResponseMock);
+ $this->httpClientMock->expects($this->once())->method('send')->willReturn($httpResponseMock);
- $this->zendClientFactoryMock->expects($this->once())
+ $this->httpClientFactoryMock->expects($this->once())
->method('create')
- ->willReturn($this->zendClientMock);
+ ->willReturn($this->httpClientMock);
$this->assertIsBool($this->model->sendRequest());
}
@@ -148,17 +150,17 @@ public function testSendRequestStatusBad()
$json = '{"eventType":"Cron","appName":"app_name","appId":"app_id"}';
$statusBad = '401';
$uri = 'https://example.com/listener';
- $method = ZendClient::POST;
+ $method = Request::METHOD_POST;
$headers = ['X-Insert-Key' => 'insert_key_value', 'Content-Type' => 'application/json'];
$accId = 'acc_id';
$appId = 'app_id';
$appName = 'app_name';
$insightApiKey = 'insert_key_value';
- $this->zendClientMock->expects($this->once())->method('setUri')->with($uri)->willReturnSelf();
- $this->zendClientMock->expects($this->once())->method('setMethod')->with($method)->willReturnSelf();
- $this->zendClientMock->expects($this->once())->method('setHeaders')->with($headers)->willReturnSelf();
- $this->zendClientMock->expects($this->once())->method('setRawData')->with($json)->willReturnSelf();
+ $this->httpClientMock->expects($this->once())->method('setUri')->with($uri)->willReturnSelf();
+ $this->httpClientMock->expects($this->once())->method('setMethod')->with($method)->willReturnSelf();
+ $this->httpClientMock->expects($this->once())->method('setHeaders')->with($headers)->willReturnSelf();
+ $this->httpClientMock->expects($this->once())->method('setRawBody')->with($json)->willReturnSelf();
$this->configMock->expects($this->once())
->method('getNewRelicAccountId')
@@ -182,17 +184,17 @@ public function testSendRequestStatusBad()
$this->jsonEncoderMock->expects($this->once())->method('encode')->willReturn($json);
- $zendHttpResponseMock = $this->getMockBuilder(
- \Zend_Http_Response::class
+ $httpResponseMock = $this->getMockBuilder(
+ Response::class
)->disableOriginalConstructor()
->getMock();
- $zendHttpResponseMock->expects($this->any())->method('getStatus')->willReturn($statusBad);
+ $httpResponseMock->expects($this->any())->method('getStatusCode')->willReturn($statusBad);
- $this->zendClientMock->expects($this->once())->method('request')->willReturn($zendHttpResponseMock);
+ $this->httpClientMock->expects($this->once())->method('send')->willReturn($httpResponseMock);
- $this->zendClientFactoryMock->expects($this->once())
+ $this->httpClientFactoryMock->expects($this->once())
->method('create')
- ->willReturn($this->zendClientMock);
+ ->willReturn($this->httpClientMock);
$this->assertIsBool($this->model->sendRequest());
}
@@ -206,9 +208,9 @@ public function testSendRequestException()
{
$accId = '';
- $this->zendClientFactoryMock->expects($this->once())
+ $this->httpClientFactoryMock->expects($this->once())
->method('create')
- ->willReturn($this->zendClientMock);
+ ->willReturn($this->httpClientMock);
$this->configMock->expects($this->once())
->method('getNewRelicAccountId')
->willReturn($accId);
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/NewRelicReporting/etc/csp_whitelist.xml b/app/code/Magento/NewRelicReporting/etc/csp_whitelist.xml
new file mode 100644
index 0000000000000..88511e22b8419
--- /dev/null
+++ b/app/code/Magento/NewRelicReporting/etc/csp_whitelist.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ *.newrelic.com
+ *.nr-data.net
+
+
+
+
+ *.newrelic.com
+ *.nr-data.net
+
+
+
+
diff --git a/app/code/Magento/Newsletter/Model/Template.php b/app/code/Magento/Newsletter/Model/Template.php
index 549378ab967e1..6c2cfeff857bc 100644
--- a/app/code/Magento/Newsletter/Model/Template.php
+++ b/app/code/Magento/Newsletter/Model/Template.php
@@ -5,6 +5,10 @@
*/
namespace Magento\Newsletter\Model;
+use Magento\Framework\Filter\FilterInput;
+use Magento\Framework\Validator\EmailAddress;
+use Magento\Framework\Validator\IntUtils;
+
/**
* Template model
*
@@ -40,8 +44,9 @@ class Template extends \Magento\Email\Model\AbstractTemplate
/**
* Mail object
*
- * @deprecated 100.3.0 Unused property
* @var string
+ * @deprecated 100.3.0 Unused property
+ * @see no alternatives
*/
protected $_mail;
@@ -139,17 +144,17 @@ protected function _construct()
public function validate()
{
$validators = [
- 'template_code' => [\Zend_Filter_Input::ALLOW_EMPTY => false],
- 'template_type' => 'Int',
- 'template_sender_email' => 'EmailAddress',
- 'template_sender_name' => [\Zend_Filter_Input::ALLOW_EMPTY => false],
+ 'template_code' => [FilterInput::ALLOW_EMPTY => false],
+ 'template_type' => IntUtils::class,
+ 'template_sender_email' => EmailAddress::class,
+ 'template_sender_name' => [FilterInput::ALLOW_EMPTY => false],
];
$data = [];
foreach (array_keys($validators) as $validateField) {
$data[$validateField] = $this->getDataUsingMethod($validateField);
}
- $validateInput = new \Zend_Filter_Input([], $validators, $data);
+ $validateInput = new FilterInput([], $validators, $data);
if (!$validateInput->isValid()) {
$errorMessages = [];
foreach ($validateInput->getMessages() as $messages) {
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 @@
0
offline
-
- offline
- authorize_capture
-
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/OfflineShipping/etc/db_schema.xml b/app/code/Magento/OfflineShipping/etc/db_schema.xml
index 35df088f708a1..707829df5e4cd 100644
--- a/app/code/Magento/OfflineShipping/etc/db_schema.xml
+++ b/app/code/Magento/OfflineShipping/etc/db_schema.xml
@@ -38,8 +38,8 @@
'catalog_search_opensearch_server_hostname',
+ 'port' => 'catalog_search_opensearch_server_port',
+ 'index' => 'catalog_search_opensearch_index_prefix',
+ 'enableAuth' => 'catalog_search_opensearch_enable_auth',
+ 'username' => 'catalog_search_opensearch_username',
+ 'password' => 'catalog_search_opensearch_password',
+ 'timeout' => 'catalog_search_opensearch_server_timeout',
+ ];
+
+ return array_merge(parent::_getFieldMapping(), $fields);
+ }
+}
diff --git a/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/IntegerMapper.php b/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/IntegerMapper.php
new file mode 100644
index 0000000000000..257205a4ef625
--- /dev/null
+++ b/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/IntegerMapper.php
@@ -0,0 +1,31 @@
+ [
+ 'match_mapping_type' => 'long',
+ 'mapping' => [
+ 'type' => 'integer',
+ ],
+ ],
+ ];
+
+ return $templates;
+ }
+}
diff --git a/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/MapperInterface.php b/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/MapperInterface.php
new file mode 100644
index 0000000000000..25ce93ee27df7
--- /dev/null
+++ b/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/MapperInterface.php
@@ -0,0 +1,22 @@
+ [
+ 'match' => 'position_*',
+ 'match_mapping_type' => 'string',
+ 'mapping' => [
+ 'type' => 'integer',
+ 'index' => true,
+ ],
+ ],
+ ];
+
+ return $templates;
+ }
+}
diff --git a/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/PriceMapper.php b/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/PriceMapper.php
new file mode 100644
index 0000000000000..03ced99cc632f
--- /dev/null
+++ b/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/PriceMapper.php
@@ -0,0 +1,33 @@
+ [
+ 'match' => 'price_*',
+ 'match_mapping_type' => 'string',
+ 'mapping' => [
+ 'type' => 'double',
+ 'store' => true,
+ ],
+ ],
+ ];
+
+ return $templates;
+ }
+}
diff --git a/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/StringMapper.php b/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/StringMapper.php
new file mode 100644
index 0000000000000..75be558d8939c
--- /dev/null
+++ b/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplates/StringMapper.php
@@ -0,0 +1,34 @@
+ [
+ 'match' => '*',
+ 'match_mapping_type' => 'string',
+ 'mapping' => [
+ 'type' => 'text',
+ 'index' => true,
+ 'copy_to' => '_search',
+ ],
+ ],
+ ];
+
+ return $templates;
+ }
+}
diff --git a/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplatesProvider.php b/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplatesProvider.php
new file mode 100644
index 0000000000000..58de429814a2e
--- /dev/null
+++ b/app/code/Magento/OpenSearch/Model/Adapter/DynamicTemplatesProvider.php
@@ -0,0 +1,51 @@
+mappers = $mappers;
+ }
+
+ /**
+ * Get Search Engine dynamic templates.
+ *
+ * @return array
+ * @throws InvalidArgumentException
+ */
+ public function getTemplates(): array
+ {
+ $templates = [];
+ foreach ($this->mappers as $mapper) {
+ if (!$mapper instanceof MapperInterface) {
+ throw new InvalidArgumentException(
+ __('Mapper %1 should implement %2', get_class($mapper), MapperInterface::class)
+ );
+ }
+ $templates = $mapper->processTemplates($templates);
+ }
+
+ return $templates;
+ }
+}
diff --git a/app/code/Magento/OpenSearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php b/app/code/Magento/OpenSearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php
new file mode 100644
index 0000000000000..b1aeb012152fe
--- /dev/null
+++ b/app/code/Magento/OpenSearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php
@@ -0,0 +1,47 @@
+baseResolver = $baseResolver;
+ }
+
+ /**
+ * Returns field name.
+ *
+ * @param AttributeAdapter $attribute
+ * @param array $context
+ * @return string|null
+ */
+ public function getFieldName(AttributeAdapter $attribute, $context = []): ?string
+ {
+ $fieldName = $this->baseResolver->getFieldName($attribute, $context);
+ if ($fieldName === '_all') {
+ $fieldName = '_search';
+ }
+
+ return $fieldName;
+ }
+}
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 @@
+ $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/Elasticsearch6/Model/Client/Elasticsearch.php b/app/code/Magento/OpenSearch/Model/SearchClient.php
similarity index 51%
rename from app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php
rename to app/code/Magento/OpenSearch/Model/SearchClient.php
index 2d787f4d4377d..dbce79c2496bb 100644
--- a/app/code/Magento/Elasticsearch6/Model/Client/Elasticsearch.php
+++ b/app/code/Magento/OpenSearch/Model/SearchClient.php
@@ -3,53 +3,61 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
-namespace Magento\Elasticsearch6\Model\Client;
+namespace Magento\OpenSearch\Model;
use Magento\AdvancedSearch\Model\Client\ClientInterface;
use Magento\Elasticsearch\Model\Adapter\FieldsMappingPreprocessorInterface;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\LocalizedException;
+use Magento\OpenSearch\Model\Adapter\DynamicTemplatesProvider;
+use OpenSearch\Client;
+use OpenSearch\ClientBuilder;
-/**
- * Elasticsearch client
- *
- * @deprecated the new minor release supports compatibility with Elasticsearch 7
- */
-class Elasticsearch implements ClientInterface
+class SearchClient implements ClientInterface
{
/**
- * Elasticsearch Client instances
- *
- * @var \Elasticsearch\Client[]
+ * @var array
*/
- private $client;
+ private $clientOptions;
/**
- * @var array
+ * Client instances
+ *
+ * @var Client[]
*/
- private $clientOptions;
+ private $client;
/**
* @var bool
*/
private $pingResult;
+
/**
* @var FieldsMappingPreprocessorInterface[]
*/
private $fieldsMappingPreprocessors;
/**
- * Initialize Elasticsearch Client
+ * @var DynamicTemplatesProvider|null
+ */
+ public $dynamicTemplatesProvider;
+
+ /**
+ * Initialize Client
*
* @param array $options
- * @param \Elasticsearch\Client|null $elasticsearchClient
- * @param FieldsMappingPreprocessorInterface[] $fieldsMappingPreprocessors
+ * @param Client|null $openSearchClient
+ * @param array $fieldsMappingPreprocessors
+ * @param DynamicTemplatesProvider|null $dynamicTemplatesProvider
* @throws LocalizedException
*/
public function __construct(
$options = [],
- $elasticsearchClient = null,
- $fieldsMappingPreprocessors = []
+ $openSearchClient = null,
+ $fieldsMappingPreprocessors = [],
+ ?DynamicTemplatesProvider $dynamicTemplatesProvider = null
) {
if (empty($options['hostname'])
|| ((!empty($options['enableAuth']) && ($options['enableAuth'] == 1))
@@ -59,72 +67,74 @@ public function __construct(
__('The search failed because of a search engine misconfiguration.')
);
}
-
- if (!($elasticsearchClient instanceof \Elasticsearch\Client)) {
- $config = $this->buildConfig($options);
- $elasticsearchClient = \Elasticsearch\ClientBuilder::fromConfig($config, true);
+ // phpstan:ignore
+ if ($openSearchClient instanceof Client) {
+ $this->client[getmypid()] = $openSearchClient;
}
- $this->client[getmypid()] = $elasticsearchClient;
$this->clientOptions = $options;
- foreach ($fieldsMappingPreprocessors as $preprocessor) {
- if (!$preprocessor instanceof FieldsMappingPreprocessorInterface) {
- throw new \InvalidArgumentException(
- sprintf(
- 'Instance of FieldsMappingPreprocessorInterface is expected, got %s instead.',
- get_class($preprocessor)
- )
- );
- }
- }
$this->fieldsMappingPreprocessors = $fieldsMappingPreprocessors;
+ $this->dynamicTemplatesProvider = $dynamicTemplatesProvider ?: ObjectManager::getInstance()
+ ->get(DynamicTemplatesProvider::class);
+ }
+
+ /**
+ * Execute suggest query for OpenSearch
+ *
+ * @param array $query
+ * @return array
+ */
+ public function suggest(array $query): array
+ {
+ return $this->getOpenSearchClient()->suggest($query);
}
/**
- * Get Elasticsearch Client
+ * Get OS Client
*
- * @return \Elasticsearch\Client
+ * @return Client
*/
- private function getClient()
+ public function getOpenSearchClient(): Client
{
$pid = getmypid();
if (!isset($this->client[$pid])) {
- $config = $this->buildConfig($this->clientOptions);
- $this->client[$pid] = \Elasticsearch\ClientBuilder::fromConfig($config, true);
+ $config = $this->buildOSConfig($this->clientOptions);
+ $this->client[$pid] = ClientBuilder::fromConfig($config, true);
}
return $this->client[$pid];
}
/**
- * Ping the Elasticsearch client
+ * Ping the client
*
* @return bool
*/
- public function ping()
+ public function ping(): bool
{
if ($this->pingResult === null) {
- $this->pingResult = $this->getClient()->ping(['client' => ['timeout' => $this->clientOptions['timeout']]]);
+ $this->pingResult = $this->getOpenSearchClient()
+ ->ping(['client' => ['timeout' => $this->clientOptions['timeout']]]);
}
return $this->pingResult;
}
/**
- * Validate connection params
+ * Validate connection params for OpenSearch
*
* @return bool
*/
- public function testConnection()
+ public function testConnection(): bool
{
return $this->ping();
}
/**
- * Build config.
+ * Build config for OpenSearch
*
* @param array $options
* @return array
*/
- private function buildConfig($options = [])
+ private function buildOSConfig(array $options = []): array
{
$hostname = preg_replace('/http[s]?:\/\//i', '', $options['hostname']);
// @codingStandardsIgnoreStart
@@ -152,26 +162,26 @@ private function buildConfig($options = [])
}
/**
- * Performs bulk query over Elasticsearch index
+ * Performs bulk query over OpenSearch index
*
* @param array $query
* @return void
*/
- public function bulkQuery($query)
+ public function bulkQuery(array $query)
{
- $this->getClient()->bulk($query);
+ $this->getOpenSearchClient()->bulk($query);
}
/**
- * Creates an Elasticsearch index.
+ * Creates an OpenSearch index.
*
* @param string $index
* @param array $settings
* @return void
*/
- public function createIndex($index, $settings)
+ public function createIndex(string $index, array $settings)
{
- $this->getClient()->indices()->create(
+ $this->getOpenSearchClient()->indices()->create(
[
'index' => $index,
'body' => $settings,
@@ -188,7 +198,7 @@ public function createIndex($index, $settings)
*/
public function putIndexSettings(string $index, array $settings): void
{
- $this->getClient()->indices()->putSettings(
+ $this->getOpenSearchClient()->indices()->putSettings(
[
'index' => $index,
'body' => $settings,
@@ -197,14 +207,14 @@ public function putIndexSettings(string $index, array $settings): void
}
/**
- * Delete an Elasticsearch index.
+ * Delete an OpenSearch index.
*
* @param string $index
* @return void
*/
- public function deleteIndex($index)
+ public function deleteIndex(string $index)
{
- $this->getClient()->indices()->delete(['index' => $index]);
+ $this->getOpenSearchClient()->indices()->delete(['index' => $index]);
}
/**
@@ -213,12 +223,13 @@ public function deleteIndex($index)
* @param string $index
* @return bool
*/
- public function isEmptyIndex($index)
+ public function isEmptyIndex(string $index): bool
{
- $stats = $this->getClient()->indices()->stats(['index' => $index, 'metric' => 'docs']);
- if ($stats['indices'][$index]['primaries']['docs']['count'] == 0) {
+ $stats = $this->getOpenSearchClient()->indices()->stats(['index' => $index, 'metric' => 'docs']);
+ if ($stats['indices'][$index]['primaries']['docs']['count'] === 0) {
return true;
}
+
return false;
}
@@ -230,9 +241,13 @@ public function isEmptyIndex($index)
* @param string $oldIndex
* @return void
*/
- public function updateAlias($alias, $newIndex, $oldIndex = '')
+ public function updateAlias(string $alias, string $newIndex, string $oldIndex = '')
{
- $params['body'] = ['actions' => []];
+ $params = [
+ 'body' => [
+ 'actions' => [],
+ ],
+ ];
if ($oldIndex) {
$params['body']['actions'][] = ['remove' => ['alias' => $alias, 'index' => $oldIndex]];
}
@@ -240,18 +255,18 @@ public function updateAlias($alias, $newIndex, $oldIndex = '')
$params['body']['actions'][] = ['add' => ['alias' => $alias, 'index' => $newIndex]];
}
- $this->getClient()->indices()->updateAliases($params);
+ $this->getOpenSearchClient()->indices()->updateAliases($params);
}
/**
- * Checks whether Elasticsearch index exists
+ * Checks whether OpenSearch index exists
*
* @param string $index
* @return bool
*/
- public function indexExists($index)
+ public function indexExists(string $index): bool
{
- return $this->getClient()->indices()->exists(['index' => $index]);
+ return $this->getOpenSearchClient()->indices()->exists(['index' => $index]);
}
/**
@@ -261,13 +276,14 @@ public function indexExists($index)
* @param string $index
* @return bool
*/
- public function existsAlias($alias, $index = '')
+ public function existsAlias(string $alias, string $index = ''): bool
{
$params = ['name' => $alias];
if ($index) {
$params['index'] = $index;
}
- return $this->getClient()->indices()->existsAlias($params);
+
+ return $this->getOpenSearchClient()->indices()->existsAlias($params);
}
/**
@@ -276,68 +292,29 @@ public function existsAlias($alias, $index = '')
* @param string $alias
* @return array
*/
- public function getAlias($alias)
+ public function getAlias(string $alias): array
{
- return $this->getClient()->indices()->getAlias(['name' => $alias]);
+ return $this->getOpenSearchClient()->indices()->getAlias(['name' => $alias]);
}
/**
- * Add mapping to Elasticsearch index
+ * Add mapping to OpenSearch index
*
* @param array $fields
* @param string $index
* @param string $entityType
* @return void
*/
- public function addFieldsMapping(array $fields, $index, $entityType)
+ public function addFieldsMapping(array $fields, string $index, string $entityType)
{
$params = [
'index' => $index,
'type' => $entityType,
+ 'include_type_name' => true,
'body' => [
$entityType => [
'properties' => [],
- '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',
- ],
- ],
- ],
- ],
+ 'dynamic_templates' => $this->dynamicTemplatesProvider->getTemplates(),
],
],
];
@@ -346,7 +323,18 @@ public function addFieldsMapping(array $fields, $index, $entityType)
$params['body'][$entityType]['properties'][$field] = $fieldInfo;
}
- $this->getClient()->indices()->putMapping($params);
+ $this->getOpenSearchClient()->indices()->putMapping($params);
+ }
+
+ /**
+ * Execute search by $query
+ *
+ * @param array $query
+ * @return array
+ */
+ public function query(array $query): array
+ {
+ return $this->getOpenSearchClient()->search($query);
}
/**
@@ -357,19 +345,19 @@ public function addFieldsMapping(array $fields, $index, $entityType)
*/
public function getMapping(array $params): array
{
- return $this->getClient()->indices()->getMapping($params);
+ return $this->getOpenSearchClient()->indices()->getMapping($params);
}
/**
- * Delete mapping in Elasticsearch index
+ * Delete mapping in OpenSearch index
*
* @param string $index
* @param string $entityType
* @return void
*/
- public function deleteMapping($index, $entityType)
+ public function deleteMapping(string $index, string $entityType)
{
- $this->getClient()->indices()->deleteMapping(
+ $this->getOpenSearchClient()->indices()->deleteMapping(
[
'index' => $index,
'type' => $entityType,
@@ -377,35 +365,13 @@ public function deleteMapping($index, $entityType)
);
}
- /**
- * Execute search by $query
- *
- * @param array $query
- * @return array
- */
- public function query($query)
- {
- return $this->getClient()->search($query);
- }
-
- /**
- * Execute suggest query
- *
- * @param array $query
- * @return array
- */
- public function suggest($query)
- {
- return $this->getClient()->suggest($query);
- }
-
/**
* Apply fields mapping preprocessors
*
* @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/README.md b/app/code/Magento/OpenSearch/README.md
new file mode 100644
index 0000000000000..36ab12072d572
--- /dev/null
+++ b/app/code/Magento/OpenSearch/README.md
@@ -0,0 +1,3 @@
+#Magento_OpenSearch module
+
+Magento_OpenSearch module allows using OpenSearch 1.x engine for the product searching capabilities.
diff --git a/app/code/Magento/OpenSearch/SearchAdapter/Adapter.php b/app/code/Magento/OpenSearch/SearchAdapter/Adapter.php
new file mode 100644
index 0000000000000..03fb9977b0d25
--- /dev/null
+++ b/app/code/Magento/OpenSearch/SearchAdapter/Adapter.php
@@ -0,0 +1,128 @@
+ [
+ 'hits' => []
+ ],
+ 'aggregations' => [
+ 'price_bucket' => [],
+ 'category_bucket' => [
+ 'buckets' => []
+ ]
+ ]
+ ];
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ /**
+ * @param ConnectionManager $connectionManager
+ * @param Mapper $mapper
+ * @param ResponseFactory $responseFactory
+ * @param AggregationBuilder $aggregationBuilder
+ * @param QueryContainerFactory $queryContainerFactory
+ * @param LoggerInterface $logger
+ */
+ public function __construct(
+ ConnectionManager $connectionManager,
+ Mapper $mapper,
+ ResponseFactory $responseFactory,
+ AggregationBuilder $aggregationBuilder,
+ QueryContainerFactory $queryContainerFactory,
+ LoggerInterface $logger
+ ) {
+ $this->connectionManager = $connectionManager;
+ $this->mapper = $mapper;
+ $this->responseFactory = $responseFactory;
+ $this->aggregationBuilder = $aggregationBuilder;
+ $this->queryContainerFactory = $queryContainerFactory;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Search query
+ *
+ * @param RequestInterface $request
+ * @return QueryResponse
+ */
+ public function query(RequestInterface $request) : QueryResponse
+ {
+ $client = $this->connectionManager->getConnection();
+ $aggregationBuilder = $this->aggregationBuilder;
+ $query = $this->mapper->buildQuery($request);
+ $aggregationBuilder->setQuery($this->queryContainerFactory->create(['query' => $query]));
+
+ try {
+ $rawResponse = $client->query($query);
+ } catch (\Exception $e) {
+ $this->logger->critical($e);
+ // return empty search result in case an exception is thrown from OpenSearch
+ $rawResponse = self::$emptyRawResponse;
+ }
+
+ $rawDocuments = $rawResponse['hits']['hits'] ?? [];
+ $queryResponse = $this->responseFactory->create(
+ [
+ 'documents' => $rawDocuments,
+ 'aggregations' => $aggregationBuilder->build($request, $rawResponse),
+ 'total' => $rawResponse['hits']['total']['value'] ?? 0
+ ]
+ );
+ return $queryResponse;
+ }
+}
diff --git a/app/code/Magento/OpenSearch/SearchAdapter/Mapper.php b/app/code/Magento/OpenSearch/SearchAdapter/Mapper.php
new file mode 100644
index 0000000000000..bbe9846116bcd
--- /dev/null
+++ b/app/code/Magento/OpenSearch/SearchAdapter/Mapper.php
@@ -0,0 +1,42 @@
+mapper = $mapper;
+ }
+
+ /**
+ * Build adapter dependent query
+ *
+ * @param RequestInterface $request
+ * @return array
+ */
+ public function buildQuery(RequestInterface $request) : array
+ {
+ $searchQuery = $this->mapper->buildQuery($request);
+ $searchQuery['track_total_hits'] = true;
+ return $searchQuery;
+ }
+}
diff --git a/app/code/Magento/OpenSearch/Setup/Validator.php b/app/code/Magento/OpenSearch/Setup/Validator.php
new file mode 100644
index 0000000000000..cdac0b9712bc9
--- /dev/null
+++ b/app/code/Magento/OpenSearch/Setup/Validator.php
@@ -0,0 +1,49 @@
+clientResolver = $clientResolver;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validate(): array
+ {
+ $errors = [];
+ try {
+ $client = $this->clientResolver->create();
+ if (!$client->testConnection()) {
+ $engine = $this->clientResolver->getCurrentEngine();
+ $errors[] = "Could not validate a connection to the Search engine: $engine."
+ . ' Verify that the host and port are configured correctly.';
+ }
+ } catch (\Exception $e) {
+ $errors[] = 'Could not validate a connection to the OpenSearch. ' . $e->getMessage();
+ }
+ return $errors;
+ }
+}
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/OpenSearch/Test/Unit/Model/DataProvider/Base/SuggestionsTest.php b/app/code/Magento/OpenSearch/Test/Unit/Model/DataProvider/Base/SuggestionsTest.php
new file mode 100644
index 0000000000000..016675da2ce9a
--- /dev/null
+++ b/app/code/Magento/OpenSearch/Test/Unit/Model/DataProvider/Base/SuggestionsTest.php
@@ -0,0 +1,231 @@
+config = $this->getMockBuilder(Config::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['isElasticsearchEnabled'])
+ ->getMock();
+
+ $this->queryResultFactory = $this->getMockBuilder(QueryResultFactory::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['create'])
+ ->getMock();
+
+ $this->connectionManager = $this->getMockBuilder(ConnectionManager::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['getConnection'])
+ ->getMock();
+
+ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->searchIndexNameResolver = $this
+ ->getMockBuilder(SearchIndexNameResolver::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['getIndexName'])
+ ->getMock();
+
+ $this->storeManager = $this->getMockBuilder(StoreManager::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->fieldProvider = $this->getMockBuilder(FieldProviderInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->logger = $this->getMockBuilder(LoggerInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->client = $this->getMockBuilder(SearchClient::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->query = $this->getMockBuilder(QueryInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $objectManager = new ObjectManagerHelper($this);
+
+ $this->model = $objectManager->getObject(
+ Suggestions::class,
+ [
+ 'queryResultFactory' => $this->queryResultFactory,
+ 'connectionManager' => $this->connectionManager,
+ 'scopeConfig' => $this->scopeConfig,
+ 'config' => $this->config,
+ 'searchIndexNameResolver' => $this->searchIndexNameResolver,
+ 'storeManager' => $this->storeManager,
+ 'fieldProvider' => $this->fieldProvider,
+ 'logger' => $this->logger,
+ 'responseErrorExceptionList' => ['opensearchBadRequest400' => BadRequest400Exception::class]
+ ]
+ );
+ }
+
+ /**
+ * Test get items process when throwing an exception.
+ *
+ * @return void
+ */
+ public function testGetItemsException(): void
+ {
+ $this->prepareSearchQuery();
+ $exception = new BadRequest400Exception();
+
+ $this->client->expects($this->once())
+ ->method('query')
+ ->willThrowException($exception);
+
+ $this->logger->expects($this->once())
+ ->method('critical')
+ ->with($exception);
+
+ $this->queryResultFactory->expects($this->never())
+ ->method('create');
+
+ $this->assertEmpty($this->model->getItems($this->query));
+ }
+
+ /**
+ * Prepare Mocks for default get items process.
+ * @return void
+ */
+ private function prepareSearchQuery(): void
+ {
+ $storeId = 1;
+
+ $this->scopeConfig->expects($this->exactly(2))
+ ->method('isSetFlag')
+ ->willReturn(true);
+
+ $this->scopeConfig->expects($this->once())
+ ->method('getValue')
+ ->willReturn(1);
+
+ $this->config->expects($this->once())
+ ->method('isElasticsearchEnabled')
+ ->willReturn(true);
+
+ $store = $this->getMockBuilder(StoreInterface::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $store->expects($this->once())
+ ->method('getId')
+ ->willReturn($storeId);
+
+ $this->storeManager->expects($this->once())
+ ->method('getStore')
+ ->willReturn($store);
+
+ $this->searchIndexNameResolver->expects($this->once())
+ ->method('getIndexName')
+ ->with($storeId, Config::ELASTICSEARCH_TYPE_DEFAULT)
+ ->willReturn('magento2_product_1');
+
+ $this->query->expects($this->once())
+ ->method('getQueryText')
+ ->willReturn('query');
+
+ $this->fieldProvider->expects($this->once())
+ ->method('getFields')
+ ->willReturn([]);
+
+ $this->connectionManager->expects($this->once())
+ ->method('getConnection')
+ ->willReturn($this->client);
+ }
+}
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 @@
+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/Elasticsearch6/composer.json b/app/code/Magento/OpenSearch/composer.json
similarity index 66%
rename from app/code/Magento/Elasticsearch6/composer.json
rename to app/code/Magento/OpenSearch/composer.json
index 80eb60946a759..1b9e006b9e9b1 100644
--- a/app/code/Magento/Elasticsearch6/composer.json
+++ b/app/code/Magento/OpenSearch/composer.json
@@ -1,17 +1,15 @@
{
- "name": "magento/module-elasticsearch-6",
+ "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-search": "*",
"magento/module-elasticsearch": "*",
- "elasticsearch/elasticsearch": "~7.17.0"
- },
- "suggest": {
- "magento/module-config": "*"
+ "magento/module-search": "*",
+ "magento/module-config": "*",
+ "opensearch-project/opensearch-php": "^1.0 || ^2.0"
},
"type": "magento2-module",
"license": [
@@ -23,7 +21,7 @@
"registration.php"
],
"psr-4": {
- "Magento\\Elasticsearch6\\": ""
+ "Magento\\OpenSearch\\": ""
}
}
}
diff --git a/app/code/Magento/OpenSearch/etc/adminhtml/system.xml b/app/code/Magento/OpenSearch/etc/adminhtml/system.xml
new file mode 100644
index 0000000000000..56d9eff92fd3e
--- /dev/null
+++ b/app/code/Magento/OpenSearch/etc/adminhtml/system.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+ OpenSearch Server Hostname
+
+ opensearch
+
+
+
+
+ OpenSearch Server Port
+
+ opensearch
+
+
+
+
+ OpenSearch Index Prefix
+
+ opensearch
+
+
+
+
+ Enable OpenSearch HTTP Auth
+ Magento\Config\Model\Config\Source\Yesno
+
+ opensearch
+
+
+
+
+ OpenSearch HTTP Username
+
+ opensearch
+ 1
+
+
+
+
+ OpenSearch HTTP Password
+
+ opensearch
+ 1
+
+
+
+
+ OpenSearch Server Timeout
+
+ opensearch
+
+
+
+
+
+ Test Connection
+ Magento\OpenSearch\Block\Adminhtml\System\Config\TestConnection
+
+ opensearch
+
+
+
+ Minimum Terms to Match
+
+ opensearch
+
+ Learn more about valid syntax.]]>
+ Magento\Elasticsearch\Model\Config\Backend\MinimumShouldMatch
+
+
+
+
+
diff --git a/app/code/Magento/OpenSearch/etc/config.xml b/app/code/Magento/OpenSearch/etc/config.xml
new file mode 100644
index 0000000000000..d3a659b1046f5
--- /dev/null
+++ b/app/code/Magento/OpenSearch/etc/config.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ opensearch
+ localhost
+ 9200
+ magento2
+ 0
+ 15
+
+
+
+
+
diff --git a/app/code/Magento/OpenSearch/etc/di.xml b/app/code/Magento/OpenSearch/etc/di.xml
new file mode 100644
index 0000000000000..ad1072165ad48
--- /dev/null
+++ b/app/code/Magento/OpenSearch/etc/di.xml
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+ - opensearch
+
+
+
+
+
+
+
+
+ - OpenSearch
+
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\Elasticsearch5\Model\Adapter\BatchDataMapper\CategoryFieldsProvider
+
+
+
+
+
+
+
+ - Magento\OpenSearch\Model\Adapter\FieldMapper\ProductFieldMapper
+
+
+
+
+
+ elasticsearch5FieldProvider
+ \Magento\OpenSearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CompositeResolver
+
+
+
+
+
+ - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\NotEavAttribute
+ - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\SpecialAttribute
+ - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\Price
+ - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CategoryName
+ - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\Position
+ - Magento\OpenSearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver
+
+
+
+
+
+
+
+
+ - \Magento\OpenSearch\Model\Client\OpenSearchFactory
+
+
+ - \Magento\Elasticsearch\Model\Config
+
+
+
+
+
+ Magento\OpenSearch\Model\SearchClient
+ Magento\OpenSearch\Model\OpenSearch
+
+
+
+
+
+ - Magento\OpenSearch\Model\Client\OpenSearchFactory
+
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\Model\Indexer\IndexerHandler
+
+
+
+
+
+
+ - Magento\Elasticsearch\Model\Indexer\IndexStructure
+
+
+
+
+
+
+
+ - OpenSearch\Common\Exceptions\Missing404Exception
+
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Aggregation\Interval
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\SearchAdapter\Dynamic\DataProvider
+
+
+
+
+
+
+
+ elasticsearch5FieldProvider
+
+ - OpenSearch\Common\Exceptions\BadRequest400Exception
+
+
+
+
+
+
+ - Magento\OpenSearch\Model\DataProvider\Suggestions
+
+
+
+
+
+
+
+
+ - opensearch
+
+ opensearch
+
+
+
+
+
+ - Magento\Elasticsearch\Model\ResourceModel\Engine
+
+
+
+
+
+
+
+
+ - \Magento\OpenSearch\SearchAdapter\Adapter
+
+
+
+
+
+
+
+
+ - opensearch_server_hostname
+ - opensearch_server_port
+ - opensearch_server_timeout
+ - opensearch_index_prefix
+ - opensearch_enable_auth
+ - opensearch_username
+ - opensearch_password
+
+
+
+
+
+
+ - Magento\OpenSearch\Setup\InstallConfig
+
+
+
+
+
+
+
+
+ - 1
+ - 1
+ - 1
+
+
+ - 1
+ - 1
+ - 1
+ - 1
+ - 1
+ - 1
+ - 1
+
+
+
+
+
+
+
+
+ - 10000
+
+
+
+
+
+
+
+ - elasticsearchCategoryCollectionFactory
+
+
+
+
+
+
+
+ - elasticsearchAdvancedCollectionFactory
+ - elasticsearchAdvancedCollectionFactory
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\Model\Advanced\ProductCollectionPrepareStrategy
+
+
+
+
+
+
+
+ - elasticsearchFulltextSearchCollectionFactory
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\Model\Adapter\FieldMapper\CopySearchableFieldsToSearchField
+ - Magento\Elasticsearch\Model\Adapter\FieldMapper\AddDefaultSearchField
+
+
+
+
+
+
+
+ - Magento\OpenSearch\Setup\Validator
+
+
+
+
+
+
+ Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver
+
+
+
+
+
+
+ - Magento\OpenSearch\Model\Adapter\DynamicTemplates\PriceMapper
+ - Magento\OpenSearch\Model\Adapter\DynamicTemplates\PositionMapper
+ - Magento\OpenSearch\Model\Adapter\DynamicTemplates\StringMapper
+ - Magento\OpenSearch\Model\Adapter\DynamicTemplates\IntegerMapper
+
+
+
+
diff --git a/app/code/Magento/OpenSearch/etc/module.xml b/app/code/Magento/OpenSearch/etc/module.xml
new file mode 100644
index 0000000000000..3d19026a62e99
--- /dev/null
+++ b/app/code/Magento/OpenSearch/etc/module.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Elasticsearch6/etc/search_engine.xml b/app/code/Magento/OpenSearch/etc/search_engine.xml
similarity index 90%
rename from app/code/Magento/Elasticsearch6/etc/search_engine.xml
rename to app/code/Magento/OpenSearch/etc/search_engine.xml
index 40bc69bfd8298..1fd3a4154d034 100644
--- a/app/code/Magento/Elasticsearch6/etc/search_engine.xml
+++ b/app/code/Magento/OpenSearch/etc/search_engine.xml
@@ -6,7 +6,7 @@
*/
-->
-
+
diff --git a/app/code/Magento/OpenSearch/i18n/en_US.csv b/app/code/Magento/OpenSearch/i18n/en_US.csv
new file mode 100644
index 0000000000000..f59051df1df1d
--- /dev/null
+++ b/app/code/Magento/OpenSearch/i18n/en_US.csv
@@ -0,0 +1,9 @@
+"There is no such data mapper ""%1"" for interface %2","There is no such data mapper ""%1"" for interface %2"
+"Data mapper ""%1"" must implement interface %2","Data mapper ""%1"" must implement interface %2"
+"OpenSearch Server Hostname","OpenSearch Server Hostname"
+"OpenSearch Server Port","OpenSearch Server Port"
+"OpenSearch Index Prefix","OpenSearch Index Prefix"
+"Enable OpenSearch HTTP Auth","Enable OpenSearch HTTP Auth"
+"OpenSearch HTTP Username","OpenSearch HTTP Username"
+"OpenSearch HTTP Password","OpenSearch HTTP Password"
+"OpenSearch Server Timeout","OpenSearch Server Timeout"
diff --git a/app/code/Magento/AmqpStore/registration.php b/app/code/Magento/OpenSearch/registration.php
similarity index 81%
rename from app/code/Magento/AmqpStore/registration.php
rename to app/code/Magento/OpenSearch/registration.php
index 22ce677dc8302..2fa6ee5a1fba1 100644
--- a/app/code/Magento/AmqpStore/registration.php
+++ b/app/code/Magento/OpenSearch/registration.php
@@ -4,6 +4,8 @@
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
use Magento\Framework\Component\ComponentRegistrar;
-ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_AmqpStore', __DIR__);
+ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_OpenSearch', __DIR__);
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/Gateway/Http/Client/Zend.php b/app/code/Magento/Payment/Gateway/Http/Client/Zend.php
index 89ae807385c68..1bcf2e0296b56 100644
--- a/app/code/Magento/Payment/Gateway/Http/Client/Zend.php
+++ b/app/code/Magento/Payment/Gateway/Http/Client/Zend.php
@@ -5,23 +5,26 @@
*/
namespace Magento\Payment\Gateway\Http\Client;
-use Magento\Framework\HTTP\ZendClient;
-use Magento\Framework\HTTP\ZendClientFactory;
+use Laminas\Http\Exception\RuntimeException;
+use Laminas\Http\Request;
+use LogicException;
+use Magento\Framework\HTTP\LaminasClient;
+use Magento\Framework\HTTP\LaminasClientFactory;
+use Magento\Payment\Gateway\Http\ClientException;
use Magento\Payment\Gateway\Http\ClientInterface;
+use Magento\Payment\Gateway\Http\ConverterException;
use Magento\Payment\Gateway\Http\ConverterInterface;
use Magento\Payment\Gateway\Http\TransferInterface;
use Magento\Payment\Model\Method\Logger;
/**
- * Class Zend
- * @package Magento\Payment\Gateway\Http\Client
* @api
* @since 100.0.2
*/
class Zend implements ClientInterface
{
/**
- * @var ZendClientFactory
+ * @var LaminasClientFactory
*/
private $clientFactory;
@@ -36,12 +39,12 @@ class Zend implements ClientInterface
private $logger;
/**
- * @param ZendClientFactory $clientFactory
+ * @param LaminasClientFactory $clientFactory
* @param Logger $logger
- * @param ConverterInterface | null $converter
+ * @param ConverterInterface|null $converter
*/
public function __construct(
- ZendClientFactory $clientFactory,
+ LaminasClientFactory $clientFactory,
Logger $logger,
ConverterInterface $converter = null
) {
@@ -51,7 +54,7 @@ public function __construct(
}
/**
- * {inheritdoc}
+ * @inheritdoc
*/
public function placeRequest(TransferInterface $transferObject)
{
@@ -60,21 +63,20 @@ public function placeRequest(TransferInterface $transferObject)
'request_uri' => $transferObject->getUri()
];
$result = [];
- /** @var ZendClient $client */
+ /** @var LaminasClient $client */
$client = $this->clientFactory->create();
-
- $client->setConfig($transferObject->getClientConfig());
+ $client->setOptions($transferObject->getClientConfig());
$client->setMethod($transferObject->getMethod());
-
+ $methodParam = is_array($transferObject->getBody()) ? $transferObject->getBody() : [$transferObject->getBody()];
switch ($transferObject->getMethod()) {
- case \Zend_Http_Client::GET:
- $client->setParameterGet($transferObject->getBody());
+ case Request::METHOD_GET:
+ $client->setParameterGet($methodParam);
break;
- case \Zend_Http_Client::POST:
- $client->setParameterPost($transferObject->getBody());
+ case Request::METHOD_POST:
+ $client->setParameterPost($methodParam);
break;
default:
- throw new \LogicException(
+ throw new LogicException(
sprintf(
'Unsupported HTTP method %s',
$transferObject->getMethod()
@@ -87,17 +89,15 @@ public function placeRequest(TransferInterface $transferObject)
$client->setUri($transferObject->getUri());
try {
- $response = $client->request();
+ $response = $client->send();
$result = $this->converter
? $this->converter->convert($response->getBody())
: [$response->getBody()];
$log['response'] = $result;
- } catch (\Zend_Http_Client_Exception $e) {
- throw new \Magento\Payment\Gateway\Http\ClientException(
- __($e->getMessage())
- );
- } catch (\Magento\Payment\Gateway\Http\ConverterException $e) {
+ } catch (RuntimeException $e) {
+ throw new ClientException(__($e->getMessage()));
+ } catch (ConverterException $e) {
throw $e;
} finally {
$this->logger->debug($log);
diff --git a/app/code/Magento/Payment/Model/Method/Free.php b/app/code/Magento/Payment/Model/Method/Free.php
index a02fa4b18d5e1..56b96f30aba21 100644
--- a/app/code/Magento/Payment/Model/Method/Free.php
+++ b/app/code/Magento/Payment/Model/Method/Free.php
@@ -20,16 +20,16 @@
*/
class Free extends \Magento\Payment\Model\Method\AbstractMethod
{
- const PAYMENT_METHOD_FREE_CODE = 'free';
+ public const PAYMENT_METHOD_FREE_CODE = 'free';
/**
* XML Paths for configuration constants
*/
- const XML_PATH_PAYMENT_FREE_ACTIVE = 'payment/free/active';
+ public const XML_PATH_PAYMENT_FREE_ACTIVE = 'payment/free/active';
- const XML_PATH_PAYMENT_FREE_ORDER_STATUS = 'payment/free/order_status';
+ public const XML_PATH_PAYMENT_FREE_ORDER_STATUS = 'payment/free/order_status';
- const XML_PATH_PAYMENT_FREE_PAYMENT_ACTION = 'payment/free/payment_action';
+ public const XML_PATH_PAYMENT_FREE_PAYMENT_ACTION = 'payment/free/payment_action';
/**
* Payment Method features
@@ -50,6 +50,11 @@ class Free extends \Magento\Payment\Model\Method\AbstractMethod
*/
protected $priceCurrency;
+ /**
+ * @var bool
+ */
+ protected $_isOffline = true;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
diff --git a/app/code/Magento/Payment/Model/MethodList.php b/app/code/Magento/Payment/Model/MethodList.php
index 0700f25fcbee5..3b9d8b98f4389 100644
--- a/app/code/Magento/Payment/Model/MethodList.php
+++ b/app/code/Magento/Payment/Model/MethodList.php
@@ -19,12 +19,14 @@ class MethodList
{
/**
* @var \Magento\Payment\Helper\Data
+ * @see MAGETWO-71174
* @deprecated 100.1.0 Do not use this property in case of inheritance.
*/
protected $paymentHelper;
/**
* @var \Magento\Payment\Model\Checks\SpecificationFactory
+ * @see MAGETWO-71174
* @deprecated 100.2.0 Do not use this property in case of inheritance.
*/
protected $methodSpecificationFactory;
diff --git a/app/code/Magento/Payment/Plugin/PaymentMethodProcess.php b/app/code/Magento/Payment/Plugin/PaymentMethodProcess.php
new file mode 100644
index 0000000000000..7808f4cb4af6b
--- /dev/null
+++ b/app/code/Magento/Payment/Plugin/PaymentMethodProcess.php
@@ -0,0 +1,63 @@
+braintreeCCVault = $braintreeCCVault;
+ $this->tokensConfigProvider = $tokensConfigProvider ??
+ ObjectManager::getInstance()->get(TokensConfigProvider::class);
+ }
+
+ /**
+ * Retrieve available payment methods
+ *
+ * @param Container $container
+ * @param array $results
+ * @return array
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterGetMethods(Container $container, array $results): array
+ {
+ $methods = [];
+ foreach ($results as $result) {
+ if ($result->getCode() === $this->braintreeCCVault
+ && empty($this->tokensConfigProvider->getTokensComponents($result->getCode()))) {
+
+ continue;
+ }
+ $methods[] = $result;
+ }
+ return $methods;
+ }
+}
diff --git a/app/code/Magento/Payment/Test/Mftf/Data/PaymentMethodConfigData.xml b/app/code/Magento/Payment/Test/Mftf/Data/PaymentMethodConfigData.xml
index 3fd2d1b068b36..68c3b968f143c 100644
--- a/app/code/Magento/Payment/Test/Mftf/Data/PaymentMethodConfigData.xml
+++ b/app/code/Magento/Payment/Test/Mftf/Data/PaymentMethodConfigData.xml
@@ -44,4 +44,22 @@
No
0
+
+ payment/cashondelivery/allowspecific
+ 1
+ Specific Countries
+ 1
+
+
+ payment/cashondelivery/allowspecific
+ 1
+ All Allowed Countries
+ 0
+
+
+ payment/cashondelivery/specificcountry
+ 1
+ All Allowed Countries
+ ''
+
diff --git a/app/code/Magento/Payment/Test/Unit/Gateway/Http/Client/ZendTest.php b/app/code/Magento/Payment/Test/Unit/Gateway/Http/Client/ZendTest.php
index b74716e4ec083..a100ad88f00b4 100644
--- a/app/code/Magento/Payment/Test/Unit/Gateway/Http/Client/ZendTest.php
+++ b/app/code/Magento/Payment/Test/Unit/Gateway/Http/Client/ZendTest.php
@@ -7,8 +7,11 @@
namespace Magento\Payment\Test\Unit\Gateway\Http\Client;
-use Magento\Framework\HTTP\ZendClient;
-use Magento\Framework\HTTP\ZendClientFactory;
+use Laminas\Http\Exception\RuntimeException;
+use Laminas\Http\Request;
+use Laminas\Http\Response;
+use Magento\Framework\HTTP\LaminasClient;
+use Magento\Framework\HTTP\LaminasClientFactory;
use Magento\Payment\Gateway\Http\Client\Zend;
use Magento\Payment\Gateway\Http\ClientException;
use Magento\Payment\Gateway\Http\ConverterException;
@@ -29,12 +32,12 @@ class ZendTest extends TestCase
protected $converterMock;
/**
- * @var ZendClientFactory|MockObject
+ * @var LaminasClientFactory|MockObject
*/
- protected $zendClientFactoryMock;
+ protected $clientFactoryMock;
/**
- * @var ZendClient|MockObject
+ * @var LaminasClient|MockObject
*/
protected $clientMock;
@@ -53,12 +56,12 @@ protected function setUp(): void
$this->converterMock = $this->getMockBuilder(ConverterInterface::class)
->getMockForAbstractClass();
- $this->zendClientFactoryMock = $this->getMockBuilder(ZendClientFactory::class)
+ $this->clientFactoryMock = $this->getMockBuilder(LaminasClientFactory::class)
->setMethods(['create'])
->disableOriginalConstructor()
->getMock();
- $this->clientMock = $this->getMockBuilder(ZendClient::class)
+ $this->clientMock = $this->getMockBuilder(LaminasClient::class)
->disableOriginalConstructor()
->getMock();
@@ -70,7 +73,7 @@ protected function setUp(): void
->getMockForAbstractClass();
$this->model = new Zend(
- $this->zendClientFactoryMock,
+ $this->clientFactoryMock,
$this->loggerMock,
$this->converterMock
);
@@ -81,15 +84,15 @@ public function testPlaceRequest()
$this->setClientTransferObjects();
$responseBody = 'Response body content';
- $zendHttpResponseMock = $this->getMockBuilder(
- \Zend_Http_Response::class
+ $httpResponseMock = $this->getMockBuilder(
+ Response::class
)->disableOriginalConstructor()
->getMock();
- $zendHttpResponseMock->expects($this->once())->method('getBody')->willReturn($responseBody);
+ $httpResponseMock->expects($this->once())->method('getBody')->willReturn($responseBody);
- $this->clientMock->expects($this->once())->method('request')->willReturn($zendHttpResponseMock);
+ $this->clientMock->expects($this->once())->method('send')->willReturn($httpResponseMock);
$this->converterMock->expects($this->once())->method('convert')->with($responseBody);
- $this->zendClientFactoryMock->expects($this->once())
+ $this->clientFactoryMock->expects($this->once())
->method('create')
->willReturn($this->clientMock);
@@ -105,12 +108,12 @@ public function testPlaceRequestClientFail()
$this->setClientTransferObjects();
$this->clientMock->expects($this->once())
- ->method('request')
- ->willThrowException(new \Zend_Http_Client_Exception());
+ ->method('send')
+ ->willThrowException(new RuntimeException());
$this->converterMock->expects($this->never())->method('convert');
- $this->zendClientFactoryMock->expects($this->once())
+ $this->clientFactoryMock->expects($this->once())
->method('create')
->willReturn($this->clientMock);
@@ -126,19 +129,19 @@ public function testPlaceRequestConvertResponseFail()
$this->setClientTransferObjects();
$responseBody = 'Response body content';
- $zendHttpResponseMock = $this->getMockBuilder(
- \Zend_Http_Response::class
+ $httpResponseMock = $this->getMockBuilder(
+ Response::class
)->disableOriginalConstructor()
->getMock();
- $zendHttpResponseMock->expects($this->once())->method('getBody')->willReturn($responseBody);
+ $httpResponseMock->expects($this->once())->method('getBody')->willReturn($responseBody);
- $this->clientMock->expects($this->once())->method('request')->willReturn($zendHttpResponseMock);
+ $this->clientMock->expects($this->once())->method('send')->willReturn($httpResponseMock);
$this->converterMock->expects($this->once())
->method('convert')
->with($responseBody)
->willThrowException(new ConverterException(__()));
- $this->zendClientFactoryMock->expects($this->once())
+ $this->clientFactoryMock->expects($this->once())
->method('create')
->willReturn($this->clientMock);
@@ -148,7 +151,7 @@ public function testPlaceRequestConvertResponseFail()
private function setClientTransferObjects()
{
$config = ['key1' => 'value1', 'key2' => 'value2'];
- $method = \Zend_Http_Client::POST;
+ $method = Request::METHOD_POST;
$headers = ['key1' => 'value1', 'key2' => 'value2'];
$body = 'Body content';
$uri = 'https://example.com/listener';
@@ -161,9 +164,9 @@ private function setClientTransferObjects()
$this->transferObjectMock->expects($this->once())->method('shouldEncode')->willReturn($shouldEncode);
$this->transferObjectMock->expects(static::atLeastOnce())->method('getUri')->willReturn($uri);
- $this->clientMock->expects($this->once())->method('setConfig')->with($config)->willReturnSelf();
+ $this->clientMock->expects($this->once())->method('setOptions')->with($config)->willReturnSelf();
$this->clientMock->expects($this->once())->method('setMethod')->with($method)->willReturnSelf();
- $this->clientMock->expects($this->once())->method('setParameterPost')->with($body)->willReturnSelf();
+ $this->clientMock->expects($this->once())->method('setParameterPost')->with([$body])->willReturnSelf();
$this->clientMock->expects($this->once())->method('setHeaders')->with($headers)->willReturnSelf();
$this->clientMock->expects($this->once())->method('setUrlEncodeBody')->with($shouldEncode)->willReturnSelf();
$this->clientMock->expects($this->once())->method('setUri')->with($uri)->willReturnSelf();
diff --git a/app/code/Magento/Payment/Test/Unit/Plugin/PaymentMethodProcessTest.php b/app/code/Magento/Payment/Test/Unit/Plugin/PaymentMethodProcessTest.php
new file mode 100644
index 0000000000000..9a08f47727bb2
--- /dev/null
+++ b/app/code/Magento/Payment/Test/Unit/Plugin/PaymentMethodProcessTest.php
@@ -0,0 +1,142 @@
+tokensConfigProviderMock = $this->getMockBuilder(TokensConfigProvider::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $objectManagerHelper = new ObjectManager($this);
+ $this->containerMock = $objectManagerHelper->getObject(Container::class);
+
+ $this->plugin = $objectManagerHelper->getObject(
+ PaymentMethodProcess::class,
+ [
+ 'braintreeCCVault' => self::PAYMENT_METHOD_BRAINTREE_CC_VAULT,
+ 'tokensConfigProvider' => $this->tokensConfigProviderMock
+ ]
+ );
+ }
+
+ /**
+ * @param array $methods
+ * @param array $expectedResult
+ * @param array $tokenComponents
+ * @dataProvider afterGetMethodsDataProvider
+ */
+ public function testAfterGetMethods(array $methods, array $expectedResult, array $tokenComponents)
+ {
+
+ $this->tokensConfigProviderMock->method('getTokensComponents')
+ ->with(self::PAYMENT_METHOD_BRAINTREE_CC_VAULT)
+ ->willReturn($tokenComponents);
+
+ $result = $this->plugin->afterGetMethods($this->containerMock, $methods);
+ $this->assertEquals($result, $expectedResult);
+ }
+
+ /**
+ * Data provider for AfterGetMethods.
+ *
+ * @return array
+ */
+ public function afterGetMethodsDataProvider(): array
+ {
+ $tokenUiComponentInterface = $this->getMockBuilder(TokenUiComponentInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $checkmoPaymentMethod = $this
+ ->getMockBuilder(PaymentMethodInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getCode'])
+ ->getMockForAbstractClass();
+ $brainTreePaymentMethod = $this
+ ->getMockBuilder(PaymentMethodInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getCode'])
+ ->getMockForAbstractClass();
+ $brainTreeCCVaultTPaymentMethod = $this
+ ->getMockBuilder(PaymentMethodInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getCode'])
+ ->getMockForAbstractClass();
+
+ $checkmoPaymentMethod->expects($this->any())->method('getCode')
+ ->willReturn(self::PAYMENT_METHOD_CHECKMO);
+ $brainTreePaymentMethod->expects($this->any())->method('getCode')
+ ->willReturn(self::PAYMENT_METHOD_BRAINTREE);
+ $brainTreeCCVaultTPaymentMethod->expects($this->any())->method('getCode')
+ ->willReturn(self::PAYMENT_METHOD_BRAINTREE_CC_VAULT);
+
+ $paymentMethods = [
+ $checkmoPaymentMethod,
+ $brainTreePaymentMethod,
+ $brainTreeCCVaultTPaymentMethod,
+ ];
+ $expectedResult1 = [
+ $checkmoPaymentMethod,
+ $brainTreePaymentMethod,
+ $brainTreeCCVaultTPaymentMethod
+ ];
+ $expectedResult2 = [
+ $checkmoPaymentMethod,
+ $brainTreePaymentMethod,
+ ];
+
+ return [
+ [$paymentMethods, $expectedResult1, [$tokenUiComponentInterface]],
+ [$paymentMethods, $expectedResult2, []],
+ ];
+ }
+}
diff --git a/app/code/Magento/Payment/composer.json b/app/code/Magento/Payment/composer.json
index 8caad77d9b36b..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": "*",
@@ -13,7 +13,8 @@
"magento/module-quote": "*",
"magento/module-sales": "*",
"magento/module-store": "*",
- "magento/module-ui": "*"
+ "magento/module-ui": "*",
+ "magento/module-vault": "*"
},
"type": "magento2-module",
"license": [
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 @@
Automatically Invoice All Items
Magento\Payment\Model\Source\Invoice
-
- processing
-
+ If 'Automatically Invoice All Items' is set to 'Yes', the order is placed in 'Processing' state.
Sort Order
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 @@
1
Magento\Payment\Model\Method\Free
pending
+ authorize_capture
No Payment Information Required
- authorize
0
1
+ offline
0
diff --git a/app/code/Magento/Payment/etc/di.xml b/app/code/Magento/Payment/etc/di.xml
index b7422bb00d543..a826dedf9f02c 100644
--- a/app/code/Magento/Payment/etc/di.xml
+++ b/app/code/Magento/Payment/etc/di.xml
@@ -81,4 +81,12 @@
Magento\Payment\Model\Method\VirtualLogger
+
+
+
+
+
+ braintree_cc_vault
+
+
diff --git a/app/code/Magento/Payment/view/base/web/images/cc/au.png b/app/code/Magento/Payment/view/base/web/images/cc/au.png
index 04cb2df8fa332..f686eb8034830 100644
Binary files a/app/code/Magento/Payment/view/base/web/images/cc/au.png and b/app/code/Magento/Payment/view/base/web/images/cc/au.png differ
diff --git a/app/code/Magento/Payment/view/base/web/images/cc/elo.png b/app/code/Magento/Payment/view/base/web/images/cc/elo.png
index eba0296a09104..eb9f18374833e 100644
Binary files a/app/code/Magento/Payment/view/base/web/images/cc/elo.png and b/app/code/Magento/Payment/view/base/web/images/cc/elo.png differ
diff --git a/app/code/Magento/Payment/view/base/web/images/cc/hc.png b/app/code/Magento/Payment/view/base/web/images/cc/hc.png
index 203e0b7e305c1..7af9ba53d3953 100644
Binary files a/app/code/Magento/Payment/view/base/web/images/cc/hc.png and b/app/code/Magento/Payment/view/base/web/images/cc/hc.png differ
diff --git a/app/code/Magento/PaymentGraphQl/Model/Resolver/IsDeferred.php b/app/code/Magento/PaymentGraphQl/Model/Resolver/IsDeferred.php
new file mode 100644
index 0000000000000..77fb250b0e48f
--- /dev/null
+++ b/app/code/Magento/PaymentGraphQl/Model/Resolver/IsDeferred.php
@@ -0,0 +1,84 @@
+paymentData = $paymentData;
+ $this->overrides = $overrides;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ if (!$value['code']) {
+ throw new LocalizedException(__('"code" value should be specified'));
+ }
+ return $this->isDeferredPaymentMethod($value['code']);
+ }
+
+ /**
+ * Identifies whether the payment method is deferred
+ *
+ * @param string $code
+ *
+ * @return bool
+ */
+ private function isDeferredPaymentMethod(string $code): bool
+ {
+ if (isset($this->overrides['deferred']) &&
+ is_array($this->overrides['deferred']) &&
+ in_array($code, $this->overrides['deferred'])
+ ) {
+ return true;
+ }
+ if (isset($this->overrides['undeferred']) &&
+ is_array($this->overrides['undeferred']) &&
+ in_array($code, $this->overrides['undeferred'])
+ ) {
+ return false;
+ }
+ return !$this->paymentData->getMethodInstance($code)->isOffline();
+ }
+}
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/PaymentGraphQl/etc/schema.graphqls b/app/code/Magento/PaymentGraphQl/etc/schema.graphqls
index 91150362dc5ef..c705ebb8b0300 100644
--- a/app/code/Magento/PaymentGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/PaymentGraphQl/etc/schema.graphqls
@@ -19,3 +19,7 @@ type StoreConfig {
check_money_order_max_order_total: String @doc(description: "The maximum order amount required to qualify for the Check/Money Order payment method.")
check_money_order_sort_order: Int @doc(description: "A number indicating the position of the Check/Money Order payment method in the list of available payment methods during checkout.")
}
+
+type AvailablePaymentMethod @doc(description: "Describes a payment method that the shopper can use to pay for the order.") {
+ is_deferred: Boolean! @doc(description: "If the payment method is an online integration") @resolver(class: "\\Magento\\PaymentGraphQl\\Model\\Resolver\\IsDeferred")
+}
\ No newline at end of file
diff --git a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php
index 95dc8ee487edf..54bc4381cd215 100644
--- a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php
+++ b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php
@@ -112,6 +112,14 @@ public function execute()
->setLastOrderStatus($order->getStatus());
}
+ $this->_eventManager->dispatch(
+ 'checkout_submit_all_after',
+ [
+ 'order' => $order,
+ 'quote' => $this->_getQuote()
+ ]
+ );
+
$this->_eventManager->dispatch(
'paypal_express_place_order_success',
[
diff --git a/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php b/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php
index ff7f9a132d0f8..a5e40d959e9eb 100644
--- a/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php
+++ b/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php
@@ -142,6 +142,14 @@ public function execute(): ResultInterface
->setLastRealOrderId($order->getIncrementId())
->setLastOrderStatus($order->getStatus());
+ $this->_eventManager->dispatch(
+ 'checkout_submit_all_after',
+ [
+ 'order' => $order,
+ 'quote' => $quote
+ ]
+ );
+
$this->_eventManager->dispatch(
'paypal_express_place_order_success',
[
diff --git a/app/code/Magento/Paypal/Model/AbstractIpn.php b/app/code/Magento/Paypal/Model/AbstractIpn.php
index b5efd1e2529da..0164bd8eb9525 100644
--- a/app/code/Magento/Paypal/Model/AbstractIpn.php
+++ b/app/code/Magento/Paypal/Model/AbstractIpn.php
@@ -6,7 +6,14 @@
namespace Magento\Paypal\Model;
+use Exception;
+use Laminas\Http\Request;
+use Laminas\Http\Response;
use Magento\Framework\Exception\RemoteServiceUnavailableException;
+use Magento\Framework\HTTP\Adapter\Curl;
+use Magento\Framework\HTTP\Adapter\CurlFactory;
+use Psr\Log\LoggerInterface;
+use Throwable;
/**
* Abstract Ipn class for paypal
@@ -33,30 +40,30 @@ class AbstractIpn
protected $_debugData = [];
/**
- * @var \Magento\Paypal\Model\ConfigFactory
+ * @var ConfigFactory
*/
protected $_configFactory;
/**
- * @var \Magento\Framework\HTTP\Adapter\CurlFactory
+ * @var CurlFactory
*/
protected $_curlFactory;
/**
- * @var \Psr\Log\LoggerInterface
+ * @var LoggerInterface
*/
private $logger;
/**
- * @param \Magento\Paypal\Model\ConfigFactory $configFactory
- * @param \Psr\Log\LoggerInterface $logger
- * @param \Magento\Framework\HTTP\Adapter\CurlFactory $curlFactory
+ * @param ConfigFactory $configFactory
+ * @param LoggerInterface $logger
+ * @param CurlFactory $curlFactory
* @param array $data
*/
public function __construct(
- \Magento\Paypal\Model\ConfigFactory $configFactory,
- \Psr\Log\LoggerInterface $logger,
- \Magento\Framework\HTTP\Adapter\CurlFactory $curlFactory,
+ ConfigFactory $configFactory,
+ LoggerInterface $logger,
+ CurlFactory $curlFactory,
array $data = []
) {
$this->_configFactory = $configFactory;
@@ -84,20 +91,21 @@ public function getRequestData($key = null)
*
* @return void
* @throws RemoteServiceUnavailableException
- * @throws \Exception
+ * @throws Exception
*/
protected function _postBack()
{
+ /** @var Curl $httpAdapter */
$httpAdapter = $this->_curlFactory->create();
$postbackQuery = http_build_query($this->getRequestData()) . '&cmd=_notify-validate';
$postbackUrl = $this->_config->getPayPalIpnUrl();
$this->_addDebugData('postback_to', $postbackUrl);
- $httpAdapter->setConfig(['verifypeer' => $this->_config->getValue('verifyPeer')]);
- $httpAdapter->write(\Zend_Http_Client::POST, $postbackUrl, '1.1', ['Connection: close'], $postbackQuery);
+ $httpAdapter->setOptions(['verifypeer' => $this->_config->getValue('verifyPeer')]);
+ $httpAdapter->write(Request::METHOD_POST, $postbackUrl, '1.1', ['Connection: close'], $postbackQuery);
try {
$postbackResult = $httpAdapter->read();
- } catch (\Exception $e) {
+ } catch (Exception $e) {
$this->_addDebugData('http_error', ['error' => $e->getMessage(), 'code' => $e->getCode()]);
throw $e;
}
@@ -105,7 +113,7 @@ protected function _postBack()
/*
* Handle errors on PayPal side.
*/
- $responseCode = \Zend_Http_Response::extractCode($postbackResult);
+ $responseCode = $this->extractCodeFromResponse($postbackResult);
if (empty($postbackResult) || in_array($responseCode, ['500', '502', '503'])) {
if (empty($postbackResult)) {
$reason = 'Empty response.';
@@ -122,7 +130,7 @@ protected function _postBack()
$this->_addDebugData('postback', $postbackQuery);
$this->_addDebugData('postback_result', $postbackResult);
// phpcs:ignore Magento2.Exceptions.DirectThrow
- throw new \Exception('PayPal IPN postback failure. See system.log for details.');
+ throw new Exception('PayPal IPN postback failure. See system.log for details.');
}
}
@@ -191,4 +199,22 @@ protected function _addDebugData($key, $value)
$this->_debugData[$key] = $value;
return $this;
}
+
+ /**
+ * Extract the response code from a response string
+ *
+ * @param string $responseString
+ *
+ * @return false|int
+ */
+ private function extractCodeFromResponse(string $responseString)
+ {
+ try {
+ $responseCode = Response::fromString($responseString)->getStatusCode();
+ } catch (Throwable $e) {
+ $responseCode = false;
+ }
+
+ return $responseCode;
+ }
}
diff --git a/app/code/Magento/Paypal/Model/Api/Nvp.php b/app/code/Magento/Paypal/Model/Api/Nvp.php
index 2f44f307be4b2..2173c34d67a38 100644
--- a/app/code/Magento/Paypal/Model/Api/Nvp.php
+++ b/app/code/Magento/Paypal/Model/Api/Nvp.php
@@ -6,7 +6,9 @@
namespace Magento\Paypal\Model\Api;
+use Laminas\Http\Request;
use Magento\Framework\DataObject;
+use Magento\Framework\HTTP\Adapter\Curl;
use Magento\Payment\Gateway\Http\ClientException;
use Magento\Payment\Model\Cart;
use Magento\Payment\Model\Method\Logger;
@@ -1174,6 +1176,7 @@ public function call($methodName, array $request)
$debugData = ['url' => $this->getApiEndpoint(), $methodName => $request];
try {
+ /** @var Curl $http */
$http = $this->_curlFactory->create();
$config = ['timeout' => 60, 'verifypeer' => $this->_config->getValue('verifyPeer')];
if ($this->getUseProxy()) {
@@ -1182,9 +1185,9 @@ public function call($methodName, array $request)
if ($this->getUseCertAuthentication()) {
$config['ssl_cert'] = $this->getApiCertificate();
}
- $http->setConfig($config);
+ $http->setOptions($config);
$http->write(
- \Zend_Http_Client::POST,
+ Request::METHOD_POST,
$this->getApiEndpoint(),
'1.1',
$this->_headers,
diff --git a/app/code/Magento/Paypal/Model/Payflow/Service/Gateway.php b/app/code/Magento/Paypal/Model/Payflow/Service/Gateway.php
index 00d5f05f047f2..14c0675a81407 100644
--- a/app/code/Magento/Paypal/Model/Payflow/Service/Gateway.php
+++ b/app/code/Magento/Paypal/Model/Payflow/Service/Gateway.php
@@ -5,9 +5,11 @@
*/
namespace Magento\Paypal\Model\Payflow\Service;
+use Laminas\Http\Exception\RuntimeException;
+use Laminas\Http\Request;
use Magento\Framework\DataObject;
-use Magento\Framework\HTTP\ZendClient;
-use Magento\Framework\HTTP\ZendClientFactory;
+use Magento\Framework\HTTP\LaminasClient;
+use Magento\Framework\HTTP\LaminasClientFactory;
use Magento\Framework\Math\Random;
use Magento\Payment\Model\Method\ConfigInterface;
use Magento\Payment\Model\Method\Logger;
@@ -19,7 +21,7 @@
class Gateway implements GatewayInterface
{
/**
- * @var ZendClientFactory
+ * @var LaminasClientFactory
*/
protected $httpClientFactory;
@@ -34,12 +36,12 @@ class Gateway implements GatewayInterface
protected $logger;
/**
- * @param ZendClientFactory $httpClientFactory
+ * @param LaminasClientFactory $httpClientFactory
* @param Random $mathRandom
* @param Logger $logger
*/
public function __construct(
- ZendClientFactory $httpClientFactory,
+ LaminasClientFactory $httpClientFactory,
Random $mathRandom,
Logger $logger
) {
@@ -55,7 +57,7 @@ public function __construct(
* @param ConfigInterface $config
*
* @return DataObject
- * @throws \Zend_Http_Client_Exception
+ * @throws RuntimeException
*/
public function postRequest(DataObject $request, ConfigInterface $config)
{
@@ -75,7 +77,7 @@ public function postRequest(DataObject $request, ConfigInterface $config)
$clientConfig['proxytype'] = CURLPROXY_HTTP;
}
- /** @var ZendClient $client */
+ /** @var LaminasClient $client */
$client = $this->httpClientFactory->create();
$client->setUri(
@@ -83,8 +85,8 @@ public function postRequest(DataObject $request, ConfigInterface $config)
? $config->getValue('transaction_url_test_mode')
: $config->getValue('transaction_url')
);
- $client->setConfig($clientConfig);
- $client->setMethod(\Zend_Http_Client::POST);
+ $client->setOptions($clientConfig);
+ $client->setMethod(Request::METHOD_POST);
$requestData = $this->prepareRequestData($request->getData());
$client->setParameterPost($requestData);
$client->setHeaders(
@@ -97,12 +99,12 @@ public function postRequest(DataObject $request, ConfigInterface $config)
$client->setUrlEncodeBody(false);
try {
- $response = $client->request();
+ $response = $client->send();
$responseArray = $this->parseNVP(strstr($response->getBody(), 'RESULT'));
$result->setData(array_change_key_case($responseArray, CASE_LOWER));
$result->setData('result_code', $result->getData('result'));
- } catch (\Zend_Http_Client_Exception $e) {
+ } catch (RuntimeException $e) {
$result->addData(
[
'response_code' => -1,
diff --git a/app/code/Magento/Paypal/Model/Payflowpro.php b/app/code/Magento/Paypal/Model/Payflowpro.php
index ce9cb1f839f1a..3cef8606b78b8 100644
--- a/app/code/Magento/Paypal/Model/Payflowpro.php
+++ b/app/code/Magento/Paypal/Model/Payflowpro.php
@@ -6,6 +6,7 @@
namespace Magento\Paypal\Model;
+use Laminas\Http\Exception\RuntimeException;
use Magento\Framework\DataObject;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\State\InvalidTransitionException;
@@ -612,7 +613,7 @@ public function postRequest(DataObject $request, ConfigInterface $config)
{
try {
return $this->gateway->postRequest($request, $config);
- } catch (\Zend_Http_Client_Exception $e) {
+ } catch (RuntimeException $e) {
throw new ClientException(
__('Payment Gateway is unreachable at the moment. Please use another payment option.'),
$e
diff --git a/app/code/Magento/Paypal/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Paypal/Test/Mftf/Data/AdminMenuData.xml
index 207bf62abf3ce..d4653fb7fc798 100644
--- a/app/code/Magento/Paypal/Test/Mftf/Data/AdminMenuData.xml
+++ b/app/code/Magento/Paypal/Test/Mftf/Data/AdminMenuData.xml
@@ -13,11 +13,6 @@
PayPal Settlement
magento-paypal-report-salesroot-paypal-settlement-reports
-
- Sales
- Sales
- magento-sales-sales
-
Billing Agreements
Billing Agreements
diff --git a/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml b/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml
index 533df1003282d..95e69cf6e93cf 100644
--- a/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml
+++ b/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml
@@ -216,4 +216,11 @@
1111
01/2030
+
+ rlus_1349181941_biz@ebay.com
+ rlus_1349181941_biz_api1.ebay.com
+ MDNAZYSJBDYNA6BN
+ AFcWxV21C7fd0v3bYYYRCpSSRl31AqoP3QLd.JUUpDPuPpQIgT0-m401
+ 54Z2EE6T7PRB4
+
diff --git a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalAdvancedSettingConfigSection.xml b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalAdvancedSettingConfigSection.xml
index 5b4b73804b9dd..feb889ec7660f 100644
--- a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalAdvancedSettingConfigSection.xml
+++ b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection/PayPalAdvancedSettingConfigSection.xml
@@ -11,5 +11,6 @@
-
+
+
diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminEnablePaypalExpressCheckoutAndValidateTheGuestCheckoutPaymentWorksWithPaypalExpressTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminEnablePaypalExpressCheckoutAndValidateTheGuestCheckoutPaymentWorksWithPaypalExpressTest.xml
new file mode 100644
index 0000000000000..33ef8968137b7
--- /dev/null
+++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminEnablePaypalExpressCheckoutAndValidateTheGuestCheckoutPaymentWorksWithPaypalExpressTest.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml
index d73021876325b..24713366958a4 100644
--- a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml
+++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml
@@ -61,7 +61,9 @@
-
+
+
+
grabRate
[USD / EUR rate:]
diff --git a/app/code/Magento/Paypal/Test/Unit/Model/IpnTest.php b/app/code/Magento/Paypal/Test/Unit/Model/IpnTest.php
index feec12ed6e53a..25e4cd612b807 100644
--- a/app/code/Magento/Paypal/Test/Unit/Model/IpnTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Model/IpnTest.php
@@ -90,12 +90,12 @@ protected function setUp(): void
->willReturn('https://ipnpb_paypal_url');
$this->curlFactory = $this->getMockBuilder(CurlFactory::class)
- ->addMethods(['setConfig', 'write', 'read'])
+ ->addMethods(['setOptions', 'write', 'read'])
->onlyMethods(['create'])
->disableOriginalConstructor()
->getMock();
$this->curlFactory->expects($this->any())->method('create')->willReturnSelf();
- $this->curlFactory->expects($this->any())->method('setConfig')->willReturnSelf();
+ $this->curlFactory->expects($this->any())->method('setOptions')->willReturnSelf();
$this->curlFactory->expects($this->any())->method('write')->willReturnSelf();
$this->curlFactory->expects($this->any())->method('read')->willReturn(
'
diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/GatewayTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/GatewayTest.php
index a2d8111ec33c6..995297939eca2 100644
--- a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/GatewayTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/GatewayTest.php
@@ -7,9 +7,11 @@
namespace Magento\Paypal\Test\Unit\Model\Payflow\Service;
+use Laminas\Http\Exception\RuntimeException;
+use Laminas\Http\Response;
use Magento\Framework\DataObject;
-use Magento\Framework\HTTP\ZendClient;
-use Magento\Framework\HTTP\ZendClientFactory;
+use Magento\Framework\HTTP\LaminasClient;
+use Magento\Framework\HTTP\LaminasClientFactory;
use Magento\Framework\Math\Random;
use Magento\Payment\Model\Method\ConfigInterface;
use Magento\Payment\Model\Method\Logger;
@@ -18,8 +20,6 @@
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use ReflectionMethod;
-use Zend_Http_Client_Exception;
-use Zend_Http_Response;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -32,7 +32,7 @@ class GatewayTest extends TestCase
private $object;
/**
- * @var ZendClientFactory|MockObject
+ * @var LaminasClientFactory|MockObject
*/
private $httpClientFactoryMock;
@@ -47,26 +47,26 @@ class GatewayTest extends TestCase
private $loggerMock;
/**
- * @var ZendClient|MockObject
+ * @var LaminasClient|MockObject
*/
- private $zendClientMock;
+ private $httpClientMock;
/**
* @inheritdoc
*/
protected function setUp(): void
{
- $this->httpClientFactoryMock = $this->getMockBuilder(ZendClientFactory::class)
+ $this->httpClientFactoryMock = $this->getMockBuilder(LaminasClientFactory::class)
->setMethods(['create'])
->disableOriginalConstructor()
->getMock();
- $this->zendClientMock = $this->getMockBuilder(ZendClient::class)
- ->setMethods(['request', 'setUri'])
+ $this->httpClientMock = $this->getMockBuilder(LaminasClient::class)
+ ->setMethods(['send', 'setUri'])
->disableOriginalConstructor()
->getMock();
$this->httpClientFactoryMock->expects(static::once())
->method('create')
- ->willReturn($this->zendClientMock);
+ ->willReturn($this->httpClientMock);
$this->mathRandomMock = $this->getMockBuilder(Random::class)
->disableOriginalConstructor()
->getMock();
@@ -97,22 +97,25 @@ public function testPostRequestOk(string $nvpResponse, array $expectedResult): v
/** @var ConfigInterface|MockObject $configInterfaceMock */
$configInterfaceMock = $this->getMockBuilder(ConfigInterface::class)
->getMockForAbstractClass();
- $zendResponseMock = $this->getMockBuilder(Zend_Http_Response::class)
+ $responseMock = $this->getMockBuilder(Response::class)
->setMethods(['getBody'])
->disableOriginalConstructor()
->getMock();
- $zendResponseMock->expects(static::once())
+ $responseMock->expects(static::once())
->method('getBody')
->willReturn($nvpResponse);
- $this->zendClientMock->expects(static::once())
- ->method('request')
- ->willReturn($zendResponseMock);
+ $this->httpClientMock->expects(static::once())
+ ->method('send')
+ ->willReturn($responseMock);
$configInterfaceMock->expects(static::any())
->method('getValue')
->willReturnMap($configMap);
$this->loggerMock->expects(static::once())
->method('debug');
+ $this->mathRandomMock->expects(static::once())
+ ->method('getUniqueHash')
+ ->willReturn('UniqueHash');
$object = new DataObject();
@@ -184,28 +187,31 @@ public function testRequestBody(array $requestData, string $requestBody): void
/** @var ConfigInterface|MockObject $configInterfaceMock */
$configInterfaceMock = $this->getMockBuilder(ConfigInterface::class)
->getMockForAbstractClass();
- $zendResponseMock = $this->getMockBuilder(Zend_Http_Response::class)
+ $responseMock = $this->getMockBuilder(Response::class)
->setMethods(['getBody'])
->disableOriginalConstructor()
->getMock();
- $zendResponseMock->expects(static::once())
+ $responseMock->expects(static::once())
->method('getBody')
->willReturn('RESULT=0&RESPMSG=Approved');
- $this->zendClientMock->expects(static::once())
- ->method('request')
- ->willReturn($zendResponseMock);
+ $this->httpClientMock->expects(static::once())
+ ->method('send')
+ ->willReturn($responseMock);
$configInterfaceMock->expects(static::any())
->method('getValue')
->willReturnMap($configMap);
$this->loggerMock->expects(static::once())
->method('debug');
+ $this->mathRandomMock->expects(static::once())
+ ->method('getUniqueHash')
+ ->willReturn('UniqueHash');
$request = new DataObject($requestData);
$this->object->postRequest($request, $configInterfaceMock);
- $method = new ReflectionMethod($this->zendClientMock, '_prepareBody');
+ $method = new ReflectionMethod($this->httpClientMock, 'prepareBody');
$method->setAccessible(true);
- $this->assertEquals($requestBody, $method->invoke($this->zendClientMock));
+ $this->assertEquals($requestBody, urldecode($method->invoke($this->httpClientMock)));
}
/**
@@ -235,19 +241,22 @@ public function requestBodyDataProvider(): array
public function testPostRequestFail()
{
- $this->expectException('Zend_Http_Client_Exception');
+ $this->expectException(RuntimeException::class);
/** @var ConfigInterface|MockObject $configInterfaceMock */
$configInterfaceMock = $this->getMockBuilder(ConfigInterface::class)
->getMockForAbstractClass();
- $zendResponseMock = $this->getMockBuilder(Zend_Http_Response::class)
+ $responseMock = $this->getMockBuilder(Response::class)
->setMethods(['getBody'])
->disableOriginalConstructor()
->getMock();
- $zendResponseMock->expects(static::never())
+ $responseMock->expects(static::never())
->method('getBody');
- $this->zendClientMock->expects(static::once())
- ->method('request')
- ->willThrowException(new Zend_Http_Client_Exception());
+ $this->httpClientMock->expects(static::once())
+ ->method('send')
+ ->willThrowException(new RuntimeException());
+ $this->mathRandomMock->expects(static::once())
+ ->method('getUniqueHash')
+ ->willReturn('UniqueHash');
$object = new DataObject();
$this->object->postRequest($object, $configInterfaceMock);
diff --git a/app/code/Magento/Paypal/Test/Unit/Model/PayflowproTest.php b/app/code/Magento/Paypal/Test/Unit/Model/PayflowproTest.php
index 8a3a013ea970c..874204d2c9f05 100644
--- a/app/code/Magento/Paypal/Test/Unit/Model/PayflowproTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Model/PayflowproTest.php
@@ -7,11 +7,12 @@
namespace Magento\Paypal\Test\Unit\Model;
+use Laminas\Http\Exception\RuntimeException;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\DataObject;
use Magento\Framework\Event\ManagerInterface;
-use Magento\Framework\HTTP\ZendClient;
-use Magento\Framework\HTTP\ZendClientFactory;
+use Magento\Framework\HTTP\LaminasClient;
+use Magento\Framework\HTTP\LaminasClientFactory;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Payment\Model\Info;
use Magento\Payment\Model\InfoInterface;
@@ -92,10 +93,10 @@ protected function setUp(): void
$configFactoryMock->method('create')
->willReturn($this->configMock);
- $client = $this->getMockBuilder(ZendClient::class)
+ $client = $this->getMockBuilder(LaminasClient::class)
->getMock();
- $clientFactory = $this->getMockBuilder(ZendClientFactory::class)
+ $clientFactory = $this->getMockBuilder(LaminasClientFactory::class)
->disableOriginalConstructor()
->getMock();
$clientFactory->method('create')->willReturn($client);
@@ -634,7 +635,7 @@ public function testPostRequestException(): void
$this->gatewayMock->expects(static::once())
->method('postRequest')
->with($request, $config)
- ->willThrowException(new \Zend_Http_Client_Exception());
+ ->willThrowException(new RuntimeException());
$this->payflowpro->postRequest($request, $config);
}
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/Test/Mftf/Test/StorefrontPaymentsCaptchaWithPayflowProTest.xml b/app/code/Magento/PaypalCaptcha/Test/Mftf/Test/StorefrontPaymentsCaptchaWithPayflowProTest.xml
index 0a84cebfe3770..4f23433147be6 100644
--- a/app/code/Magento/PaypalCaptcha/Test/Mftf/Test/StorefrontPaymentsCaptchaWithPayflowProTest.xml
+++ b/app/code/Magento/PaypalCaptcha/Test/Mftf/Test/StorefrontPaymentsCaptchaWithPayflowProTest.xml
@@ -20,6 +20,7 @@
+
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/Controller/Adminhtml/Product/Gallery/RetrieveImage.php b/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php
index dae322fbff3bc..94abb33227683 100644
--- a/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php
+++ b/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php
@@ -21,7 +21,7 @@ class RetrieveImage extends \Magento\Backend\App\Action implements HttpPostActio
*
* @see _isAllowed()
*/
- const ADMIN_RESOURCE = 'Magento_Catalog::products';
+ public const ADMIN_RESOURCE = 'Magento_Catalog::products';
/**
* @var \Magento\Framework\Controller\Result\RawFactory
@@ -103,6 +103,8 @@ public function __construct(
}
/**
+ * Execute retrieve image action
+ *
* @return \Magento\Framework\Controller\Result\Raw
*/
public function execute()
@@ -111,6 +113,7 @@ public function execute()
try {
$remoteFileUrl = $this->getRequest()->getParam('remote_image');
$this->validateRemoteFile($remoteFileUrl);
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$localFileName = Uploader::getCorrectFileName(basename($remoteFileUrl));
$localTmpFileName = Uploader::getDispersionPath($localFileName) . DIRECTORY_SEPARATOR . $localFileName;
$localFilePath = $baseTmpMediaPath . ($localTmpFileName);
@@ -163,6 +166,7 @@ private function validateRemoteFile($remoteFileUrl)
*/
private function validateRemoteFileExtensions($filePath)
{
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
if (!$this->extensionValidator->isValid($extension)) {
throw new \Magento\Framework\Exception\ValidatorException(__('Disallowed file type.'));
@@ -170,11 +174,14 @@ private function validateRemoteFileExtensions($filePath)
}
/**
+ * Get image info
+ *
* @param string $fileName
* @return mixed
*/
protected function appendResultSaveRemoteImage($fileName)
{
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$fileInfo = pathinfo($fileName);
$tmpFileName = Uploader::getDispersionPath($fileInfo['basename']) . DIRECTORY_SEPARATOR . $fileInfo['basename'];
$result['name'] = $fileInfo['basename'];
@@ -197,7 +204,7 @@ protected function appendResultSaveRemoteImage($fileName)
*/
protected function retrieveRemoteImage($fileUrl, $localFilePath)
{
- $this->curl->setConfig(['header' => false]);
+ $this->curl->setOptions(['header' => false]);
$this->curl->write('GET', $fileUrl);
$image = $this->curl->read();
if (empty($image)) {
@@ -209,6 +216,8 @@ protected function retrieveRemoteImage($fileUrl, $localFilePath)
}
/**
+ * Get local file path
+ *
* @param string $localFilePath
* @return string
*/
@@ -216,11 +225,14 @@ protected function appendNewFileName($localFilePath)
{
$destinationFile = $this->appendAbsoluteFileSystemPath($localFilePath);
$fileName = Uploader::getNewFileName($destinationFile);
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$fileInfo = pathinfo($localFilePath);
return $fileInfo['dirname'] . DIRECTORY_SEPARATOR . $fileName;
}
/**
+ * Get local temporary file path
+ *
* @param string $localTmpFile
* @return string
*/
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 c4f999a52b0b2..9be1e9d32e379 100644
--- a/app/code/Magento/Quote/Model/Cart/AddProductsToCart.php
+++ b/app/code/Magento/Quote/Model/Cart/AddProductsToCart.php
@@ -7,7 +7,6 @@
namespace Magento\Quote\Model\Cart;
-use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Model\Cart\BuyRequest\BuyRequestBuilder;
@@ -22,34 +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 ProductRepositoryInterface
- */
- private $productRepository;
-
/**
* @var CartRepositoryInterface
*/
@@ -66,21 +37,34 @@ class AddProductsToCart
private $requestBuilder;
/**
- * @param ProductRepositoryInterface $productRepository
+ * @var ProductReaderInterface
+ */
+ private $productReader;
+
+ /**
+ * @var AddProductsToCartError
+ */
+ private $error;
+
+ /**
* @param CartRepositoryInterface $cartRepository
* @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId
* @param BuyRequestBuilder $requestBuilder
+ * @param ProductReaderInterface $productReader
+ * @param AddProductsToCartError $addProductsToCartError
*/
public function __construct(
- ProductRepositoryInterface $productRepository,
CartRepositoryInterface $cartRepository,
MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId,
- BuyRequestBuilder $requestBuilder
+ BuyRequestBuilder $requestBuilder,
+ ProductReaderInterface $productReader,
+ AddProductsToCartError $addProductsToCartError
) {
- $this->productRepository = $productRepository;
$this->cartRepository = $cartRepository;
$this->maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId;
$this->requestBuilder = $requestBuilder;
+ $this->productReader = $productReader;
+ $this->error = $addProductsToCartError;
}
/**
@@ -101,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());
}
}
@@ -122,11 +106,9 @@ public function execute(string $maskedCartId, array $cartItems): AddProductsToCa
}
}
}
-
if ($saveCart) {
$this->cartRepository->save($cart);
}
-
if (count($allErrors) !== 0) {
/* Revert changes introduced by add to cart processes in case of an error */
$cart->getItemsCollection()->clear();
@@ -145,7 +127,14 @@ public function execute(string $maskedCartId, array $cartItems): AddProductsToCa
public function addItemsToCart(Quote $cart, array $cartItems): array
{
$failedCartItems = [];
-
+ // add new cart items for preload
+ $skus = \array_map(
+ function ($item) {
+ return $item->getSku();
+ },
+ $cartItems
+ );
+ $this->productReader->loadProducts($skus, $cart->getStoreId());
foreach ($cartItems as $cartItemPosition => $cartItem) {
$errors = $this->addItemToCart($cart, $cartItem, $cartItemPosition);
if ($errors) {
@@ -169,37 +158,33 @@ private function addItemToCart(Quote $cart, Data\CartItem $cartItem, int $cartIt
$sku = $cartItem->getSku();
$errors = [];
$result = null;
- $product = null;
if ($cartItem->getQuantity() <= 0) {
- $errors[] = $this->createError(
+ $errors[] = $this->error->create(
__('The product quantity should be greater than 0')->render(),
$cartItemPosition
);
} else {
- try {
- $product = $this->productRepository->get($sku, false, null, true);
- } catch (NoSuchEntityException $e) {
- $errors[] = $this->createError(
+ $product = $this->productReader->getProductBySku($sku);
+ if (!$product || !$product->isSaleable() || !$product->isAvailable()) {
+ $errors[] = $this->error->create(
__('Could not find a product with SKU "%sku"', ['sku' => $sku])->render(),
$cartItemPosition
);
- }
-
- if ($product !== null) {
+ } else {
try {
$result = $cart->addProduct($product, $this->requestBuilder->build($cartItem));
} catch (\Throwable $e) {
- $errors[] = $this->createError(
+ $errors[] = $this->error->create(
__($e->getMessage())->render(),
$cartItemPosition
);
}
+ }
- if (is_string($result)) {
- foreach (array_unique(explode("\n", $result)) as $error) {
- $errors[] = $this->createError(__($error)->render(), $cartItemPosition);
- }
+ if (is_string($result)) {
+ foreach (array_unique(explode("\n", $result)) as $error) {
+ $errors[] = $this->error->create(__($error)->render(), $cartItemPosition);
}
}
}
@@ -207,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/Model/Cart/CartTotalRepository.php b/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php
index 7952a6e9c4b4c..fec137ad0a427 100644
--- a/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php
+++ b/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php
@@ -32,8 +32,6 @@ class CartTotalRepository implements CartTotalRepositoryInterface
private $totalsFactory;
/**
- * Quote repository.
- *
* @var \Magento\Quote\Api\CartRepositoryInterface
*/
private $quoteRepository;
@@ -109,11 +107,7 @@ public function get($cartId): QuoteTotalsInterface
$items = array_map([$this->itemConverter, 'modelToDataObject'], $quote->getAllVisibleItems());
$calculatedTotals = $this->totalsConverter->process($addressTotals);
$quoteTotals->setTotalSegments($calculatedTotals);
-
- $amount = $quoteTotals->getGrandTotal() - $quoteTotals->getTaxAmount();
- $amount = $amount > 0 ? $amount : 0;
$quoteTotals->setCouponCode($this->couponService->get($cartId));
- $quoteTotals->setGrandTotal($amount);
$quoteTotals->setItems($items);
$quoteTotals->setItemsQty($quote->getItemsQty());
$quoteTotals->setBaseCurrencyCode($quote->getBaseCurrencyCode());
diff --git a/app/code/Magento/Quote/Model/Cart/ProductReader.php b/app/code/Magento/Quote/Model/Cart/ProductReader.php
new file mode 100644
index 0000000000000..6a333e8b9b795
--- /dev/null
+++ b/app/code/Magento/Quote/Model/Cart/ProductReader.php
@@ -0,0 +1,78 @@
+productCollectionFactory = $productCollectionFactory;
+ $this->quoteConfig = $quoteConfig;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function loadProducts(array $skus, int $storeId): void
+ {
+ $this->productCollection = $this->productCollectionFactory->create();
+
+ $this->productCollection->addAttributeToSelect($this->quoteConfig->getProductAttributes());
+ $this->productCollection->setStoreId($storeId);
+ $this->productCollection->addStoreFilter($storeId);
+ $this->productCollection->addFieldToFilter(ProductInterface::SKU, ['in' => $skus]);
+ $this->productCollection->joinAttribute('status', 'catalog_product/status', 'entity_id', null, 'inner');
+ $this->productCollection->joinAttribute('visibility', 'catalog_product/visibility', 'entity_id', null, 'inner');
+ $this->productCollection->load();
+ foreach ($this->productCollection->getItems() as $productItem) {
+ $this->productsBySku[$productItem->getData(ProductInterface::SKU)] = $productItem;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getProductBySku(string $sku) : ?ProductInterface
+ {
+ return $this->productsBySku[$sku] ?? null;
+ }
+}
diff --git a/app/code/Magento/Quote/Model/Cart/ProductReaderInterface.php b/app/code/Magento/Quote/Model/Cart/ProductReaderInterface.php
new file mode 100644
index 0000000000000..e750ad246cc54
--- /dev/null
+++ b/app/code/Magento/Quote/Model/Cart/ProductReaderInterface.php
@@ -0,0 +1,33 @@
+getItemsCollection() as $item) {
- /** @var \Magento\Quote\Model\Quote\Item $item */
- if (!$item->isDeleted()) {
+ $product = $item->getProduct();
+ if (!$item->isDeleted() && ($product && (int)$product->getStatus() !== ProductStatus::STATUS_DISABLED)) {
$items[] = $item;
}
}
+
return $items;
}
@@ -1839,8 +1843,14 @@ public function updateItem($itemId, $buyRequest, $params = null)
*/
public function getItemByProduct($product)
{
- foreach ($this->getAllItems() as $item) {
- if ($item->representProduct($product)) {
+ /** @var \Magento\Quote\Model\Quote\Item[] $items */
+ $items = $this->getItemsCollection()->getItemsByColumnValue('product_id', $product->getId());
+ foreach ($items as $item) {
+ if (!$item->isDeleted()
+ && $item->getProduct()
+ && $item->getProduct()->getStatus() !== ProductStatus::STATUS_DISABLED
+ && $item->representProduct($product)
+ ) {
return $item;
}
}
diff --git a/app/code/Magento/Quote/Model/Quote/Address.php b/app/code/Magento/Quote/Model/Quote/Address.php
index 88d91d8efc589..2d3c072d5d882 100644
--- a/app/code/Magento/Quote/Model/Quote/Address.php
+++ b/app/code/Magento/Quote/Model/Quote/Address.php
@@ -1228,9 +1228,10 @@ public function validateMinimumAmount()
? $this->getBaseTaxAmount() + $this->getBaseDiscountTaxCompensationAmount()
: 0;
+ // Note: ($x > $y - 0.0001) means ($x >= $y) for floats
return $includeDiscount ?
- ($this->getBaseSubtotalWithDiscount() + $taxes >= $amount) :
- ($this->getBaseSubtotal() + $taxes >= $amount);
+ ($this->getBaseSubtotalWithDiscount() + $taxes > $amount - 0.0001) :
+ ($this->getBaseSubtotal() + $taxes > $amount - 0.0001);
}
/**
@@ -1386,7 +1387,7 @@ public function getBaseTotalAmount($code)
*/
public function getBaseSubtotalWithDiscount()
{
- return $this->getBaseSubtotal() + $this->getBaseDiscountAmount();
+ return $this->getBaseSubtotal() + $this->getBaseDiscountAmount() + $this->getBaseShippingDiscountAmount();
}
/**
diff --git a/app/code/Magento/Quote/Model/Quote/Address/Validator.php b/app/code/Magento/Quote/Model/Quote/Address/Validator.php
index f920fd6eb99be..b497cbcf4bdba 100644
--- a/app/code/Magento/Quote/Model/Quote/Address/Validator.php
+++ b/app/code/Magento/Quote/Model/Quote/Address/Validator.php
@@ -6,20 +6,25 @@
namespace Magento\Quote\Model\Quote\Address;
-use Zend_Validate_Exception;
+use Magento\Directory\Model\CountryFactory;
+use Magento\Framework\Validator\AbstractValidator;
+use Magento\Framework\Validator\EmailAddress;
+use Magento\Framework\Validator\ValidateException;
+use Magento\Framework\Validator\ValidatorChain;
+use Magento\Quote\Model\Quote\Address;
-class Validator extends \Magento\Framework\Validator\AbstractValidator
+class Validator extends AbstractValidator
{
/**
- * @var \Magento\Directory\Model\CountryFactory
+ * @var CountryFactory
*/
protected $countryFactory;
/**
- * @param \Magento\Directory\Model\CountryFactory $countryFactory
+ * @param CountryFactory $countryFactory
*/
public function __construct(
- \Magento\Directory\Model\CountryFactory $countryFactory
+ CountryFactory $countryFactory
) {
$this->countryFactory = $countryFactory;
}
@@ -31,15 +36,15 @@ public function __construct(
* getMessages() will return an array of messages that explain why the
* validation failed.
*
- * @param \Magento\Quote\Model\Quote\Address $value
+ * @param Address $value
* @return boolean
- * @throws Zend_Validate_Exception If validation of $value is impossible
+ * @throws ValidateException If validation of $value is impossible
*/
public function isValid($value)
{
$messages = [];
$email = $value->getEmail();
- if (!empty($email) && !\Zend_Validate::is($email, \Magento\Framework\Validator\EmailAddress::class)) {
+ if (!empty($email) && !ValidatorChain::is($email, EmailAddress::class)) {
$messages['invalid_email_format'] = 'Invalid email format';
}
diff --git a/app/code/Magento/Quote/Model/Quote/Item/CartItemPersister.php b/app/code/Magento/Quote/Model/Quote/Item/CartItemPersister.php
index 41651d3c788a4..5481f57695d11 100644
--- a/app/code/Magento/Quote/Model/Quote/Item/CartItemPersister.php
+++ b/app/code/Magento/Quote/Model/Quote/Item/CartItemPersister.php
@@ -75,7 +75,7 @@ public function save(CartInterface $quote, CartItemInterface $item)
$buyRequestData = $this->cartItemOptionProcessor->getBuyRequest($productType, $item);
if (is_object($buyRequestData)) {
/** Update item product options */
- if ($currentItem->getQty() !== $buyRequestData->getQty()) {
+ if ($quote->getIsActive()) {
$item = $quote->updateItem($itemId, $buyRequestData);
}
} else {
diff --git a/app/code/Magento/Quote/Model/Quote/Plugin/UpdateQuoteStoreId.php b/app/code/Magento/Quote/Model/Quote/Plugin/UpdateQuoteStoreId.php
index bffa0084e35bd..ad1880a054553 100644
--- a/app/code/Magento/Quote/Model/Quote/Plugin/UpdateQuoteStoreId.php
+++ b/app/code/Magento/Quote/Model/Quote/Plugin/UpdateQuoteStoreId.php
@@ -8,7 +8,9 @@
namespace Magento\Quote\Model\Quote\Plugin;
use Magento\Quote\Model\Quote;
+use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Model\StoreManagerInterface;
+use Magento\Framework\App\Request\Http as Request;
/**
* Updates quote store id.
@@ -20,17 +22,25 @@ class UpdateQuoteStoreId
*/
private $storeManager;
+ /**
+ * @var Request
+ */
+ private $request;
+
/**
* @param StoreManagerInterface $storeManager
+ * @param Request $request
*/
public function __construct(
- StoreManagerInterface $storeManager
+ StoreManagerInterface $storeManager,
+ Request $request
) {
$this->storeManager = $storeManager;
+ $this->request = $request;
}
/**
- * Update store id in requested quote by store id from request.
+ * Update store id in requested quote by store id from guest's request.
*
* @param Quote $subject
* @param Quote $result
@@ -39,11 +49,51 @@ public function __construct(
*/
public function afterLoadByIdWithoutStore(Quote $subject, Quote $result): Quote
{
- $storeId = $this->storeManager->getStore()
- ->getId() ?: $this->storeManager->getDefaultStoreView()
- ->getId();
- $result->setStoreId($storeId);
+ return $this->updateStoreId($result);
+ }
+
+ /**
+ * Update store id in requested quote by store id from registered customer's request.
+ *
+ * @param Quote $subject
+ * @param Quote $result
+ * @return Quote
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterLoadByCustomer(Quote $subject, Quote $result): Quote
+ {
+ return $this->updateStoreId($result);
+ }
+
+ /**
+ * Returns store based on web-api request path.
+ *
+ * @param string $requestPath
+ * @return StoreInterface|null
+ */
+ private function getStore(string $requestPath): ?StoreInterface
+ {
+ $pathParts = explode('/', trim($requestPath, '/'));
+ array_shift($pathParts);
+ $storeCode = current($pathParts);
+ $stores = $this->storeManager->getStores(false, true);
+
+ return $stores[$storeCode] ?? null;
+ }
+
+ /**
+ * Update store id in requested quote by store id from request.
+ *
+ * @param Quote $quote
+ * @return Quote
+ */
+ private function updateStoreId(Quote $quote): Quote
+ {
+ $store = $this->getStore($this->request->getPathInfo());
+ if ($store) {
+ $quote->setStoreId($store->getId());
+ }
- return $result;
+ return $quote;
}
}
diff --git a/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php b/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php
index 78aa31d7d9527..c7e566b552024 100644
--- a/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php
+++ b/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php
@@ -20,6 +20,7 @@ class ValidationMessage
/**
* @var \Magento\Framework\Locale\CurrencyInterface
* @deprecated 101.0.3 since 101.0.0
+ * @see no alternatives
*/
private $currency;
@@ -51,7 +52,6 @@ public function __construct(
* Get validation message.
*
* @return \Magento\Framework\Phrase
- * @throws \Zend_Currency_Exception
*/
public function getMessage()
{
diff --git a/app/code/Magento/Quote/Model/QuoteManagement.php b/app/code/Magento/Quote/Model/QuoteManagement.php
index 51b68411d4080..dc0858f183809 100644
--- a/app/code/Magento/Quote/Model/QuoteManagement.php
+++ b/app/code/Magento/Quote/Model/QuoteManagement.php
@@ -24,6 +24,7 @@
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\StateException;
use Magento\Framework\HTTP\PhpEnvironment\RemoteAddress;
+use Magento\Framework\Lock\LockManagerInterface;
use Magento\Framework\Model\AbstractExtensibleModel;
use Magento\Framework\Validator\Exception as ValidatorException;
use Magento\Payment\Model\Method\AbstractMethod;
@@ -32,8 +33,8 @@
use Magento\Quote\Api\Data\PaymentInterface;
use Magento\Quote\Model\Quote\Address\ToOrder as ToOrderConverter;
use Magento\Quote\Model\Quote\Address\ToOrderAddress as ToOrderAddressConverter;
-use Magento\Quote\Model\Quote as QuoteEntity;
use Magento\Quote\Model\Quote\AddressFactory;
+use Magento\Quote\Model\Quote as QuoteEntity;
use Magento\Quote\Model\Quote\Item\ToOrderItem as ToOrderItemConverter;
use Magento\Quote\Model\Quote\Payment\ToOrderPayment as ToOrderPaymentConverter;
use Magento\Quote\Model\ResourceModel\Quote\Item;
@@ -51,6 +52,10 @@
*/
class QuoteManagement implements CartManagementInterface
{
+ private const LOCK_PREFIX = 'PLACE_ORDER_';
+
+ private const LOCK_TIMEOUT = 10;
+
/**
* @var EventManager
*/
@@ -151,6 +156,11 @@ class QuoteManagement implements CartManagementInterface
*/
protected $quoteFactory;
+ /**
+ * @var LockManagerInterface
+ */
+ private $lockManager;
+
/**
* @var QuoteIdMaskFactory
*/
@@ -201,6 +211,7 @@ class QuoteManagement implements CartManagementInterface
* @param AddressRepositoryInterface|null $addressRepository
* @param RequestInterface|null $request
* @param RemoteAddress $remoteAddress
+ * @param LockManagerInterface $lockManager
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -227,7 +238,8 @@ public function __construct(
QuoteIdMaskFactory $quoteIdMaskFactory = null,
AddressRepositoryInterface $addressRepository = null,
RequestInterface $request = null,
- RemoteAddress $remoteAddress = null
+ RemoteAddress $remoteAddress = null,
+ LockManagerInterface $lockManager = null
) {
$this->eventManager = $eventManager;
$this->submitQuoteValidator = $submitQuoteValidator;
@@ -257,6 +269,8 @@ public function __construct(
->get(RequestInterface::class);
$this->remoteAddress = $remoteAddress ?: ObjectManager::getInstance()
->get(RemoteAddress::class);
+ $this->lockManager = $lockManager ?: ObjectManager::getInstance()
+ ->get(LockManagerInterface::class);
}
/**
@@ -324,7 +338,7 @@ public function assignCustomer($cartId, $customerId, $storeId)
$customerActiveQuote->setIsActive(0);
$this->quoteRepository->save($customerActiveQuote);
- // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
+ // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
} catch (NoSuchEntityException $e) {
}
@@ -412,7 +426,9 @@ public function placeOrder($cartId, PaymentInterface $paymentMethod = null)
if ($quote->getCheckoutMethod() === self::METHOD_GUEST || !$customerId) {
$quote->setCustomerId(null);
$billingAddress = $quote->getBillingAddress();
- $quote->setCustomerEmail($billingAddress ? $billingAddress->getEmail() : null);
+ if (!$quote->getCustomerEmail()) {
+ $quote->setCustomerEmail($billingAddress ? $billingAddress->getEmail() : null);
+ }
if ($quote->getCustomerFirstname() === null
&& $quote->getCustomerLastname() === null
&& $billingAddress
@@ -424,8 +440,9 @@ public function placeOrder($cartId, PaymentInterface $paymentMethod = null)
}
}
$quote->setCustomerIsGuest(true);
- $groupId = $customer ? $customer->getGroupId() : GroupInterface::NOT_LOGGED_IN_ID;
- $quote->setCustomerGroupId($groupId);
+ $quote->setCustomerGroupId(
+ $quote->getCustomerId() ? $customer->getGroupId() : GroupInterface::NOT_LOGGED_IN_ID
+ );
}
$remoteAddress = $this->remoteAddress->getRemoteAddress();
@@ -581,17 +598,13 @@ protected function submitQuote(QuoteEntity $quote, $orderData = [])
$order->setCustomerFirstname($quote->getCustomerFirstname());
$order->setCustomerMiddlename($quote->getCustomerMiddlename());
$order->setCustomerLastname($quote->getCustomerLastname());
-
if ($quote->getOrigOrderId()) {
$order->setEntityId($quote->getOrigOrderId());
}
-
if ($quote->getReservedOrderId()) {
$order->setIncrementId($quote->getReservedOrderId());
}
-
$this->submitQuoteValidator->validateOrder($order);
-
$this->eventManager->dispatch(
'sales_model_service_quote_submit_before',
[
@@ -599,7 +612,15 @@ protected function submitQuote(QuoteEntity $quote, $orderData = [])
'quote' => $quote
]
);
+
+ $lockedName = self::LOCK_PREFIX . $quote->getId();
+ if ($this->lockManager->isLocked($lockedName)) {
+ throw new LocalizedException(__(
+ 'A server error stopped your order from being placed. Please try to place your order again.'
+ ));
+ }
try {
+ $this->lockManager->lock($lockedName, self::LOCK_TIMEOUT);
$order = $this->orderManagement->place($order);
$quote->setIsActive(false);
$this->eventManager->dispatch(
@@ -610,7 +631,9 @@ protected function submitQuote(QuoteEntity $quote, $orderData = [])
]
);
$this->quoteRepository->save($quote);
+ $this->lockManager->unlock($lockedName);
} catch (\Exception $e) {
+ $this->lockManager->unlock($lockedName);
$this->rollbackAddresses($quote, $order, $e);
throw $e;
}
@@ -622,12 +645,13 @@ protected function submitQuote(QuoteEntity $quote, $orderData = [])
*
* @param Quote $quote
* @return void
+ * @throws LocalizedException
+ * @throws NoSuchEntityException
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
protected function _prepareCustomerQuote($quote)
{
- /** @var Quote $quote */
$billing = $quote->getBillingAddress();
$shipping = $quote->isVirtual() ? null : $quote->getShippingAddress();
@@ -645,7 +669,7 @@ protected function _prepareCustomerQuote($quote)
if ($defaultShipping) {
try {
$shippingAddress = $this->addressRepository->getById($defaultShipping);
- // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
+ // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
} catch (LocalizedException $e) {
// no address
}
@@ -679,7 +703,7 @@ protected function _prepareCustomerQuote($quote)
if ($defaultBilling) {
try {
$billingAddress = $this->addressRepository->getById($defaultBilling);
- // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
+ // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
} catch (LocalizedException $e) {
// no address
}
@@ -700,6 +724,13 @@ protected function _prepareCustomerQuote($quote)
$billing->setCustomerAddressData($billingAddress);
$this->addressesToSync[] = $billingAddress->getId();
$billing->setCustomerAddressId($billingAddress->getId());
+
+ // Admin order: `Same As Billing Address`- when new billing address saved in address book
+ if ($shipping !== null
+ && !$shipping->getCustomerAddressId()
+ && $shipping->getSameAsBilling()) {
+ $shipping->setCustomerAddressId($billingAddress->getId());
+ }
}
}
if ($shipping && !$shipping->getCustomerId() && !$hasDefaultBilling) {
diff --git a/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php b/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php
index 01a4e0c5e8e64..b59737bff988b 100644
--- a/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php
+++ b/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php
@@ -382,7 +382,7 @@ private function removeItemsWithAbsentProducts(): void
$productCollection = $this->_productCollectionFactory->create()->addIdFilter($this->_productIds);
$existingProductsIds = $productCollection->getAllIds();
- $absentProductsIds = array_diff($this->_productIds, $existingProductsIds);
+ $absentProductsIds = array_unique(array_diff($this->_productIds, $existingProductsIds));
// Remove not existing products from items collection
if (!empty($absentProductsIds)) {
foreach ($absentProductsIds as $productIdToExclude) {
diff --git a/app/code/Magento/Quote/Model/ShippingAddressManagement.php b/app/code/Magento/Quote/Model/ShippingAddressManagement.php
index b9edcc13d0077..44e279ae4ee02 100644
--- a/app/code/Magento/Quote/Model/ShippingAddressManagement.php
+++ b/app/code/Magento/Quote/Model/ShippingAddressManagement.php
@@ -6,6 +6,7 @@
namespace Magento\Quote\Model;
+use Magento\Customer\Model\Config\Backend\Show\Customer;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\NoSuchEntityException;
@@ -18,15 +19,11 @@
class ShippingAddressManagement implements \Magento\Quote\Model\ShippingAddressManagementInterface
{
/**
- * Quote repository.
- *
* @var \Magento\Quote\Api\CartRepositoryInterface
*/
protected $quoteRepository;
/**
- * Logger.
- *
* @var Logger
*/
protected $logger;
@@ -81,6 +78,7 @@ public function __construct(
/**
* @inheritDoc
* @SuppressWarnings(PHPMD.NPathComplexity)
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function assign($cartId, \Magento\Quote\Api\Data\AddressInterface $address)
{
@@ -95,6 +93,10 @@ public function assign($cartId, \Magento\Quote\Api\Data\AddressInterface $addres
$saveInAddressBook = $address->getSaveInAddressBook() ? 1 : 0;
$sameAsBilling = $address->getSameAsBilling() ? 1 : 0;
$customerAddressId = $address->getCustomerAddressId();
+ if ($saveInAddressBook &&
+ !$this->scopeConfig->getValue(Customer::XML_PATH_CUSTOMER_ADDRESS_SHOW_COMPANY)) {
+ $address->setCompany(null);
+ }
$this->addressValidator->validateForCart($quote, $address);
$quote->setShippingAddress($address);
$address = $quote->getShippingAddress();
diff --git a/app/code/Magento/Quote/Model/ShippingMethodManagement.php b/app/code/Magento/Quote/Model/ShippingMethodManagement.php
index 8d851c71b6602..77c3a19a34ca8 100644
--- a/app/code/Magento/Quote/Model/ShippingMethodManagement.php
+++ b/app/code/Magento/Quote/Model/ShippingMethodManagement.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Quote\Model;
use Magento\Customer\Api\AddressRepositoryInterface;
@@ -25,6 +27,7 @@
use Magento\Quote\Model\Quote\Address\Rate;
use Magento\Quote\Model\Quote\TotalsCollector;
use Magento\Quote\Model\ResourceModel\Quote\Address as QuoteAddressResource;
+use Magento\Customer\Model\Data\Address as CustomerAddress;
/**
* Shipping method read service
@@ -38,7 +41,7 @@ class ShippingMethodManagement implements
ShipmentEstimationInterface
{
/**
- * Quote repository.
+ * Quote repository model
*
* @var CartRepositoryInterface
*/
@@ -226,7 +229,7 @@ public function apply($cartId, $carrierCode, $methodCode)
}
$shippingMethod = $carrierCode . '_' . $methodCode;
$shippingAddress->setShippingMethod($shippingMethod);
- $shippingAssignments = $quote->getExtensionAttributes()->getShippingAssignments();
+ $shippingAssignments = (array)$quote->getExtensionAttributes()->getShippingAssignments();
if (!empty($shippingAssignments)) {
$shippingAssignment = $shippingAssignments[0];
$shipping = $shippingAssignment->getShipping();
@@ -268,6 +271,8 @@ public function estimateByExtendedAddress($cartId, AddressInterface $address)
/**
* @inheritDoc
+ * @throws InputException
+ * @throws NoSuchEntityException
*/
public function estimateByAddressId($cartId, $addressId)
{
@@ -278,7 +283,7 @@ public function estimateByAddressId($cartId, $addressId)
if ($quote->isVirtual() || 0 == $quote->getItemsCount()) {
return [];
}
- $address = $this->addressRepository->getById($addressId);
+ $address = $this->getAddress($addressId, $quote);
return $this->getShippingMethods($quote, $address);
}
@@ -295,6 +300,7 @@ public function estimateByAddressId($cartId, $addressId)
* @return ShippingMethodInterface[] An array of shipping methods.
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @deprecated 100.1.6
+ * @see Updated-deprication-doc-annotations
*/
protected function getEstimatedRates(
Quote $quote,
@@ -375,6 +381,7 @@ private function extractAddressData($address)
*
* @return DataObjectProcessor
* @deprecated 101.0.0
+ * @see Updated-deprication-doc-annotations
*/
private function getDataObjectProcessor()
{
@@ -384,4 +391,24 @@ private function getDataObjectProcessor()
}
return $this->dataProcessor;
}
+
+ /**
+ * Gets the address if exists for customer
+ *
+ * @param int $addressId
+ * @param Quote $quote
+ * @return CustomerAddress
+ * @throws InputException The shipping address is incorrect.
+ */
+ private function getAddress(int $addressId, Quote $quote): CustomerAddress
+ {
+ $addresses = $quote->getCustomer()->getAddresses();
+ foreach ($addresses as $address) {
+ if ($addressId === (int)$address->getId()) {
+ return $address;
+ }
+ }
+
+ throw new InputException(__('The shipping address is missing. Set the address and try again.'));
+ }
}
diff --git a/app/code/Magento/Quote/Model/ValidationRules/MinimumAmountValidationRule.php b/app/code/Magento/Quote/Model/ValidationRules/MinimumAmountValidationRule.php
index 34e953be43c74..0c50b97a5be39 100644
--- a/app/code/Magento/Quote/Model/ValidationRules/MinimumAmountValidationRule.php
+++ b/app/code/Magento/Quote/Model/ValidationRules/MinimumAmountValidationRule.php
@@ -48,7 +48,6 @@ public function __construct(
/**
* @inheritdoc
- * @throws \Zend_Currency_Exception
*/
public function validate(Quote $quote): array
{
diff --git a/app/code/Magento/Quote/Plugin/SendOrderNotification.php b/app/code/Magento/Quote/Plugin/SendOrderNotification.php
new file mode 100644
index 0000000000000..f7189ac0492d4
--- /dev/null
+++ b/app/code/Magento/Quote/Plugin/SendOrderNotification.php
@@ -0,0 +1,53 @@
+request = $request;
+ }
+
+ /**
+ * Adjusts order flag for confirmation email delivery
+ *
+ * @param SubmitObserver $subject
+ * @param Observer $observer
+ * @return Observer[]
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function beforeExecute(SubmitObserver $subject, Observer $observer): array
+ {
+ /** @var Order $order */
+ $order = $observer->getEvent()->getOrder();
+ $requestInfo = $this->request->getParam('order');
+ if (!empty($requestInfo)) {
+ $order->setCanSendNewEmailFlag((bool)($requestInfo['send_confirmation'] ?? 0));
+ }
+
+ return [$observer];
+ }
+}
diff --git a/app/code/Magento/Quote/Plugin/Webapi/Controller/Rest/ValidateQuoteData.php b/app/code/Magento/Quote/Plugin/Webapi/Controller/Rest/ValidateQuoteData.php
new file mode 100644
index 0000000000000..d27d49571e04c
--- /dev/null
+++ b/app/code/Magento/Quote/Plugin/Webapi/Controller/Rest/ValidateQuoteData.php
@@ -0,0 +1,56 @@
+validateInputData($inputData[self:: QUOTE_KEY]);
+ };
+ return [$inputData, $parameters];
+ }
+
+ /**
+ * Validates InputData
+ *
+ * @param array $inputData
+ * @return array
+ */
+ private function validateInputData(array $inputData): array
+ {
+ $result = [];
+
+ $data = array_filter($inputData, function ($k) use (&$result) {
+ $key = is_string($k) ? strtolower($k) : $k;
+ return !isset($result[$key]) && ($result[$key] = true);
+ }, ARRAY_FILTER_USE_KEY);
+
+ return array_map(function ($value) {
+ return is_array($value) ? $this->validateInputData($value) : $value;
+ }, $data);
+ }
+}
diff --git a/app/code/Magento/Quote/Test/Fixture/AddProductToCart.php b/app/code/Magento/Quote/Test/Fixture/AddProductToCart.php
index 27d764a224a6a..4cf0937952682 100644
--- a/app/code/Magento/Quote/Test/Fixture/AddProductToCart.php
+++ b/app/code/Magento/Quote/Test/Fixture/AddProductToCart.php
@@ -9,6 +9,8 @@
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Framework\DataObject;
+use Magento\Framework\DataObjectFactory;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\TestFramework\Fixture\DataFixtureInterface;
@@ -17,23 +19,31 @@ class AddProductToCart implements DataFixtureInterface
/**
* @var CartRepositoryInterface
*/
- private $cartRepository;
+ private CartRepositoryInterface $cartRepository;
/**
* @var ProductRepositoryInterface
*/
- private $productRepository;
+ private ProductRepositoryInterface $productRepository;
+
+ /**
+ * @var DataObjectFactory
+ */
+ private DataObjectFactory $dataObjectFactory;
/**
* @param CartRepositoryInterface $cartRepository
* @param ProductRepositoryInterface $productRepository
+ * @param DataObjectFactory $dataObjectFactory
*/
public function __construct(
CartRepositoryInterface $cartRepository,
- ProductRepositoryInterface $productRepository
+ ProductRepositoryInterface $productRepository,
+ DataObjectFactory $dataObjectFactory,
) {
$this->cartRepository = $cartRepository;
$this->productRepository = $productRepository;
+ $this->dataObjectFactory = $dataObjectFactory;
}
/**
@@ -44,6 +54,7 @@ public function __construct(
* 'cart_id' => (int) Cart ID. Required.
* 'product_id' => (int) Product ID. Required.
* 'qty' => (int) Quantity. Optional. Default: 1.
+ * 'buy_request'=> (array|DataObject) advanced product configuration
* ]
*
*/
@@ -51,8 +62,17 @@ public function apply(array $data = []): ?DataObject
{
$cart = $this->cartRepository->get($data['cart_id']);
$product = $this->productRepository->getById($data['product_id']);
- $catItem = $cart->addProduct($product, $data['qty'] ?? 1);
+ $qty = $data['qty'] ?? 1;
+ if (isset($data['buy_request'])) {
+ $buyRequest = $data['buy_request'] instanceof DataObject
+ ? $data['buy_request']
+ : $this->dataObjectFactory->create(['data' => $data['buy_request']]);
+ }
+ $catItem = $cart->addProduct($product, $buyRequest ?? $qty);
$this->cartRepository->save($cart);
+ if (is_string($catItem)) {
+ throw new LocalizedException(__($catItem));
+ }
return $catItem;
}
}
diff --git a/app/code/Magento/Quote/Test/Fixture/CustomerCart.php b/app/code/Magento/Quote/Test/Fixture/CustomerCart.php
new file mode 100644
index 0000000000000..b9597ef7723f2
--- /dev/null
+++ b/app/code/Magento/Quote/Test/Fixture/CustomerCart.php
@@ -0,0 +1,67 @@
+ null
+ ];
+
+ /**
+ * @var CartRepositoryInterface
+ */
+ private $cartRepository;
+
+ /**
+ * @var CartManagementInterface
+ */
+ private $cartManagement;
+
+ /**
+ * @param CartRepositoryInterface $cartRepository
+ * @param CartManagementInterface $cartManagement
+ */
+ public function __construct(
+ CartRepositoryInterface $cartRepository,
+ CartManagementInterface $cartManagement
+ ) {
+ $this->cartRepository = $cartRepository;
+ $this->cartManagement = $cartManagement;
+ }
+
+ /**
+ * {@inheritdoc}
+ * @param array $data Parameters
+ *
+ * $data = [
+ * 'customer_id' => (int) Customer ID. Required.
+ * ]
+ *
+ */
+ public function apply(array $data = []): ?DataObject
+ {
+ $data = array_merge(self::DEFAULT_DATA, $data);
+ $cartId = $this->cartManagement->createEmptyCartForCustomer($data['customer_id']);
+
+ return $this->cartRepository->get($cartId);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function revert(DataObject $data): void
+ {
+ $this->cartRepository->delete($data);
+ }
+}
diff --git a/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php b/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php
index bd2a2b7a82712..7d4064fd3c8ba 100644
--- a/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php
+++ b/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php
@@ -231,7 +231,7 @@ public function testGetCartTotal($isVirtual, $getAddressType): void
->method('setCouponCode')
->with(self::STUB_COUPON)
->willReturnSelf();
- $totalsMock->expects($this->once())
+ $totalsMock->expects($this->never())
->method('setGrandTotal')
->willReturnSelf();
$totalsMock->expects($this->once())
diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php
index b92ddc2b50863..e22df6a9b2cba 100644
--- a/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php
+++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php
@@ -252,30 +252,84 @@ public function testValidateMinimumAmountVirtual(): void
}
/**
- * @return void
+ * Provide data for test different cases
+ *
+ * @param void
+ * @return array
*/
- public function testValidateMinimumAmount(): void
+ public function getDataProvider(): array
{
- $storeId = 1;
- $scopeConfigValues = [
- ['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, $storeId, true],
- ['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, $storeId, 20],
- ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, $storeId, true],
- ['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, $storeId, true]
+ return [
+ 'Non-virtual Quote' => [
+ 'scopeConfigValues' => [
+ ['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, 1, true],
+ ['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, 1, 20],
+ ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, 1, true],
+ ['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, 1, true]
+ ],
+ 'address' => [
+ 'setAddressType' => 'billing'
+ ],
+ 'quote' => [
+ 'getStoreId' => 1,
+ 'getIsVirtual' => false
+ ],
+ 'result' => true
+ ],
+ 'With Shipping Discount' => [
+ 'scopeConfigValues' => [
+ ['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, 1, true],
+ ['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, 1, 2],
+ ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, 1, true],
+ ['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, 1, true]
+ ],
+ 'address' => [
+ 'setBaseSubtotal' => 25.00,
+ 'setBaseDiscountAmount' => -27.60,
+ 'setBaseShippingDiscountAmount' => 4.6,
+ 'setAddressType' => 'shipping'
+ ],
+ 'quote' => [
+ 'getStoreId' => 1,
+ 'getIsVirtual' => false
+ ],
+ 'result' => true
+ ]
];
+ }
- $this->quote->expects($this->once())
- ->method('getStoreId')
- ->willReturn($storeId);
- $this->quote->expects($this->once())
- ->method('getIsVirtual')
- ->willReturn(false);
+ /**
+ * Tests minimum order amount validation
+ *
+ * @param array $scopeConfigValues
+ * @param array $address
+ * @param array $quote
+ * @param bool $result
+ * @dataProvider getDataProvider
+ *
+ * @return void
+ */
+ public function testValidateMinimumAmount(
+ array $scopeConfigValues,
+ array $address,
+ array $quote,
+ bool $result
+ ): void {
+ foreach ($quote as $method => $value) {
+ $this->quote->expects($this->once())
+ ->method($method)
+ ->willReturn($value);
+ }
+
+ foreach ($address as $setter => $value) {
+ $this->address->$setter($value);
+ }
$this->scopeConfig->expects($this->once())
->method('isSetFlag')
->willReturnMap($scopeConfigValues);
- $this->assertTrue($this->address->validateMinimumAmount());
+ $this->assertEquals($result, $this->address->validateMinimumAmount());
}
/**
diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php
index 4937ac945ce28..ead52c61bcb18 100644
--- a/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php
+++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php
@@ -25,6 +25,7 @@
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\StateException;
use Magento\Framework\HTTP\PhpEnvironment\RemoteAddress;
+use Magento\Framework\Lock\LockManagerInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Model\CustomerManagement;
@@ -196,6 +197,11 @@ class QuoteManagementTest extends TestCase
*/
private $quoteIdMaskFactoryMock;
+ /**
+ * @var LockManagerInterface|MockObject
+ */
+ private $lockManagerMock;
+
/**
* @inheriDoc
*
@@ -240,7 +246,17 @@ protected function setUp(): void
);
$this->quoteMock = $this->getMockBuilder(Quote::class)
- ->addMethods(['setCustomerEmail', 'setCustomerGroupId', 'setCustomerId', 'setRemoteIp', 'setXForwardedFor'])
+ ->addMethods(
+ [
+ 'getCustomerEmail',
+ 'setCustomerEmail',
+ 'setCustomerGroupId',
+ 'getCustomerId',
+ 'setCustomerId',
+ 'setRemoteIp',
+ 'setXForwardedFor',
+ ]
+ )
->onlyMethods(
[
'assignCustomer',
@@ -282,6 +298,9 @@ protected function setUp(): void
$this->addressRepositoryMock = $this->getMockBuilder(AddressRepositoryInterface::class)
->getMockForAbstractClass();
+ $this->lockManagerMock = $this->getMockBuilder(LockManagerInterface::class)
+ ->getMockForAbstractClass();
+
$this->model = $objectManager->getObject(
QuoteManagement::class,
[
@@ -305,7 +324,8 @@ protected function setUp(): void
'customerSession' => $this->customerSessionMock,
'accountManagement' => $this->accountManagementMock,
'quoteFactory' => $this->quoteFactoryMock,
- 'addressRepository' => $this->addressRepositoryMock
+ 'addressRepository' => $this->addressRepositoryMock,
+ 'lockManager' => $this->lockManagerMock
]
);
@@ -751,7 +771,8 @@ public function testSubmit(): void
$customerId,
$quoteId,
$quoteItems,
- $shippingAddress
+ $shippingAddress,
+ false
);
$this->submitQuoteValidator->expects($this->once())
@@ -816,14 +837,16 @@ public function testSubmit(): void
['order' => $order, 'quote' => $quote]
]
);
+ $this->lockManagerMock->method('isLocked')->willReturn(false);
$this->quoteRepositoryMock->expects($this->once())->method('save')->with($quote);
$this->assertEquals($order, $this->model->submit($quote, $orderData));
}
/**
+ * @dataProvider guestPlaceOrderDataProvider
* @return void
*/
- public function testPlaceOrderIfCustomerIsGuest(): void
+ public function testPlaceOrderIfCustomerIsGuest(?string $settledEmail, int $countSetAddress): void
{
$cartId = 100;
$orderId = 332;
@@ -845,14 +868,21 @@ public function testPlaceOrderIfCustomerIsGuest(): void
$this->quoteMock->expects($this->once())
->method('getCustomer')
->willReturn($customerMock);
+ $this->quoteMock->expects($this->once())
+ ->method('getCustomerEmail')
+ ->willReturn($settledEmail);
$this->quoteMock->expects($this->once())->method('setCustomerId')->with(null)->willReturnSelf();
- $this->quoteMock->expects($this->once())->method('setCustomerEmail')->with($email)->willReturnSelf();
+ $this->quoteMock->expects($this->exactly($countSetAddress))
+ ->method('setCustomerEmail')
+ ->with($email)
+ ->willReturnSelf();
$addressMock = $this->createPartialMock(Address::class, ['getEmail']);
- $addressMock->expects($this->once())->method('getEmail')->willReturn($email);
+ $addressMock->expects($this->exactly($countSetAddress))->method('getEmail')->willReturn($email);
$this->quoteMock->expects($this->any())->method('getBillingAddress')->with()->willReturn($addressMock);
$this->quoteMock->expects($this->once())->method('setCustomerIsGuest')->with(true)->willReturnSelf();
+ $this->quoteMock->expects($this->once())->method('getCustomerId')->willReturn(null);
$this->quoteMock->expects($this->once())
->method('setCustomerGroupId')
->with(GroupInterface::NOT_LOGGED_IN_ID);
@@ -912,6 +942,17 @@ public function testPlaceOrderIfCustomerIsGuest(): void
$this->assertEquals($orderId, $service->placeOrder($cartId));
}
+ /**
+ * @return array
+ */
+ public function guestPlaceOrderDataProvider(): array
+ {
+ return [
+ [null, 1],
+ ['test@example.com', 0],
+ ];
+ }
+
/**
* @return void
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
@@ -1034,7 +1075,8 @@ protected function getQuote(
int $customerId,
int $id,
array $quoteItems,
- Address $shippingAddress = null
+ Address $shippingAddress = null,
+ bool $setIsActive
): MockObject {
$quote = $this->getMockBuilder(Quote::class)
->addMethods(['getCustomerEmail', 'getCustomerId'])
@@ -1056,9 +1098,11 @@ protected function getQuote(
)
->disableOriginalConstructor()
->getMock();
- $quote->expects($this->once())
- ->method('setIsActive')
- ->with(false);
+ if ($setIsActive) {
+ $quote->expects($this->once())
+ ->method('setIsActive')
+ ->with(false);
+ }
$quote->expects($this->any())
->method('getAllVisibleItems')
->willReturn($quoteItems);
@@ -1100,7 +1144,7 @@ protected function getQuote(
$quote->expects($this->any())
->method('getCustomer')
->willReturn($customer);
- $quote->expects($this->once())
+ $quote->expects($this->exactly(2))
->method('getId')
->willReturn($id);
$this->customerRepositoryMock->expects($this->any())->method('getById')->willReturn($customer);
@@ -1263,7 +1307,8 @@ public function testSubmitForCustomer(): void
$customerId,
$quoteId,
$quoteItems,
- $shippingAddress
+ $shippingAddress,
+ false
);
$this->submitQuoteValidator->method('validateQuote')
@@ -1357,4 +1402,110 @@ private function createPartialMockForAbstractClass(string $className, array $met
$methods
);
}
+
+ /**
+ * @return void
+ *
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
+ public function testSubmitWithLockException(): void
+ {
+ $orderData = [];
+ $isGuest = true;
+ $isVirtual = false;
+ $customerId = 1;
+ $quoteId = 1;
+ $quoteItem = $this->createMock(Item::class);
+ $billingAddress = $this->createMock(Address::class);
+ $shippingAddress = $this->getMockBuilder(Address::class)
+ ->addMethods(['getQuoteId'])
+ ->onlyMethods(['getShippingMethod', 'getId'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $payment = $this->createMock(Payment::class);
+ $baseOrder = $this->getMockForAbstractClass(OrderInterface::class);
+ $convertedBilling = $this->createPartialMockForAbstractClass(OrderAddressInterface::class, ['setData']);
+ $convertedShipping = $this->createPartialMockForAbstractClass(OrderAddressInterface::class, ['setData']);
+ $convertedPayment = $this->getMockForAbstractClass(OrderPaymentInterface::class);
+ $convertedQuoteItem = $this->getMockForAbstractClass(OrderItemInterface::class);
+ $addresses = [$convertedShipping, $convertedBilling];
+ $quoteItems = [$quoteItem];
+ $convertedItems = [$convertedQuoteItem];
+ $quote = $this->getQuote(
+ $isGuest,
+ $isVirtual,
+ $billingAddress,
+ $payment,
+ $customerId,
+ $quoteId,
+ $quoteItems,
+ $shippingAddress,
+ false
+ );
+
+ $this->submitQuoteValidator->expects($this->once())
+ ->method('validateQuote')
+ ->with($quote);
+ $this->quoteAddressToOrder->expects($this->once())
+ ->method('convert')
+ ->with($shippingAddress, $orderData)
+ ->willReturn($baseOrder);
+ $this->quoteAddressToOrderAddress
+ ->method('convert')
+ ->withConsecutive(
+ [
+ $shippingAddress,
+ [
+ 'address_type' => 'shipping',
+ 'email' => 'customer@example.com'
+ ]
+ ],
+ [
+ $billingAddress,
+ [
+ 'address_type' => 'billing',
+ 'email' => 'customer@example.com'
+ ]
+ ]
+ )->willReturnOnConsecutiveCalls($convertedShipping, $convertedBilling);
+ $billingAddress->expects($this->once())->method('getId')->willReturn(4);
+ $convertedBilling->expects($this->once())->method('setData')->with('quote_address_id', 4);
+
+ $this->quoteItemToOrderItem->expects($this->once())->method('convert')
+ ->with($quoteItem, ['parent_item' => null])
+ ->willReturn($convertedQuoteItem);
+ $this->quotePaymentToOrderPayment->expects($this->once())->method('convert')->with($payment)
+ ->willReturn($convertedPayment);
+ $shippingAddress->expects($this->once())->method('getShippingMethod')->willReturn('free');
+ $shippingAddress->expects($this->once())->method('getId')->willReturn(5);
+ $convertedShipping->expects($this->once())->method('setData')->with('quote_address_id', 5);
+ $order = $this->prepareOrderFactory(
+ $baseOrder,
+ $convertedBilling,
+ $addresses,
+ $convertedPayment,
+ $convertedItems,
+ $quoteId,
+ $convertedShipping
+ );
+
+ $this->eventManager
+ ->method('dispatch')
+ ->withConsecutive(
+ [
+ 'sales_model_service_quote_submit_before',
+ ['order' => $order, 'quote' => $quote]
+ ],
+ [
+ 'sales_model_service_quote_submit_success',
+ ['order' => $order, 'quote' => $quote]
+ ]
+ );
+ $this->lockManagerMock->method('isLocked')->willReturn(true);
+
+ $this->expectExceptionMessage(
+ 'A server error stopped your order from being placed. Please try to place your order again.'
+ );
+ $this->assertEquals($order, $this->model->submit($quote, $orderData));
+ }
}
diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php
index fd0a4982541e3..b7571da30b9be 100644
--- a/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php
+++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php
@@ -1025,7 +1025,7 @@ public function testAddProductItemPreparation(): void
$productMock = $this->getMockBuilder(Product::class)
->addMethods(['getParentProductId', 'setStickWithinParent'])
- ->onlyMethods(['__wakeup'])
+ ->onlyMethods(['__wakeup', 'getId'])
->disableOriginalConstructor()
->getMock();
@@ -1034,12 +1034,24 @@ public function testAddProductItemPreparation(): void
$itemMock->expects($this->any())
->method('representProduct')
->willReturn(true);
+ $itemMock->expects($this->any())
+ ->method('getProduct')
+ ->willReturn($this->productMock);
$iterator = new \ArrayIterator([$itemMock]);
$collectionMock->expects($this->any())
->method('getIterator')
->willReturn($iterator);
+ $productMock->expects($this->any())
+ ->method('getId')
+ ->willReturn(123);
+
+ $collectionMock->expects($this->any())
+ ->method('getItemsByColumnValue')
+ ->with('product_id', 123)
+ ->willReturn([$itemMock]);
+
$this->quoteItemCollectionFactoryMock->expects($this->once())
->method('create')
->willReturn($collectionMock);
@@ -1084,7 +1096,7 @@ public function testAddProductItemNew(): void
$productMock = $this->getMockBuilder(Product::class)
->addMethods(['getParentProductId', 'setStickWithinParent'])
- ->onlyMethods(['__wakeup'])
+ ->onlyMethods(['__wakeup', 'getId'])
->disableOriginalConstructor()
->getMock();
@@ -1099,6 +1111,15 @@ public function testAddProductItemNew(): void
->method('getIterator')
->willReturn($iterator);
+ $productMock->expects($this->any())
+ ->method('getId')
+ ->willReturn(123);
+
+ $collectionMock->expects($this->any())
+ ->method('getItemsByColumnValue')
+ ->with('product_id', 123)
+ ->willReturn([$itemMock]);
+
$this->quoteItemCollectionFactoryMock->expects($this->once())
->method('create')
->willReturn($collectionMock);
@@ -1402,20 +1423,26 @@ public function testGetItemsCollection(): void
public function testGetAllItems(): void
{
$itemOneMock = $this->getMockBuilder(\Magento\Quote\Model\ResourceModel\Quote\Item::class)
- ->addMethods(['isDeleted'])
+ ->addMethods(['isDeleted', 'getProduct'])
->disableOriginalConstructor()
->getMock();
$itemOneMock->expects($this->once())
->method('isDeleted')
->willReturn(false);
+ $itemOneMock->expects($this->once())
+ ->method('getProduct')
+ ->willReturn($this->productMock);
$itemTwoMock = $this->getMockBuilder(\Magento\Quote\Model\ResourceModel\Quote\Item::class)
- ->addMethods(['isDeleted'])
+ ->addMethods(['isDeleted', 'getProduct'])
->disableOriginalConstructor()
->getMock();
$itemTwoMock->expects($this->once())
->method('isDeleted')
->willReturn(true);
+ $itemTwoMock->expects($this->once())
+ ->method('getProduct')
+ ->willReturn($this->productMock);
$items = [$itemOneMock, $itemTwoMock];
$itemResult = [$itemOneMock];
diff --git a/app/code/Magento/Quote/Test/Unit/Model/ShippingAddressManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/ShippingAddressManagementTest.php
new file mode 100644
index 0000000000000..d907017df1dda
--- /dev/null
+++ b/app/code/Magento/Quote/Test/Unit/Model/ShippingAddressManagementTest.php
@@ -0,0 +1,167 @@
+quoteRepositoryMock = $this->createMock(CartRepositoryInterface::class);
+ $this->addressValidatorMock = $this->createMock(QuoteAddressValidator::class);
+ $this->loggerMock = $this->createMock(LoggerInterface::class);
+ $this->addressRepositoryMock = $this->createMock(AddressRepositoryInterface::class);
+ $this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class);
+ $this->totalsCollectorMock = $this->createMock(TotalsCollector::class);
+ $this->quoteMock = $this->createMock(Quote::class);
+ $this->model = new ShippingAddressManagement(
+ $this->quoteRepositoryMock,
+ $this->addressValidatorMock,
+ $this->loggerMock,
+ $this->addressRepositoryMock,
+ $this->scopeConfigMock,
+ $this->totalsCollectorMock
+ );
+ }
+
+ /**
+ * @throws InputException
+ * @throws NoSuchEntityException
+ * @dataProvider assignDataProvider
+ */
+ public function testAssign(bool $saveInAddressBook, bool $showCompany): void
+ {
+ $cartId = $customerId = 123;
+ $addressMock = $this->getMockBuilder(AddressInterface::class)
+ ->addMethods(['setCollectShippingRates', 'save', 'importCustomerAddressData'])
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $this->quoteMock
+ ->expects($this->once())
+ ->method('isVirtual')
+ ->willReturn(false);
+ $this->quoteRepositoryMock
+ ->expects($this->once())
+ ->method('getActive')
+ ->with($cartId)
+ ->willReturn($this->quoteMock);
+ $addressMock
+ ->expects($this->once())
+ ->method('getSaveInAddressBook')
+ ->willReturn($saveInAddressBook);
+ $addressMock
+ ->expects($this->once())
+ ->method('getSameAsBilling')
+ ->willReturn(true);
+ $addressMock
+ ->expects($this->once())
+ ->method('getCustomerAddressId')
+ ->willReturn($customerId);
+ $addressMock
+ ->expects($saveInAddressBook && !$showCompany ? $this->once() : $this->never())
+ ->method('setCompany')
+ ->with(null);
+ $addressMock
+ ->expects($this->once())
+ ->method('importCustomerAddressData')
+ ->willReturn($addressMock);
+ $addressMock
+ ->expects($this->once())
+ ->method('setSameAsBilling')
+ ->with(true);
+ $addressMock
+ ->expects($this->once())
+ ->method('setSaveInAddressBook')
+ ->with($saveInAddressBook);
+ $addressMock->method('setCollectShippingRates');
+ $addressMock->method('save');
+ $this->scopeConfigMock
+ ->expects($saveInAddressBook ? $this->once() : $this->never())
+ ->method('getValue')
+ ->willReturn($showCompany);
+ $this->addressValidatorMock
+ ->expects($this->once())
+ ->method('validateForCart');
+ $this->quoteMock
+ ->expects($this->once())
+ ->method('setShippingAddress')
+ ->with($addressMock);
+ $this->quoteMock
+ ->method('getShippingAddress')
+ ->willReturn($addressMock);
+ $this->model->assign($cartId, $addressMock);
+ }
+
+ /**
+ * @return array
+ */
+ public function assignDataProvider(): array
+ {
+ return [
+ [true, true],
+ [true, false],
+ [false, true],
+ [false, false],
+ ];
+ }
+}
diff --git a/app/code/Magento/Quote/Test/Unit/Model/ShippingMethodManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/ShippingMethodManagementTest.php
index 784bf04ddf7d3..c1c7de3369a24 100644
--- a/app/code/Magento/Quote/Test/Unit/Model/ShippingMethodManagementTest.php
+++ b/app/code/Magento/Quote/Test/Unit/Model/ShippingMethodManagementTest.php
@@ -24,12 +24,13 @@
use Magento\Quote\Model\QuoteRepository;
use Magento\Quote\Model\ResourceModel\Quote\Address as QuoteAddressResource;
use Magento\Quote\Model\ShippingMethodManagement;
-use Magento\Store\Model\Store;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Magento\Quote\Api\Data\CartExtensionInterface;
use Magento\Sales\Model\Order\ShippingAssignmentBuilder;
use Magento\Quote\Api\Data\ShippingInterface;
+use Magento\Customer\Api\Data\CustomerInterface;
+use Magento\Customer\Model\Session;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -41,16 +42,6 @@ class ShippingMethodManagementTest extends TestCase
*/
protected $model;
- /**
- * @var MockObject
- */
- protected $shippingMethodMock;
-
- /**
- * @var MockObject
- */
- protected $methodDataFactoryMock;
-
/**
* @var ShippingMethodConverter|MockObject
*/
@@ -96,11 +87,6 @@ class ShippingMethodManagementTest extends TestCase
*/
private $totalsCollector;
- /**
- * @var Store|MockObject
- */
- private $storeMock;
-
/**
* @var QuoteAddressResource|MockObject
*/
@@ -121,13 +107,20 @@ class ShippingMethodManagementTest extends TestCase
*/
private $shippingAssignmentBuilder;
+ /**
+ * @var Session
+ */
+ private $customerSession;
+
protected function setUp(): void
{
$this->objectManager = new ObjectManager($this);
$this->quoteRepository = $this->getMockForAbstractClass(CartRepositoryInterface::class);
$this->addressRepository = $this->getMockForAbstractClass(AddressRepositoryInterface::class);
+ $this->customerSession = $this->createMock(Session::class);
- $this->methodDataFactoryMock = $this->getMockBuilder(ShippingMethodInterfaceFactory::class)
+ /** @var MockObject $methodDataFactoryMock */
+ $methodDataFactoryMock = $this->getMockBuilder(ShippingMethodInterfaceFactory::class)
->disableOriginalConstructor()
->onlyMethods(['create'])
->getMock();
@@ -141,7 +134,6 @@ protected function setUp(): void
$this->dataProcessor = $this->createMock($className);
$this->quoteAddressResource = $this->createMock(QuoteAddressResource::class);
- $this->storeMock = $this->createMock(Store::class);
$this->quote = $this->getMockBuilder(Quote::class)
->disableOriginalConstructor()
->setMethods([
@@ -153,7 +145,8 @@ protected function setUp(): void
'collectTotals',
'save',
'__wakeup',
- 'getExtensionAttributes'
+ 'getExtensionAttributes',
+ 'getCustomer'
])
->getMock();
@@ -190,25 +183,17 @@ protected function setUp(): void
ShippingMethodManagement::class,
[
'quoteRepository' => $this->quoteRepository,
- 'methodDataFactory' => $this->methodDataFactoryMock,
+ 'methodDataFactory' => $methodDataFactoryMock,
'converter' => $this->converter,
'totalsCollector' => $this->totalsCollector,
'addressRepository' => $this->addressRepository,
'quoteAddressResource' => $this->quoteAddressResource,
+ 'customerSession' => $this->customerSession,
]
);
- $this->objectManager->setBackwardCompatibleProperty(
- $this->model,
- 'addressFactory',
- $this->addressFactory
- );
-
- $this->objectManager->setBackwardCompatibleProperty(
- $this->model,
- 'dataProcessor',
- $this->dataProcessor
- );
+ $this->objectManager->setBackwardCompatibleProperty($this->model, 'addressFactory', $this->addressFactory);
+ $this->objectManager->setBackwardCompatibleProperty($this->model, 'dataProcessor', $this->dataProcessor);
$this->extensionAttributesMock = $this->getMockBuilder(CartExtensionInterface::class)
->setMethods(['getShippingAssignments'])
@@ -266,11 +251,12 @@ public function testGetMethod()
->with('one_two')
->willReturn($shippingRateMock);
- $this->shippingMethodMock = $this->getMockForAbstractClass(ShippingMethodInterface::class);
+ /** @var MockObject $shippingMethodMock */
+ $shippingMethodMock = $this->getMockForAbstractClass(ShippingMethodInterface::class);
$this->converter->expects($this->once())
->method('modelToDataObject')
->with($shippingRateMock, $currencyCode)
- ->willReturn($this->shippingMethodMock);
+ ->willReturn($shippingMethodMock);
$this->model->get($cartId);
}
@@ -578,32 +564,32 @@ public function testEstimateByExtendedAddress()
->method('create')
->willReturn($address);
- $this->quoteRepository->expects(static::once())
+ $this->quoteRepository->expects(self::once())
->method('getActive')
->with($cartId)
->willReturn($this->quote);
- $this->quote->expects(static::once())
+ $this->quote->expects(self::once())
->method('isVirtual')
->willReturn(false);
- $this->quote->expects(static::once())
+ $this->quote->expects(self::once())
->method('getItemsCount')
->willReturn(1);
- $this->quote->expects(static::once())
+ $this->quote->expects(self::once())
->method('getShippingAddress')
->willReturn($this->shippingAddress);
- $this->dataProcessor->expects(static::any())
+ $this->dataProcessor->expects(self::any())
->method('buildOutputDataArray')
->willReturn($addressData);
- $this->shippingAddress->expects(static::once())
+ $this->shippingAddress->expects(self::once())
->method('setCollectShippingRates')
->with(true)
->willReturnSelf();
- $this->totalsCollector->expects(static::once())
+ $this->totalsCollector->expects(self::once())
->method('collectAddressTotals')
->with($this->quote, $this->shippingAddress)
->willReturnSelf();
@@ -615,30 +601,34 @@ public function testEstimateByExtendedAddress()
$methodObject = $this->getMockForAbstractClass(ShippingMethodInterface::class);
$expectedRates = [$methodObject];
- $this->shippingAddress->expects(static::once())
+ $this->shippingAddress->expects(self::once())
->method('getGroupedAllShippingRates')
->willReturn([[$rate]]);
- $this->quote->expects(static::once())
+ $this->quote->expects(self::once())
->method('getQuoteCurrencyCode')
->willReturn($currencyCode);
- $this->converter->expects(static::once())
+ $this->converter->expects(self::once())
->method('modelToDataObject')
->with($rate, $currencyCode)
->willReturn($methodObject);
$carriersRates = $this->model->estimateByExtendedAddress($cartId, $address);
- static::assertEquals($expectedRates, $carriersRates);
+ self::assertEquals($expectedRates, $carriersRates);
}
/**
+ * @dataProvider getAddressDataProvider
+ *
* @covers \Magento\Quote\Model\ShippingMethodManagement::estimateByAddressId
+ * @param int $cartId
+ * @param int $addressId
+ * @param int $randomAddressId
+ * @param bool $throwsException
*/
- public function testEstimateByAddressId()
+ public function testEstimateByAddressId($cartId, $addressId, $randomAddressId, $throwsException)
{
- $cartId = 1;
-
$addressData = [
'region' => 'California',
'region_id' => 23,
@@ -646,73 +636,109 @@ public function testEstimateByAddressId()
'postcode' => 90200,
];
$currencyCode = 'UAH';
+ $customerId = 1;
- /**
- * @var \Magento\Customer\Api\Data\AddressInterface|MockObject $address
- */
- $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class)
+ $rate = $this->getMockBuilder(Rate::class)
->disableOriginalConstructor()
+ ->setMethods([])
->getMock();
+ $methodObject = $this->getMockForAbstractClass(ShippingMethodInterface::class);
- $this->addressRepository->expects($this->any())
- ->method('getById')
- ->willReturn($address);
-
- $this->addressFactory->expects($this->any())
- ->method('create')
- ->willReturn($address);
-
- $this->quoteRepository->expects(static::once())
+ $this->quoteRepository->expects(self::once())
->method('getActive')
->with($cartId)
->willReturn($this->quote);
- $this->quote->expects(static::once())
+ $this->quote->expects(self::once())
->method('isVirtual')
->willReturn(false);
- $this->quote->expects(static::once())
+
+ $this->quote->expects(self::once())
->method('getItemsCount')
->willReturn(1);
- $this->quote->expects(static::once())
- ->method('getShippingAddress')
- ->willReturn($this->shippingAddress);
-
- $this->dataProcessor->expects(static::any())
- ->method('buildOutputDataArray')
- ->willReturn($addressData);
-
- $this->shippingAddress->expects(static::once())
- ->method('setCollectShippingRates')
- ->with(true)
- ->willReturnSelf();
+ $this->setCustomerSession($addressId, $customerId);
+ if ($throwsException) {
+ $this->expectException('Magento\Framework\Exception\InputException');
+ $this->expectExceptionMessage('The shipping address is missing. Set the address and try again.');
+ $this->model->estimateByAddressId($cartId, $randomAddressId);
+ } else {
+ $this->quote->expects(self::once())
+ ->method('getShippingAddress')
+ ->willReturn($this->shippingAddress);
+
+ $this->dataProcessor->expects(self::any())
+ ->method('buildOutputDataArray')
+ ->willReturn($addressData);
+
+ $this->shippingAddress->expects(self::once())
+ ->method('setCollectShippingRates')
+ ->with(true)
+ ->willReturnSelf();
+
+ $this->totalsCollector->expects(self::once())
+ ->method('collectAddressTotals')
+ ->with($this->quote, $this->shippingAddress)
+ ->willReturnSelf();
+
+ $expectedRates = [$methodObject];
+ $this->shippingAddress->expects(self::once())
+ ->method('getGroupedAllShippingRates')
+ ->willReturn([[$rate]]);
+
+ $this->quote->expects(self::once())
+ ->method('getQuoteCurrencyCode')
+ ->willReturn($currencyCode);
+
+ $this->converter->expects(self::once())
+ ->method('modelToDataObject')
+ ->with($rate, $currencyCode)
+ ->willReturn($methodObject);
+
+ $carriersRates = $this->model->estimateByAddressId($cartId, $addressId);
+ self::assertEquals($expectedRates, $carriersRates);
+ }
+ }
- $this->totalsCollector->expects(static::once())
- ->method('collectAddressTotals')
- ->with($this->quote, $this->shippingAddress)
- ->willReturnSelf();
+ /**
+ * @return array
+ */
+ public function getAddressDataProvider()
+ {
+ return [
+ [1, 1, 5, true],
+ [1, 1, 1, false],
+ ];
+ }
- $rate = $this->getMockBuilder(Rate::class)
+ private function setCustomerSession($addressId, $customerId)
+ {
+ /**
+ * @var \Magento\Customer\Model\Data\Address|MockObject $address
+ */
+ $address = $this->getMockBuilder(\Magento\Customer\Model\Data\Address::class)
+ ->setMethods(['getId'])
->disableOriginalConstructor()
- ->setMethods([])
->getMock();
- $methodObject = $this->getMockForAbstractClass(ShippingMethodInterface::class);
- $expectedRates = [$methodObject];
+ $address->expects($this->atLeastOnce())
+ ->method('getId')
+ ->willReturn($addressId);
- $this->shippingAddress->expects(static::once())
- ->method('getGroupedAllShippingRates')
- ->willReturn([[$rate]]);
+ $this->addressRepository->expects($this->any())
+ ->method('getById')
+ ->willReturn($address);
- $this->quote->expects(static::once())
- ->method('getQuoteCurrencyCode')
- ->willReturn($currencyCode);
+ $this->addressFactory->expects($this->any())
+ ->method('create')
+ ->willReturn($address);
- $this->converter->expects(static::once())
- ->method('modelToDataObject')
- ->with($rate, $currencyCode)
- ->willReturn($methodObject);
+ $customerAddresses = [$address];
+ $customerMock = $this->getMockForAbstractClass(CustomerInterface::class);
+ $customerMock->method('getAddresses')->willReturn($customerAddresses);
- $carriersRates = $this->model->estimateByAddressId($cartId, $address);
- static::assertEquals($expectedRates, $carriersRates);
+ $this->quote->method('getCustomer')->willReturn($customerMock);
+ $this->customerSession->method('getCustomerId')->willReturn($customerId);
+ $this->customerSession->expects(self::any())->method('isLoggedIn')->willReturn(true);
+ $this->customerSession->expects(self::any())->method('getCustomerData')->willReturn($customerMock);
}
}
diff --git a/app/code/Magento/Quote/Test/Unit/Plugin/SendOrderNotificationTest.php b/app/code/Magento/Quote/Test/Unit/Plugin/SendOrderNotificationTest.php
new file mode 100644
index 0000000000000..5ee25672b1521
--- /dev/null
+++ b/app/code/Magento/Quote/Test/Unit/Plugin/SendOrderNotificationTest.php
@@ -0,0 +1,116 @@
+request = $this->createMock(RequestInterface::class);
+ $this->subject = $this->createMock(SubmitObserver::class);
+ $this->observer = $this->createMock(Observer::class);
+ $this->notification = new SendOrderNotification($this->request);
+ }
+
+ /**
+ * @return void
+ */
+ public function testBeforeExecuteWithSendConfirmation()
+ {
+ $this->request->expects($this->once())->method('getParam')->with('order')
+ ->willReturn(['send_confirmation' => 1]);
+
+ $order = $this->getMockBuilder(Order::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods([])
+ ->getMock();
+
+ $event = $this->getMockBuilder(Event::class)
+ ->disableOriginalConstructor()
+ ->addMethods(['getOrder'])
+ ->getMock();
+ $event->expects($this->exactly(2))->method('getOrder')->willReturn($order);
+
+ $this->observer->expects($this->exactly(2))->method('getEvent')->willReturn($event);
+
+ $result = $this->notification->beforeExecute($this->subject, $this->observer);
+ $this->assertIsArray($result);
+ $this->assertContains($this->observer, $result);
+
+ $observerCheck = $result[0];
+ /** @var Order $orderCheck */
+ $orderCheck = $observerCheck->getEvent()->getOrder();
+ $this->assertTrue($orderCheck->getCanSendNewEmailFlag());
+ }
+
+ /**
+ * @return void
+ */
+ public function testBeforeExecuteWithoutSendConfirmation()
+ {
+ $this->request->expects($this->once())->method('getParam')->with('order')
+ ->willReturn(['order' => []]);
+
+ $order = $this->getMockBuilder(Order::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods([])
+ ->getMock();
+
+ $event = $this->getMockBuilder(Event::class)
+ ->disableOriginalConstructor()
+ ->addMethods(['getOrder'])
+ ->getMock();
+ $event->expects($this->exactly(2))->method('getOrder')->willReturn($order);
+
+ $this->observer->expects($this->exactly(2))->method('getEvent')->willReturn($event);
+
+ $result = $this->notification->beforeExecute($this->subject, $this->observer);
+ $this->assertIsArray($result);
+ $this->assertContains($this->observer, $result);
+
+ $observerCheck = $result[0];
+ /** @var Order $orderCheck */
+ $orderCheck = $observerCheck->getEvent()->getOrder();
+ $this->assertFalse($orderCheck->getCanSendNewEmailFlag());
+ }
+}
diff --git a/app/code/Magento/Quote/Test/Unit/Plugin/Webapi/Controller/Rest/ValidateQuoteDataTest.php b/app/code/Magento/Quote/Test/Unit/Plugin/Webapi/Controller/Rest/ValidateQuoteDataTest.php
new file mode 100644
index 0000000000000..8f01cbcddd418
--- /dev/null
+++ b/app/code/Magento/Quote/Test/Unit/Plugin/Webapi/Controller/Rest/ValidateQuoteDataTest.php
@@ -0,0 +1,114 @@
+validateQuoteDataObject = ObjectManager::getInstance()->get(ValidateQuoteData::class);
+ $this->reflectionObject = new ReflectionClass(get_class($this->validateQuoteDataObject));
+ }
+ /**
+ * Test if the quote array is valid
+ *
+ * @param array $array
+ * @param array $result
+ * @dataProvider dataProviderInputData
+ * @throws Exception
+ */
+ public function testValidateInputData(array $array, array $result)
+ {
+ $this->assertEquals(
+ $result,
+ $this->invokeValidateInputData('validateInputData', [$array])
+ );
+ }
+
+ /**
+ * @param string $methodName
+ * @param array $arguments
+ * @return mixed
+ * @throws Exception
+ */
+ private function invokeValidateInputData(string $methodName, array $arguments = [])
+ {
+ $validateInputDataMethod = $this->reflectionObject->getMethod($methodName);
+ $validateInputDataMethod->setAccessible(true);
+ return $validateInputDataMethod->invokeArgs($this->validateQuoteDataObject, $arguments);
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProviderInputData(): array
+ {
+ return [
+ [
+ ['person' =>
+ [
+ 'id' => -1,
+ 'Id' => 1,
+ 'name' =>
+ [
+ 'firstName' => 'John',
+ 'LastName' => 'S'
+ ],
+ 'isHavingVehicle' => 1,
+ 'address' =>
+ [
+ 'street' => '4th Street',
+ 'Street' => '2nd Street',
+ 'city' => 'Atlanta'
+ ],
+ ]
+ ],
+ ['person' =>
+ [
+ 'id' => -1,
+ 'name' =>
+ [
+ 'firstName' => 'John',
+ 'LastName' => 'S'
+ ],
+ 'isHavingVehicle' => 1,
+ 'address' =>
+ [
+ 'street' => '4th Street',
+ 'city' => 'Atlanta'
+ ],
+ ]
+ ],
+ ]
+ ];
+ }
+}
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/Quote/etc/adminhtml/di.xml b/app/code/Magento/Quote/etc/adminhtml/di.xml
index 08e87c7db9ec7..58368a445393f 100644
--- a/app/code/Magento/Quote/etc/adminhtml/di.xml
+++ b/app/code/Magento/Quote/etc/adminhtml/di.xml
@@ -17,4 +17,7 @@
-
\ No newline at end of file
+
+
+
+
diff --git a/app/code/Magento/Quote/etc/di.xml b/app/code/Magento/Quote/etc/di.xml
index 01821c63801a7..5ffc82d05e20f 100644
--- a/app/code/Magento/Quote/etc/di.xml
+++ b/app/code/Magento/Quote/etc/di.xml
@@ -46,6 +46,7 @@
+
diff --git a/app/code/Magento/Quote/etc/webapi_rest/di.xml b/app/code/Magento/Quote/etc/webapi_rest/di.xml
index 6ed9909f04eb9..d5893f7d16d8b 100644
--- a/app/code/Magento/Quote/etc/webapi_rest/di.xml
+++ b/app/code/Magento/Quote/etc/webapi_rest/di.xml
@@ -19,4 +19,7 @@
+
+
+
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/PlaceOrderMutex.php b/app/code/Magento/QuoteGraphQl/Model/Cart/PlaceOrderMutex.php
new file mode 100644
index 0000000000000..2b13086fc7a25
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/PlaceOrderMutex.php
@@ -0,0 +1,72 @@
+lockManager = $lockManager;
+ $this->lockWaitTimeout = $lockWaitTimeout;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function execute(string $maskedId, callable $callable, array $args = [])
+ {
+ if (empty($maskedId)) {
+ throw new \InvalidArgumentException('Quote masked id must be provided');
+ }
+
+ if ($this->lockManager->isLocked(self::LOCK_PREFIX . $maskedId)) {
+ throw new GraphQlAlreadyExistsException(
+ __('The order has already been placed and is currently processing.')
+ );
+ }
+
+ if ($this->lockManager->lock(self::LOCK_PREFIX . $maskedId, $this->lockWaitTimeout)) {
+ try {
+ return $callable(...$args);
+ } finally {
+ $this->lockManager->unlock(self::LOCK_PREFIX . $maskedId);
+ }
+ } else {
+ throw new LocalizedException(
+ __('Could not acquire lock for the quote id: %1', $maskedId)
+ );
+ }
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/PlaceOrderMutexInterface.php b/app/code/Magento/QuoteGraphQl/Model/Cart/PlaceOrderMutexInterface.php
new file mode 100644
index 0000000000000..6e4c85d1a2f6f
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/PlaceOrderMutexInterface.php
@@ -0,0 +1,27 @@
+normalizeRegion($addressInput);
- }
-
$this->validateRegion($addressInput);
$maxAllowedLineCount = $this->addressHelper->getStreetLines();
@@ -148,26 +144,6 @@ private function validateRegion(array $addressInput): void
}
}
- /**
- * Normalize region code to region id while requesting to setShippingAddress
- *
- * @param array $addressInput
- */
- private function normalizeRegion(array &$addressInput)
- {
- $shippingAddressRegion = $addressInput['region'];
- if (is_numeric($shippingAddressRegion)) {
- $regionCollection = $this->regionCollectionFactory
- ->create()
- ->addCountryFilter($addressInput['country_code']);
- $allRegions = $regionCollection->toOptionArray();
- $regionId = (int) $addressInput['region'];
- if (array_key_exists($regionId, $allRegions)) {
- $addressInput['region'] = $allRegions[$regionId]['value'];
- }
- }
- }
-
/**
* Validate the address region when region is required for the country
*
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/CartItem/DataProvider/CustomizableOptionValue/Dropdown.php b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Dropdown.php
index d62c1951eda68..8a92bb87743f8 100644
--- a/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Dropdown.php
+++ b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Dropdown.php
@@ -59,28 +59,31 @@ public function getData(
->setOption($option)
->setConfigurationItemOption($selectedOption);
+ $selectedOptionValues = [];
$selectedValue = $selectedOption->getValue();
$optionValue = $option->getValueById($selectedValue);
- $optionPriceType = (string)$optionValue->getPriceType();
- $priceValueUnits = $this->priceUnitLabel->getData($optionPriceType);
+ if ($optionValue) {
+ $optionPriceType = (string)$optionValue->getPriceType();
+ $priceValueUnits = $this->priceUnitLabel->getData($optionPriceType);
- $optionDetails = [
- self::OPTION_TYPE,
- $option->getOptionId(),
- $optionValue->getOptionTypeId()
- ];
+ $optionDetails = [
+ self::OPTION_TYPE,
+ $option->getOptionId(),
+ $optionValue->getOptionTypeId()
+ ];
- $selectedOptionValueData = [
- 'id' => $selectedOption->getId(),
- 'customizable_option_value_uid' => $this->uidEncoder->encode((string) implode('/', $optionDetails)),
- 'label' => $optionTypeRenderer->getFormattedOptionValue($selectedValue),
- 'value' => $selectedValue,
- 'price' => [
- 'type' => strtoupper($optionPriceType),
- 'units' => $priceValueUnits,
- 'value' => $optionValue->getPrice(),
- ]
- ];
- return [$selectedOptionValueData];
+ $selectedOptionValues[] = [
+ 'id' => $selectedOption->getId(),
+ 'customizable_option_value_uid' => $this->uidEncoder->encode((string)implode('/', $optionDetails)),
+ 'label' => $optionTypeRenderer->getFormattedOptionValue($selectedValue),
+ 'value' => $selectedValue,
+ 'price' => [
+ 'type' => strtoupper($optionPriceType),
+ 'units' => $priceValueUnits,
+ 'value' => $optionValue->getPrice(),
+ ]
+ ];
+ }
+ return $selectedOptionValues;
}
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Multiple.php b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Multiple.php
index 342ea1e4a498e..e3fcd6536928d 100644
--- a/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Multiple.php
+++ b/app/code/Magento/QuoteGraphQl/Model/CartItem/DataProvider/CustomizableOptionValue/Multiple.php
@@ -63,25 +63,27 @@ public function getData(
foreach ($optionIds as $optionId) {
$optionValue = $option->getValueById($optionId);
- $priceValueUnits = $this->priceUnitLabel->getData($optionValue->getPriceType());
+ if ($optionValue) {
+ $priceValueUnits = $this->priceUnitLabel->getData($optionValue->getPriceType());
- $optionDetails = [
- self::OPTION_TYPE,
- $option->getOptionId(),
- $optionValue->getOptionTypeId()
- ];
+ $optionDetails = [
+ self::OPTION_TYPE,
+ $option->getOptionId(),
+ $optionValue->getOptionTypeId()
+ ];
- $selectedOptionValueData[] = [
- 'id' => $selectedOption->getId(),
- 'customizable_option_value_uid' => $this->uidEncoder->encode((string)implode('/', $optionDetails)),
- 'label' => $optionValue->getTitle(),
- 'value' => $optionId,
- 'price' => [
- 'type' => strtoupper($optionValue->getPriceType()),
- 'units' => $priceValueUnits,
- 'value' => $optionValue->getPrice(),
- ],
- ];
+ $selectedOptionValueData[] = [
+ 'id' => $selectedOption->getId(),
+ 'customizable_option_value_uid' => $this->uidEncoder->encode((string)implode('/', $optionDetails)),
+ 'label' => $optionValue->getTitle(),
+ 'value' => $optionId,
+ 'price' => [
+ 'type' => strtoupper($optionValue->getPriceType()),
+ 'units' => $priceValueUnits,
+ 'value' => $optionValue->getPrice(),
+ ],
+ ];
+ }
}
return $selectedOptionValueData;
diff --git a/app/code/Magento/QuoteGraphQl/Model/CartItem/PrecursorComposite.php b/app/code/Magento/QuoteGraphQl/Model/CartItem/PrecursorComposite.php
new file mode 100644
index 0000000000000..b76be2f0f14f7
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/CartItem/PrecursorComposite.php
@@ -0,0 +1,54 @@
+precursors = $precursors;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function process(array $cartItemData, ContextInterface $context): array
+ {
+ foreach ($this->precursors as $precursor) {
+ $cartItemData = $precursor->process($cartItemData, $context);
+ }
+ return $cartItemData;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getErrors(): array
+ {
+ $errors = [];
+ foreach ($this->precursors as $precursor) {
+ foreach ($precursor->getErrors() as $error) {
+ $errors[] = $error;
+ }
+ }
+ return $errors;
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/CartItem/PrecursorInterface.php b/app/code/Magento/QuoteGraphQl/Model/CartItem/PrecursorInterface.php
new file mode 100644
index 0000000000000..bbca4d3ec05b1
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/CartItem/PrecursorInterface.php
@@ -0,0 +1,35 @@
+getCartForUser = $getCartForUser;
$this->addProductsToCartService = $addProductsToCart;
- $this->itemDataProcessor = $itemDataProcessor;
$this->quoteMutex = $quoteMutex;
+ $this->cartItemPrecursor = $cartItemPrecursor ?: ObjectManager::getInstance()->get(PrecursorInterface::class);
}
/**
@@ -102,11 +108,9 @@ private function run($context, ?array $args): array
// Shopping Cart validation
$this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId);
+ $cartItemsData = $this->cartItemPrecursor->process($cartItemsData, $context);
$cartItems = [];
foreach ($cartItemsData as $cartItemData) {
- if (!$this->itemIsAllowedToCart($cartItemData, $context)) {
- continue;
- }
$cartItems[] = (new CartItemFactory())->create($cartItemData);
}
@@ -125,25 +129,8 @@ function (Error $error) {
'path' => [$error->getCartItemPosition()]
];
},
- $addProductsToCartOutput->getErrors()
+ array_merge($addProductsToCartOutput->getErrors(), $this->cartItemPrecursor->getErrors())
)
];
}
-
- /**
- * Check if the item can be added to cart
- *
- * @param array $cartItemData
- * @param ContextInterface $context
- * @return bool
- */
- private function itemIsAllowedToCart(array $cartItemData, ContextInterface $context): bool
- {
- $cartItemData = $this->itemDataProcessor->process($cartItemData, $context);
- if (isset($cartItemData['grant_checkout']) && $cartItemData['grant_checkout'] === false) {
- return false;
- }
-
- return true;
- }
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php
index 9965990f7f420..6e48719e039f1 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php
@@ -90,7 +90,6 @@ private function run($context, ?array $args): array
$storeId = (int)$context->getExtensionAttributes()->getStore()->getId();
$cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId);
$this->addProductsToCart->execute($cart, $cartItems);
- $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId);
return [
'cart' => [
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php
index 6a53d976d59b3..ddd7d25943baa 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php
@@ -85,7 +85,6 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
throw new LocalizedException(__($e->getMessage()), $e);
}
- $cart = $this->getCartForUser->execute($maskedCartId, $currentUserId, $storeId);
return [
'cart' => [
'model' => $cart,
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/AvailablePaymentMethods.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/AvailablePaymentMethods.php
index ff1228b4a2ff9..f09339a3dcda3 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/AvailablePaymentMethods.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/AvailablePaymentMethods.php
@@ -50,8 +50,13 @@ public function __construct(
/**
* @inheritdoc
*/
- public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
- {
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
if (!isset($value['model'])) {
throw new LocalizedException(__('"model" value should be specified'));
}
@@ -79,17 +84,19 @@ private function getPaymentMethodsData(CartInterface $cart): array
/**
* Checking payment method and shipping method for zero price product
*/
- if ((int)$grandTotal === 0 && $carrierCode === self::FREE_SHIPPING_METHOD
- && $paymentMethod->getCode() === self::FREE_PAYMENT_METHOD_CODE) {
- $paymentMethodsData[] = [
- 'title' => $paymentMethod->getTitle(),
- 'code' => $paymentMethod->getCode(),
+ if ((int)$grandTotal === 0 && $carrierCode === self::FREE_SHIPPING_METHOD &&
+ $paymentMethod->getCode() === self::FREE_PAYMENT_METHOD_CODE
+ ) {
+ return [
+ [
+ 'title' => $paymentMethod->getTitle(),
+ 'code' => $paymentMethod->getCode()
+ ]
];
- } elseif ((int)$grandTotal >= 0
- && $carrierCode !== self::FREE_SHIPPING_METHOD) {
+ } elseif ((int)$grandTotal >= 0) {
$paymentMethodsData[] = [
'title' => $paymentMethod->getTitle(),
- 'code' => $paymentMethod->getCode(),
+ 'code' => $paymentMethod->getCode()
];
}
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/Discounts.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/Discounts.php
index 703015fd7ddb1..d0c69b8b54497 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/Discounts.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/Discounts.php
@@ -41,18 +41,18 @@ private function getDiscountValues(Quote $quote)
{
$discountValues=[];
$address = $quote->getShippingAddress();
- $totalDiscounts = $address->getExtensionAttributes()->getDiscounts();
- if ($totalDiscounts && is_array($totalDiscounts)) {
- foreach ($totalDiscounts as $value) {
- $discount = [];
- $amount = [];
- $discount['label'] = $value->getRuleLabel() ?: __('Discount');
- /* @var \Magento\SalesRule\Api\Data\DiscountDataInterface $discountData */
- $discountData = $value->getDiscountData();
- $amount['value'] = $discountData->getAmount();
- $amount['currency'] = $quote->getQuoteCurrencyCode();
- $discount['amount'] = $amount;
- $discountValues[] = $discount;
+ $totals = $address->getTotals();
+ if ($totals && is_array($totals)) {
+ foreach ($totals as $total) {
+ if (stripos($total->getCode(), 'total') === false && $total->getValue() < 0.00) {
+ $discount = [];
+ $amount = [];
+ $discount['label'] = $total->getTitle() ?: __('Discount');
+ $amount['value'] = $total->getValue() * -1;
+ $amount['currency'] = $quote->getQuoteCurrencyCode();
+ $discount['amount'] = $amount;
+ $discountValues[] = $discount;
+ }
}
return $discountValues;
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php
index f338732e4079d..7cbc64a41d37c 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php
@@ -7,14 +7,17 @@
namespace Magento\QuoteGraphQl\Model\Resolver;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\GraphQl\Helper\Error\AggregateExceptionMessageFormatter;
-use Magento\QuoteGraphQl\Model\Cart\GetCartForUser;
+use Magento\QuoteGraphQl\Model\Cart\GetCartForCheckout;
+use Magento\GraphQl\Model\Query\ContextInterface;
use Magento\QuoteGraphQl\Model\Cart\PlaceOrder as PlaceOrderModel;
+use Magento\QuoteGraphQl\Model\Cart\PlaceOrderMutexInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
/**
@@ -23,9 +26,9 @@
class PlaceOrder implements ResolverInterface
{
/**
- * @var GetCartForUser
+ * @var GetCartForCheckout
*/
- private $getCartForUser;
+ private $getCartForCheckout;
/**
* @var PlaceOrderModel
@@ -43,21 +46,29 @@ class PlaceOrder implements ResolverInterface
private $errorMessageFormatter;
/**
- * @param GetCartForUser $getCartForUser
+ * @var PlaceOrderMutexInterface
+ */
+ private $placeOrderMutex;
+
+ /**
+ * @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
+ AggregateExceptionMessageFormatter $errorMessageFormatter,
+ ?PlaceOrderMutexInterface $placeOrderMutex = null
) {
- $this->getCartForUser = $getCartForUser;
+ $this->getCartForCheckout = $getCartForCheckout;
$this->placeOrder = $placeOrder;
$this->orderRepository = $orderRepository;
$this->errorMessageFormatter = $errorMessageFormatter;
+ $this->placeOrderMutex = $placeOrderMutex ?: ObjectManager::getInstance()->get(PlaceOrderMutexInterface::class);
}
/**
@@ -68,12 +79,32 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
if (empty($args['input']['cart_id'])) {
throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
}
+
+ return $this->placeOrderMutex->execute(
+ $args['input']['cart_id'],
+ \Closure::fromCallable([$this, 'run']),
+ [$field, $context, $info, $args]
+ );
+ }
+
+ /**
+ * Run the resolver.
+ *
+ * @param Field $field
+ * @param ContextInterface $context
+ * @param ResolveInfo $info
+ * @param array|null $args
+ * @return array[]
+ * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
+ */
+ private function run(Field $field, ContextInterface $context, ResolveInfo $info, ?array $args): array
+ {
$maskedCartId = $args['input']['cart_id'];
$userId = (int)$context->getUserId();
$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/SetBillingAddressOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetBillingAddressOnCart.php
index 55725e9fcce2b..eb82510003fc7 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetBillingAddressOnCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetBillingAddressOnCart.php
@@ -69,7 +69,6 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
$cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId);
$this->checkCartCheckoutAllowance->execute($cart);
$this->setBillingAddressOnCart->execute($context, $cart, $billingAddress);
- $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId);
return [
'cart' => [
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/Model/Resolver/SetPaymentMethodOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php
index bc753d50db68a..fb6c1e678f1f0 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php
@@ -69,7 +69,6 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
$cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId);
$this->checkCartCheckoutAllowance->execute($cart);
$this->setPaymentMethodOnCart->execute($cart, $paymentData);
- $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId);
return [
'cart' => [
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingAddressesOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingAddressesOnCart.php
index 66bea8e886a57..d86244b2d8fc3 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingAddressesOnCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingAddressesOnCart.php
@@ -69,8 +69,6 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
$cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId);
$this->checkCartCheckoutAllowance->execute($cart);
$this->setShippingAddressesOnCart->execute($context, $cart, $shippingAddresses);
- // reload updated cart
- $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId);
return [
'cart' => [
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php
index 911078fd029f1..e1cd9c18d9873 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php
@@ -69,7 +69,6 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
$cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId);
$this->checkCartCheckoutAllowance->execute($cart);
$this->setShippingMethodsOnCart->execute($context, $cart, $shippingMethods);
- $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId);
return [
'cart' => [
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php
index 981f5f3603516..58c2cf7e2aaed 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php
@@ -94,7 +94,6 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
throw new GraphQlInputException(__($e->getMessage()), $e);
}
- $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId(), $storeId);
return [
'cart' => [
'model' => $cart,
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/QuoteGraphQl/etc/di.xml b/app/code/Magento/QuoteGraphQl/etc/di.xml
index 73724e03bcf57..63eb001821c01 100644
--- a/app/code/Magento/QuoteGraphQl/etc/di.xml
+++ b/app/code/Magento/QuoteGraphQl/etc/di.xml
@@ -8,6 +8,8 @@
+
+
diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls
index eba02ff8972fc..218806270ab95 100644
--- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls
@@ -123,6 +123,7 @@ input CartAddressInput @doc(description: "Defines the billing or shipping addres
postcode: String @doc(description: "The ZIP or postal code of the billing or shipping address.")
country_code: String! @doc(description: "The country code and label for the billing or shipping address.")
telephone: String @doc(description: "The telephone number for the billing or shipping address.")
+ vat_id: String @doc(description: "The VAT company number for billing or shipping address.")
save_in_address_book: Boolean @doc(description: "Determines whether to save the address in the customer's address book. The default value is true.")
}
@@ -167,7 +168,7 @@ type CartPrices @doc(description: "Contains details about the final price of it
discount: CartDiscount @deprecated(reason: "Use discounts instead.")
subtotal_with_discount_excluding_tax: Money @doc(description: "The subtotal with any discounts applied, but not taxes.")
applied_taxes: [CartTaxItem] @doc(description: "An array containing the names and amounts of taxes applied to each item in the cart.")
- discounts: [Discount] @doc(description:"An array containing all discounts applied to the cart.") @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\Discounts")
+ discounts: [Discount] @doc(description:"An array containing cart rule discounts, store credit and gift cards applied to the cart.") @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\Discounts")
}
type CartTaxItem @doc(description: "Contains tax information about an item in the cart.") {
@@ -229,6 +230,7 @@ interface CartAddressInterface @typeResolver(class: "\\Magento\\QuoteGraphQl\\Mo
postcode: String @doc(description: "The ZIP or postal code of the billing or shipping address.")
country: CartAddressCountry! @doc(description: "An object containing the country label and code.")
telephone: String @doc(description: "The telephone number for the billing or shipping address.")
+ vat_id: String @doc(description: "The VAT company number for billing or shipping address.")
}
type ShippingCartAddress implements CartAddressInterface @doc(description: "Contains shipping addresses and methods.") {
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/Block/Adminhtml/Filter/Form.php b/app/code/Magento/Reports/Block/Adminhtml/Filter/Form.php
index b9e31829d9436..b7b669163e93c 100644
--- a/app/code/Magento/Reports/Block/Adminhtml/Filter/Form.php
+++ b/app/code/Magento/Reports/Block/Adminhtml/Filter/Form.php
@@ -201,8 +201,8 @@ protected function _initFormValues()
{
$data = $this->getFilterData()->getData();
foreach ($data as $key => $value) {
- if (is_array($value) && isset($value[0])) {
- $data[$key] = explode(',', $value[0]);
+ if (is_array($value) && count($value) === 1) {
+ $data[$key] = explode(',', reset($value));
}
}
$this->getForm()->addValues($data);
diff --git a/app/code/Magento/Reports/Block/Adminhtml/Grid/Column/Renderer/Currency.php b/app/code/Magento/Reports/Block/Adminhtml/Grid/Column/Renderer/Currency.php
index 1ebfd64b2f37c..97d0493c5d9dd 100644
--- a/app/code/Magento/Reports/Block/Adminhtml/Grid/Column/Renderer/Currency.php
+++ b/app/code/Magento/Reports/Block/Adminhtml/Grid/Column/Renderer/Currency.php
@@ -7,10 +7,11 @@
namespace Magento\Reports\Block\Adminhtml\Grid\Column\Renderer;
-use Magento\Backend\Block\Widget\Grid\Column\Renderer\Currency as BackendCurrency;
use Magento\Backend\Block\Context;
+use Magento\Backend\Block\Widget\Grid\Column\Renderer\Currency as BackendCurrency;
use Magento\Directory\Model\Currency\DefaultLocator;
use Magento\Directory\Model\CurrencyFactory;
+use Magento\Framework\Currency\Exception\CurrencyException;
use Magento\Framework\DataObject;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
@@ -18,7 +19,6 @@
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
-use Zend_Currency_Exception;
/**
* Adminhtml grid item renderer currency
@@ -70,7 +70,7 @@ public function __construct(
* @return string
* @throws LocalizedException
* @throws NoSuchEntityException
- * @throws Zend_Currency_Exception
+ * @throws CurrencyException
*/
public function render(DataObject $row)
{
@@ -80,13 +80,10 @@ public function render(DataObject $row)
if (!$currencyCode) {
return $data;
}
-
$rate = $this->getStoreCurrencyRate($currencyCode, $row);
-
- $data = (float)$data * $rate;
- $data = sprintf("%f", $data);
- $data = $this->_localeCurrency->getCurrency($currencyCode)->toCurrency($data);
- return $data;
+ $data = (float) $data * $rate;
+ $data = sprintf('%f', $data);
+ return $this->_localeCurrency->getCurrency($currencyCode)->toCurrency($data);
}
/**
diff --git a/app/code/Magento/Reports/Block/Adminhtml/Sales/Coupons/Grid.php b/app/code/Magento/Reports/Block/Adminhtml/Sales/Coupons/Grid.php
index 358a0e85281a6..e6e8bdbec964a 100644
--- a/app/code/Magento/Reports/Block/Adminhtml/Sales/Coupons/Grid.php
+++ b/app/code/Magento/Reports/Block/Adminhtml/Sales/Coupons/Grid.php
@@ -211,9 +211,11 @@ protected function _addCustomFilter($collection, $filterData)
{
if ($filterData->getPriceRuleType()) {
$rulesList = $filterData->getData('rules_list');
- if (isset($rulesList[0])) {
- $rulesIds = explode(',', $rulesList[0]);
- $collection->addRuleFilter($rulesIds);
+ if (is_array($rulesList) && count($rulesList) > 0) {
+ if (count($rulesList) === 1) {
+ $rulesList = explode(',', reset($rulesList));
+ }
+ $collection->addRuleFilter($rulesList);
}
}
diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/AbstractReport.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/AbstractReport.php
index 8e0a3ab094497..6f36a59992376 100644
--- a/app/code/Magento/Reports/Controller/Adminhtml/Report/AbstractReport.php
+++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/AbstractReport.php
@@ -13,6 +13,7 @@
namespace Magento\Reports\Controller\Adminhtml\Report;
use Magento\Backend\Helper\Data as BackendHelper;
+use Magento\Framework\Filter\FilterInput;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
/**
@@ -183,7 +184,7 @@ private function initFilterData(): \Magento\Framework\DataObject
);
$filterRules = ['from' => $this->_dateFilter, 'to' => $this->_dateFilter];
- $inputFilter = new \Zend_Filter_Input($filterRules, [], $requestData);
+ $inputFilter = new FilterInput($filterRules, [], $requestData);
$requestData = $inputFilter->getUnescaped();
$requestData['store_ids'] = $this->getRequest()->getParam('store_ids');
diff --git a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
index 7ae1360adabc0..67e451c4c591c 100644
--- a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
+++ b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
@@ -463,7 +463,6 @@ public function getDateRange($range, $customStart, $customEnd, $returnObjects =
$startMonth = isset($startMonthDay[0]) ? (int)$startMonthDay[0] : 1;
$startDay = isset($startMonthDay[1]) ? (int)$startMonthDay[1] : 1;
$dateStart->setDate($dateStart->format('Y'), $startMonth, $startDay);
- $dateStart->modify('-1 year');
if ($range == '2y') {
$dateStart->modify('-1 year');
}
diff --git a/app/code/Magento/Reports/Model/ResourceModel/Quote/Item/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Quote/Item/Collection.php
index e7dc28eb74a49..9d8f89629c761 100644
--- a/app/code/Magento/Reports/Model/ResourceModel/Quote/Item/Collection.php
+++ b/app/code/Magento/Reports/Model/ResourceModel/Quote/Item/Collection.php
@@ -121,7 +121,7 @@ public function prepareActiveCartItems()
$quoteItemsSelect->reset()
->from(['main_table' => $this->getTable('quote_item')], '')
- ->columns('main_table.product_id')
+ ->columns(['main_table.product_id', 'main_table.name'])
->columns(['carts' => new \Zend_Db_Expr('COUNT(main_table.item_id)')])
->columns('quote.base_to_global_rate')
->joinInner(
diff --git a/app/code/Magento/Reports/Test/Mftf/ActionGroup/AdminVerifyOrdersTotalDatePickerActionGroup.xml b/app/code/Magento/Reports/Test/Mftf/ActionGroup/AdminVerifyOrdersTotalDatePickerActionGroup.xml
new file mode 100644
index 0000000000000..4c7cb99f590d7
--- /dev/null
+++ b/app/code/Magento/Reports/Test/Mftf/ActionGroup/AdminVerifyOrdersTotalDatePickerActionGroup.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+ Clicks on 'FROM' and 'TO' From Date Picker.Verify the calendar UI does not show any button text after selecting date.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Reports/Test/Mftf/Section/OrderReportMainSection/OrderReportMainSection.xml b/app/code/Magento/Reports/Test/Mftf/Section/OrderReportMainSection/OrderReportMainSection.xml
index 4368842e640d2..1f9c98175ca2d 100644
--- a/app/code/Magento/Reports/Test/Mftf/Section/OrderReportMainSection/OrderReportMainSection.xml
+++ b/app/code/Magento/Reports/Test/Mftf/Section/OrderReportMainSection/OrderReportMainSection.xml
@@ -10,5 +10,12 @@
diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminCanceledOrdersInOrderSalesReportTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminCanceledOrdersInOrderSalesReportTest.xml
index e56d2b5bcbe6e..600291dffade4 100644
--- a/app/code/Magento/Reports/Test/Mftf/Test/AdminCanceledOrdersInOrderSalesReportTest.xml
+++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminCanceledOrdersInOrderSalesReportTest.xml
@@ -21,6 +21,8 @@
+
+
@@ -72,6 +74,8 @@
+
+
diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderTotalDatePickerTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderTotalDatePickerTest.xml
new file mode 100644
index 0000000000000..bbc92569e07c2
--- /dev/null
+++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderTotalDatePickerTest.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Reports/Test/Unit/Block/Adminhtml/Filter/FormTest.php b/app/code/Magento/Reports/Test/Unit/Block/Adminhtml/Filter/FormTest.php
new file mode 100644
index 0000000000000..a5c5bc0efb5d8
--- /dev/null
+++ b/app/code/Magento/Reports/Test/Unit/Block/Adminhtml/Filter/FormTest.php
@@ -0,0 +1,93 @@
+context = $this->createMock(Context::class);
+ $this->registry = $this->createMock(Registry::class);
+ $this->formFactory = $this->createMock(FormFactory::class);
+ $this->context->method('getUrlBuilder')
+ ->willReturn($this->getMockForAbstractClass(UrlInterface::class));
+ $this->model = new \Magento\Reports\Block\Adminhtml\Filter\Form(
+ $this->context,
+ $this->registry,
+ $this->formFactory
+ );
+ }
+
+ /**
+ * @return void
+ * @throws \ReflectionException
+ */
+ public function testMultiselectInitialValues(): void
+ {
+ $this->context->method('getUrlBuilder')
+ ->willReturn($this->getMockForAbstractClass(UrlInterface::class));
+ $this->model->setData('filter_data', new DataObject(['multiselect' => ['5', '6']]));
+ $form = $this->getMockBuilder(Form::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['getElements'])
+ ->getMockForAbstractClass();
+ $element = $this->getMockBuilder(AbstractElement::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $element->setId('multiselect');
+ $form->method('getElements')->willReturn(new Collection($form));
+ $reflection = new ReflectionClass($form);
+ $reflectionProp = $reflection->getProperty('_allElements');
+ $reflectionProp->setAccessible(true);
+ $reflectionProp->setValue($form, new Collection($form));
+ $form->addElement($element);
+ $this->model->setForm($form);
+ $reflection = new ReflectionClass($this->model);
+ $reflectionMethod = $reflection->getMethod('_initFormValues');
+ $reflectionMethod->setAccessible(true);
+ $reflectionMethod->invoke($this->model);
+ $this->assertEquals(['5', '6'], $this->model->getForm()->getElement('multiselect')->getValue());
+ }
+}
diff --git a/app/code/Magento/Reports/Test/Unit/Block/Adminhtml/Grid/Column/Renderer/CurrencyTest.php b/app/code/Magento/Reports/Test/Unit/Block/Adminhtml/Grid/Column/Renderer/CurrencyTest.php
index a738fbc6405a9..e55cda299682e 100644
--- a/app/code/Magento/Reports/Test/Unit/Block/Adminhtml/Grid/Column/Renderer/CurrencyTest.php
+++ b/app/code/Magento/Reports/Test/Unit/Block/Adminhtml/Grid/Column/Renderer/CurrencyTest.php
@@ -12,6 +12,8 @@
use Magento\Directory\Model\Currency\DefaultLocator;
use Magento\Directory\Model\CurrencyFactory;
use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\Currency\Data\Currency as CurrencyData;
+use Magento\Framework\Currency\Exception\CurrencyException;
use Magento\Framework\DataObject;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
@@ -23,8 +25,6 @@
use Magento\Store\Model\StoreManagerInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
-use Zend_Currency;
-use Zend_Currency_Exception;
/**
* Test for class Currency.
@@ -179,11 +179,11 @@ protected function setUp(): void
* @param int $adminWebsiteId
* @param string $adminCurrencyCode
* @param string $storeCurrencyCode
- * @param float $adminOrderAmount
- * @param float $convertedAmount
+ * @param string $adminOrderAmount
+ * @param string $convertedAmount
* @throws LocalizedException
* @throws NoSuchEntityException
- * @throws Zend_Currency_Exception
+ * @throws CurrencyException
* @dataProvider getCurrencyDataProvider
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
@@ -194,8 +194,8 @@ public function testRender(
int $adminWebsiteId,
string $adminCurrencyCode,
string $storeCurrencyCode,
- float $adminOrderAmount,
- float $convertedAmount
+ string $adminOrderAmount,
+ string $convertedAmount
): void {
$this->row = new DataObject(
[
@@ -240,7 +240,7 @@ public function testRender(
->expects($this->any())
->method('getDefaultCurrency')
->willReturn($storeCurrencyCode);
- $currLocaleMock = $this->createMock(Zend_Currency::class);
+ $currLocaleMock = $this->createMock(CurrencyData::class);
$currLocaleMock
->expects($this->any())
->method('toCurrency')
@@ -271,8 +271,8 @@ public function getCurrencyDataProvider(): array
'adminWebsiteId' => 1,
'adminCurrencyCode' => 'EUR',
'storeCurrencyCode' => 'EUR',
- 'adminOrderAmount' => 105.00,
- 'convertedAmount' => 105.00
+ 'adminOrderAmount' => '105.00',
+ 'convertedAmount' => '105.00'
],
'rate conversion with different admin and storefront rate' => [
'rate' => 1.4150,
@@ -281,8 +281,8 @@ public function getCurrencyDataProvider(): array
'adminWebsiteId' => 1,
'adminCurrencyCode' => 'USD',
'storeCurrencyCode' => 'EUR',
- 'adminOrderAmount' => 105.00,
- 'convertedAmount' => 148.575
+ 'adminOrderAmount' => '105.00',
+ 'convertedAmount' => '148.575'
]
];
}
diff --git a/app/code/Magento/Reports/Test/Unit/Block/Adminhtml/Sales/Coupons/GridTest.php b/app/code/Magento/Reports/Test/Unit/Block/Adminhtml/Sales/Coupons/GridTest.php
index 1c671ca11744f..91ad4babe42bd 100644
--- a/app/code/Magento/Reports/Test/Unit/Block/Adminhtml/Sales/Coupons/GridTest.php
+++ b/app/code/Magento/Reports/Test/Unit/Block/Adminhtml/Sales/Coupons/GridTest.php
@@ -67,16 +67,18 @@ protected function setUp(): void
* @dataProvider getCountTotalsDataProvider
*
* @param string $reportType
- * @param int $priceRuleType
+ * @param array|null $rulesList
* @param int $collectionSize
* @param bool $expectedCountTotals
+ * @param array|null $expectedRuleFilter
* @return void
*/
public function testGetCountTotals(
string $reportType,
- int $priceRuleType,
+ ?array $rulesList,
int $collectionSize,
- bool $expectedCountTotals
+ bool $expectedCountTotals,
+ ?array $expectedRuleFilter = null
): void {
$filterData = new DataObject();
$filterData->setData('report_type', $reportType);
@@ -84,15 +86,18 @@ public function testGetCountTotals(
$filterData->setData('from', '2000-01-01');
$filterData->setData('to', '2000-01-30');
$filterData->setData('store_ids', '1');
- $filterData->setData('price_rule_type', $priceRuleType);
- if ($priceRuleType) {
- $filterData->setData('rules_list', ['0,1']);
- }
+ $filterData->setData('price_rule_type', $rulesList !== null);
+ $filterData->setData('rules_list', $rulesList);
$filterData->setData('order_statuses', 'statuses');
$this->model->setFilterData($filterData);
$resourceCollectionName = $this->model->getResourceCollectionName();
- $collectionMock = $this->buildBaseCollectionMock($filterData, $resourceCollectionName, $collectionSize);
+ $collectionMock = $this->buildBaseCollectionMock(
+ $filterData,
+ $resourceCollectionName,
+ $collectionSize,
+ $expectedRuleFilter
+ );
$store = $this->getMockBuilder(StoreInterface::class)
->getMock();
@@ -111,23 +116,26 @@ public function testGetCountTotals(
public function getCountTotalsDataProvider(): array
{
return [
- ['created_at_shipment', 0, 0, false],
- ['created_at_shipment', 0, 1, true],
- ['updated_at_order', 0, 1, true],
- ['updated_at_order', 1, 1, true],
+ ['created_at_shipment', null, 0, false],
+ ['created_at_shipment', null, 1, true],
+ ['updated_at_order', null, 1, true],
+ ['updated_at_order', ['1,2'], 1, true, ['1', '2']],
+ ['updated_at_order', ['1', '2'], 1, true, ['1', '2']],
];
}
/**
- * @param \Magento\Framework\DataObject $filterData
+ * @param DataObject $filterData
* @param string $resourceCollectionName
* @param int $collectionSize
+ * @param array|null $ruleFilter
* @return MockObject
*/
private function buildBaseCollectionMock(
DataObject $filterData,
string $resourceCollectionName,
- int $collectionSize
+ int $collectionSize,
+ ?array $ruleFilter
): MockObject {
$collectionMock = $this->getMockBuilder($resourceCollectionName)
->disableOriginalConstructor()
@@ -159,7 +167,7 @@ private function buildBaseCollectionMock(
if ($filterData->getData('price_rule_type')) {
$collectionMock->expects($this->once())
->method('addRuleFilter')
- ->with(\explode(',', $filterData->getData('rules_list')[0]))
+ ->with($ruleFilter)
->willReturnSelf();
}
diff --git a/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Order/CollectionTest.php b/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Order/CollectionTest.php
index e834b5fd9f69c..9e4f39be6b7dc 100644
--- a/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Order/CollectionTest.php
+++ b/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Order/CollectionTest.php
@@ -295,10 +295,11 @@ public function testGetDateRangeFirstPart($range, $customStart, $customEnd, $exp
* @param string $customStart
* @param string $customEnd
* @param string $config
+ * @param int $expectedYear
* @dataProvider secondPartDateRangeDataProvider
* @return void
*/
- public function testGetDateRangeSecondPart($range, $customStart, $customEnd, $config): void
+ public function testGetDateRangeSecondPart($range, $customStart, $customEnd, $config, $expectedYear): void
{
$this->scopeConfigMock
->expects($this->once())
@@ -311,6 +312,8 @@ public function testGetDateRangeSecondPart($range, $customStart, $customEnd, $co
$result = $this->collection->getDateRange($range, $customStart, $customEnd);
$this->assertCount(3, $result);
+ $resultStartDate = $result['from'];
+ $this->assertEquals($expectedYear, $resultStartDate->format('Y'));
}
/**
@@ -468,10 +471,14 @@ public function firstPartDateRangeDataProvider(): array
*/
public function secondPartDateRangeDataProvider(): array
{
+ $dateStart = new \DateTime();
+ $expectedYear = $dateStart->format('Y');
+ $expected2YTDYear = $expectedYear - 1;
+
return [
- ['1m', 1, 10, 'reports/dashboard/mtd_start'],
- ['1y', 1, 10, 'reports/dashboard/ytd_start'],
- ['2y', 1, 10, 'reports/dashboard/ytd_start']
+ ['1m', 1, 10, 'reports/dashboard/mtd_start', $expectedYear],
+ ['1y', 1, 10, 'reports/dashboard/ytd_start', $expectedYear],
+ ['2y', 1, 10, 'reports/dashboard/ytd_start', $expected2YTDYear]
];
}
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/Reports/view/adminhtml/templates/grid.phtml b/app/code/Magento/Reports/view/adminhtml/templates/grid.phtml
index 4f6e3c4a9a02b..36f9c624003c7 100644
--- a/app/code/Magento/Reports/view/adminhtml/templates/grid.phtml
+++ b/app/code/Magento/Reports/view/adminhtml/templates/grid.phtml
@@ -76,7 +76,7 @@
$("#{$block->escapeJs($block->getSuffixId('period_date_range'))}").dateRange({
dateFormat:"{$block->escapeJs($block->getDateFormat())}",
- buttonText:"{$block->escapeJs(__('Select Date'))}",
+ buttonText:"",
from:{
id:"{$block->escapeJs($block->getSuffixId('period_date_from'))}"
},
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/Controller/Adminhtml/Product/MassDelete.php b/app/code/Magento/Review/Controller/Adminhtml/Product/MassDelete.php
index 44b267dc5aa7c..64e2421df2a33 100644
--- a/app/code/Magento/Review/Controller/Adminhtml/Product/MassDelete.php
+++ b/app/code/Magento/Review/Controller/Adminhtml/Product/MassDelete.php
@@ -71,7 +71,10 @@ public function execute()
} catch (LocalizedException $e) {
$this->messageManager->addErrorMessage($e->getMessage());
} catch (\Exception $e) {
- $this->messageManager->addExceptionMessage($e, __('Something went wrong while deleting these records.'));
+ $this->messageManager->addExceptionMessage(
+ $e,
+ __('Something went wrong while deleting these records.')
+ );
}
}
/** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
@@ -123,6 +126,7 @@ private function getCollection(): Collection
->getIdFieldName(),
$this->getRequest()->getParam('reviews')
);
+ $collection->addStoreData();
$this->collection = $collection;
}
diff --git a/app/code/Magento/Review/Model/Review.php b/app/code/Magento/Review/Model/Review.php
index f2e0997ea8878..ef2474637f384 100644
--- a/app/code/Magento/Review/Model/Review.php
+++ b/app/code/Magento/Review/Model/Review.php
@@ -9,6 +9,9 @@
use Magento\Framework\DataObject;
use Magento\Catalog\Model\Product;
use Magento\Framework\DataObject\IdentityInterface;
+use Magento\Framework\Validator\NotEmpty;
+use Magento\Framework\Validator\ValidateException;
+use Magento\Framework\Validator\ValidatorChain;
use Magento\Review\Model\ResourceModel\Review\Product\Collection as ProductCollection;
use Magento\Review\Model\ResourceModel\Review\Status\Collection as StatusCollection;
@@ -36,39 +39,39 @@ class Review extends \Magento\Framework\Model\AbstractModel implements IdentityI
protected $_eventPrefix = 'review';
/**
- * Cache tag
+ * Cache tag value
*/
- const CACHE_TAG = 'review_block';
+ public const CACHE_TAG = 'review_block';
/**
* Product entity review code
*/
- const ENTITY_PRODUCT_CODE = 'product';
+ public const ENTITY_PRODUCT_CODE = 'product';
/**
* Customer entity review code
*/
- const ENTITY_CUSTOMER_CODE = 'customer';
+ public const ENTITY_CUSTOMER_CODE = 'customer';
/**
* Category entity review code
*/
- const ENTITY_CATEGORY_CODE = 'category';
+ public const ENTITY_CATEGORY_CODE = 'category';
/**
* Approved review status code
*/
- const STATUS_APPROVED = 1;
+ public const STATUS_APPROVED = 1;
/**
* Pending review status code
*/
- const STATUS_PENDING = 2;
+ public const STATUS_PENDING = 2;
/**
* Not Approved review status code
*/
- const STATUS_NOT_APPROVED = 3;
+ public const STATUS_NOT_APPROVED = 3;
/**
* Review product collection factory
@@ -102,6 +105,7 @@ class Review extends \Magento\Framework\Model\AbstractModel implements IdentityI
* Review model summary
*
* @deprecated 100.3.3 Summary factory injected as separate property
+ * @see we don't recommend this approach anymore
* @var \Magento\Review\Model\Review\Summary
*/
protected $_reviewSummary;
@@ -217,6 +221,7 @@ public function aggregate()
* Get entity summary
*
* @deprecated 100.3.3
+ * @see we don't recommend this approach anymore
* @param Product $product
* @param int $storeId
* @return void
@@ -269,20 +274,21 @@ public function getProductUrl($productId, $storeId)
* Validate review summary fields
*
* @return bool|string[]
+ * @throws ValidateException
*/
public function validate()
{
$errors = [];
- if (!\Zend_Validate::is($this->getTitle(), 'NotEmpty')) {
+ if (!ValidatorChain::is($this->getTitle(), NotEmpty::class)) {
$errors[] = __('Please enter a review summary.');
}
- if (!\Zend_Validate::is($this->getNickname(), 'NotEmpty')) {
+ if (!ValidatorChain::is($this->getNickname(), NotEmpty::class)) {
$errors[] = __('Please enter a nickname.');
}
- if (!\Zend_Validate::is($this->getDetail(), 'NotEmpty')) {
+ if (!ValidatorChain::is($this->getDetail(), NotEmpty::class)) {
$errors[] = __('Please enter a review.');
}
@@ -307,6 +313,7 @@ public function afterDeleteCommit()
* Append review summary data object to product collection
*
* @deprecated 100.3.3
+ * @see we don't recommend this approach anymore
* @param ProductCollection $collection
* @return $this
* @throws \Magento\Framework\Exception\NoSuchEntityException
diff --git a/app/code/Magento/Review/Test/Fixture/Review.php b/app/code/Magento/Review/Test/Fixture/Review.php
new file mode 100644
index 0000000000000..ca8c57bb34351
--- /dev/null
+++ b/app/code/Magento/Review/Test/Fixture/Review.php
@@ -0,0 +1,72 @@
+ ReviewModel::ENTITY_PRODUCT_CODE,
+ 'entity_pk_value' => 1,
+ 'nickname' => 'Nickname',
+ 'title' => 'Review title',
+ 'detail' => 'Review detail',
+ 'status_id' => ReviewModel::STATUS_APPROVED,
+ 'store_id' => 1,
+ ];
+
+ /**
+ * @var ReviewModelFactory
+ */
+ private $reviewModelFactory;
+
+ /**
+ * @var ReviewResourceModel
+ */
+ private $reviewResourceModel;
+
+ /**
+ * @param ReviewModelFactory $reviewModelFactory
+ * @param ReviewResourceModel $reviewResourceModel
+ */
+ public function __construct(
+ ReviewModelFactory $reviewModelFactory,
+ ReviewResourceModel $reviewResourceModel
+ ) {
+ $this->reviewModelFactory = $reviewModelFactory;
+ $this->reviewResourceModel = $reviewResourceModel;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply(array $data = []): ?DataObject
+ {
+ $data += self::DEFAULT_DATA;
+ $data['entity_id'] = $this->reviewResourceModel->getEntityIdByCode($data['entity_code']);
+ unset($data['entity_code']);
+ $reviewModel = $this->reviewModelFactory->create(['data' => $data]);
+ $reviewModel->setStores([$data['store_id']]);
+ $this->reviewResourceModel->save($reviewModel);
+
+ return $reviewModel;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function revert(DataObject $data): void
+ {
+ $this->reviewResourceModel->delete($data);
+ }
+}
diff --git a/app/code/Magento/Review/Test/Unit/Controller/Adminhtml/Product/MassDeleteTest.php b/app/code/Magento/Review/Test/Unit/Controller/Adminhtml/Product/MassDeleteTest.php
new file mode 100644
index 0000000000000..c798a4dca975e
--- /dev/null
+++ b/app/code/Magento/Review/Test/Unit/Controller/Adminhtml/Product/MassDeleteTest.php
@@ -0,0 +1,105 @@
+createMock(Context::class);
+ $this->requestMock = $this->createMock(RequestInterface::class);
+ $contextMock->method('getRequest')
+ ->willReturn($this->requestMock);
+ $messageManagerMock = $this->createMock(ManagerInterface::class);
+ $contextMock->method('getMessageManager')
+ ->willReturn($messageManagerMock);
+ $resultFactoryMock = $this->createMock(ResultFactory::class);
+ $contextMock->method('getResultFactory')
+ ->willReturn($resultFactoryMock);
+ $resultMock = $this->createMock(Redirect::class);
+ $resultFactoryMock->method('create')
+ ->willReturn($resultMock);
+
+ $coreRegistryMock = $this->createMock(Registry::class);
+ $reviewFactoryMock = $this->createMock(ReviewFactory::class);
+ $ratingFactoryMock = $this->createMock(RatingFactory::class);
+ $this->collectionFactoryMock = $this->createMock(ReviewCollectionFactory::class);
+
+ $this->massDelete = new MassDelete(
+ $contextMock,
+ $coreRegistryMock,
+ $reviewFactoryMock,
+ $ratingFactoryMock,
+ $this->collectionFactoryMock
+ );
+ }
+
+ public function testExecute(): void
+ {
+ $this->requestMock->expects(self::atLeastOnce())
+ ->method('getParam')
+ ->willReturnMap(
+ [
+ ['reviews', null, [10, 20]],
+ ['ret', 'index', 'index'],
+ ]
+ );
+
+ $collectionMock = $this->createMock(ReviewCollection::class);
+ $this->collectionFactoryMock->expects(self::once())
+ ->method('create')
+ ->willReturn($collectionMock);
+ $resource = $this->createMock(ReviewResourceModel::class);
+ $collectionMock->method('getResource')
+ ->willReturn($resource);
+ $resource->method('getIdFieldName')
+ ->willReturn('id');
+ $collectionMock->expects(self::once())
+ ->method('addFieldToFilter')
+ ->with('main_table.id', [10, 20])
+ ->willReturnSelf();
+ $collectionMock->expects(self::once())
+ ->method('addStoreData')
+ ->willReturnSelf();
+
+ $this->massDelete->execute();
+ }
+}
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/Model/DataProvider/ProductReviewsDataProvider.php b/app/code/Magento/ReviewGraphQl/Model/DataProvider/ProductReviewsDataProvider.php
index 635605f9091ed..4ac7cb292c201 100644
--- a/app/code/Magento/ReviewGraphQl/Model/DataProvider/ProductReviewsDataProvider.php
+++ b/app/code/Magento/ReviewGraphQl/Model/DataProvider/ProductReviewsDataProvider.php
@@ -36,10 +36,10 @@ public function __construct(
* @param int $productId
* @param int $currentPage
* @param int $pageSize
- *
+ * @param int $storeId
* @return Collection
*/
- public function getData(int $productId, int $currentPage, int $pageSize): Collection
+ public function getData(int $productId, int $currentPage, int $pageSize, int $storeId): Collection
{
/** @var Collection $reviewsCollection */
$reviewsCollection = $this->collectionFactory->create()
@@ -47,6 +47,7 @@ public function getData(int $productId, int $currentPage, int $pageSize): Collec
->addEntityFilter(Review::ENTITY_PRODUCT_CODE, $productId)
->setPageSize($pageSize)
->setCurPage($currentPage)
+ ->addStoreFilter($storeId)
->setDateOrder();
$reviewsCollection->getSelect()->join(
['cpe' => $reviewsCollection->getTable('catalog_product_entity')],
diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/ReviewCount.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/ReviewCount.php
index dfa62adf0266e..a5a758f4a4eb0 100644
--- a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/ReviewCount.php
+++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/ReviewCount.php
@@ -75,6 +75,10 @@ public function resolve(
/** @var Product $product */
$product = $value['model'];
- return (int) $this->review->getTotalReviews($product->getId(), true);
+ return (int) $this->review->getTotalReviews(
+ $product->getId(),
+ true,
+ (int) $context->getExtensionAttributes()->getStore()->getId()
+ );
}
}
diff --git a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Reviews.php b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Reviews.php
index 72eea5e6b3bd2..c5b789cc17d9c 100644
--- a/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Reviews.php
+++ b/app/code/Magento/ReviewGraphQl/Model/Resolver/Product/Reviews.php
@@ -96,7 +96,8 @@ public function resolve(
$reviewsCollection = $this->productReviewsDataProvider->getData(
(int) $product->getId(),
$args['currentPage'],
- $args['pageSize']
+ $args['pageSize'],
+ (int) $context->getExtensionAttributes()->getStore()->getId()
);
return $this->aggregatedReviewsDataProvider->getData($reviewsCollection);
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/Block/Adminhtml/Order/Create/Form/Address.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Address.php
index 123b777602924..d840e530ecbfe 100644
--- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Address.php
+++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Address.php
@@ -20,21 +20,21 @@
class Address extends \Magento\Sales\Block\Adminhtml\Order\Create\Form\AbstractForm
{
/**
- * Customer form factory
+ * Customer Metadata form factory
*
* @var \Magento\Customer\Model\Metadata\FormFactory
*/
protected $_customerFormFactory;
/**
- * Json encoder
+ * Framework Json encoder
*
* @var \Magento\Framework\Json\EncoderInterface
*/
protected $_jsonEncoder;
/**
- * Directory helper
+ * Directory helper Data
*
* @var \Magento\Directory\Helper\Data
*/
@@ -48,28 +48,28 @@ class Address extends \Magento\Sales\Block\Adminhtml\Order\Create\Form\AbstractF
protected $options;
/**
- * Address service
+ * Address service - AddressRepositoryInterface
*
* @var \Magento\Customer\Api\AddressRepositoryInterface
*/
protected $addressService;
/**
- * Address helper
+ * Customer Address helper
*
* @var \Magento\Customer\Helper\Address
*/
protected $_addressHelper;
/**
- * Search criteria builder
+ * Search criteria builder for getList calls
*
* @var \Magento\Framework\Api\SearchCriteriaBuilder
*/
protected $searchCriteriaBuilder;
/**
- * Filter builder
+ * Filter builder for getList calls
*
* @var \Magento\Framework\Api\FilterBuilder
*/
@@ -235,9 +235,13 @@ protected function _prepareForm()
if ($prefixElement) {
$prefixOptions = $this->options->getNamePrefixOptions($this->getStore());
if (!empty($prefixOptions)) {
+ $mappedPrefixOptions = [];
+ foreach ($prefixOptions as $prefix) {
+ $mappedPrefixOptions[$prefix] = $prefix;
+ }
$fieldset->removeField($prefixElement->getId());
$prefixField = $fieldset->addField($prefixElement->getId(), 'select', $prefixElement->getData(), '^');
- $prefixField->setValues($prefixOptions);
+ $prefixField->setValues($mappedPrefixOptions);
if ($this->getAddressId()) {
$prefixField->addElementValues($this->getAddress()->getPrefix());
}
@@ -322,6 +326,7 @@ private function processCountryOptions(\Magento\Framework\Data\Form\Element\Abst
* Retrieve Directory Countries collection
*
* @deprecated 100.1.3
+ * @see MAGETWO-711174: Introduce deprecated and since doc blocks.
* @return \Magento\Directory\Model\ResourceModel\Country\Collection
*/
private function getCountriesCollection()
@@ -338,6 +343,7 @@ private function getCountriesCollection()
* Retrieve Backend Quote Session
*
* @deprecated 100.1.3
+ * @see MAGETWO-711174: Introduce deprecated and since doc blocks.
* @return Quote
*/
private function getBackendQuoteSession()
diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Sidebar/Cart.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Sidebar/Cart.php
index 98fd4d9bae994..53a74ac89554f 100644
--- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Sidebar/Cart.php
+++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Sidebar/Cart.php
@@ -59,6 +59,16 @@ public function getItemCollection()
$collection = $this->getData('item_collection');
if ($collection === null) {
$collection = $this->getCreateOrderModel()->getCustomerCart()->getAllVisibleItems();
+ $transferredItems = $this->getCreateOrderModel()->getSession()->getTransferredItems() ?? [];
+ $transferredItems = $transferredItems[$this->getDataId()] ?? [];
+ if (!empty($transferredItems)) {
+ foreach ($collection as $key => $item) {
+ if (in_array($item->getId(), $transferredItems)) {
+ unset($collection[$key]);
+ }
+ }
+ }
+
$this->setData('item_collection', $collection);
}
return $collection;
diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Sidebar/Wishlist.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Sidebar/Wishlist.php
index 894ca97d62cf2..e7b53094c5a76 100644
--- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Sidebar/Wishlist.php
+++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Sidebar/Wishlist.php
@@ -55,6 +55,15 @@ public function getItemCollection()
$collection = $this->getCreateOrderModel()->getCustomerWishlist(true);
if ($collection) {
$collection = $collection->getItemCollection()->load();
+ $transferredItems = $this->getCreateOrderModel()->getSession()->getTransferredItems() ?? [];
+ $transferredItems = $transferredItems[$this->getDataId()] ?? [];
+ if (!empty($transferredItems)) {
+ foreach ($collection as $key => $item) {
+ if (in_array($item->getId(), $transferredItems)) {
+ $collection->removeItemByKey($key);
+ }
+ }
+ }
}
$this->setData('item_collection', $collection);
}
diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Creditmemo/Create/Adjustments.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Creditmemo/Create/Adjustments.php
index 781fb3b7501b5..59a19014f6cf8 100644
--- a/app/code/Magento/Sales/Block/Adminhtml/Order/Creditmemo/Create/Adjustments.php
+++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Creditmemo/Create/Adjustments.php
@@ -5,9 +5,9 @@
*/
namespace Magento\Sales\Block\Adminhtml\Order\Creditmemo\Create;
+use Magento\Framework\Currency\Data\Currency as CurrencyData;
use Magento\Framework\Pricing\PriceCurrencyInterface;
use Magento\Sales\Model\Order;
-use Zend_Currency;
/**
* Credit memo adjustments block
@@ -25,8 +25,6 @@ class Adjustments extends \Magento\Backend\Block\Template
protected $_source;
/**
- * Tax config
- *
* @var \Magento\Tax\Model\Config
*/
protected $_taxConfig;
@@ -86,7 +84,7 @@ public function formatValue($value)
return $order->getOrderCurrency()->formatPrecision(
$value,
2,
- ['display' => Zend_Currency::NO_SYMBOL],
+ ['display' => CurrencyData::NO_SYMBOL],
false,
false
);
diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/AddComment.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/AddComment.php
index 492d2d71df8d9..3dcd30edd4f04 100644
--- a/app/code/Magento/Sales/Controller/Adminhtml/Order/AddComment.php
+++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/AddComment.php
@@ -20,12 +20,12 @@ class AddComment extends \Magento\Sales\Controller\Adminhtml\Order implements Ht
*
* @see _isAllowed()
*/
- const ADMIN_RESOURCE = 'Magento_Sales::comment';
+ public const ADMIN_RESOURCE = 'Magento_Sales::comment';
/**
* ACL resource needed to send comment email notification
*/
- const ADMIN_SALES_EMAIL_RESOURCE = 'Magento_Sales::emails';
+ public const ADMIN_SALES_EMAIL_RESOURCE = 'Magento_Sales::emails';
/**
* Add order comment action
@@ -52,13 +52,12 @@ public function execute()
$notify = false;
}
- $history = $order->addStatusHistoryComment($data['comment'], $data['status']);
+ $comment = trim(strip_tags($data['comment']));
+ $history = $order->addStatusHistoryComment($comment, $data['status']);
$history->setIsVisibleOnFront($visible);
$history->setIsCustomerNotified($notify);
$history->save();
- $comment = trim(strip_tags($data['comment']));
-
$order->save();
/** @var OrderCommentSender $orderCommentSender */
$orderCommentSender = $this->_objectManager
diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/LoadBlock.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/LoadBlock.php
index 1e13e282cae3a..65ccb43879ac6 100644
--- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/LoadBlock.php
+++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/LoadBlock.php
@@ -10,8 +10,10 @@
use Magento\Backend\App\Action;
use Magento\Backend\Model\View\Result\ForwardFactory;
use Magento\Framework\View\Result\PageFactory;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Controller\Result\RawFactory;
use Magento\Sales\Controller\Adminhtml\Order\Create as CreateAction;
+use Magento\Store\Model\StoreManagerInterface;
class LoadBlock extends CreateAction implements HttpPostActionInterface, HttpGetActionInterface
{
@@ -20,6 +22,11 @@ class LoadBlock extends CreateAction implements HttpPostActionInterface, HttpGet
*/
protected $resultRawFactory;
+ /**
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
+
/**
* @param Action\Context $context
* @param \Magento\Catalog\Helper\Product $productHelper
@@ -27,6 +34,7 @@ class LoadBlock extends CreateAction implements HttpPostActionInterface, HttpGet
* @param PageFactory $resultPageFactory
* @param ForwardFactory $resultForwardFactory
* @param RawFactory $resultRawFactory
+ * @param StoreManagerInterface|null $storeManager
*/
public function __construct(
Action\Context $context,
@@ -34,7 +42,8 @@ public function __construct(
\Magento\Framework\Escaper $escaper,
PageFactory $resultPageFactory,
ForwardFactory $resultForwardFactory,
- RawFactory $resultRawFactory
+ RawFactory $resultRawFactory,
+ StoreManagerInterface $storeManager = null
) {
$this->resultRawFactory = $resultRawFactory;
parent::__construct(
@@ -44,16 +53,24 @@ public function __construct(
$resultPageFactory,
$resultForwardFactory
);
+ $this->storeManager = $storeManager ?: ObjectManager::getInstance()
+ ->get(StoreManagerInterface::class);
}
/**
* Loading page block
*
* @return \Magento\Backend\Model\View\Result\Redirect|\Magento\Framework\Controller\Result\Raw
+ *
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function execute()
{
$request = $this->getRequest();
+ if ($request->getParam('store_id') !== 'false') {
+ $this->storeManager->setCurrentStore($request->getParam('store_id'));
+ }
try {
$this->_initSession()->_processData();
} catch (\Magento\Framework\Exception\LocalizedException $e) {
diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php
index ae3c1af1e3195..1ac2cfd5828c5 100644
--- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php
+++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php
@@ -30,7 +30,7 @@ class Save extends \Magento\Backend\App\Action implements HttpPostActionInterfac
*
* @see _isAllowed()
*/
- const ADMIN_RESOURCE = 'Magento_Sales::sales_invoice';
+ public const ADMIN_RESOURCE = 'Magento_Sales::sales_invoice';
/**
* @var InvoiceSender
@@ -128,6 +128,7 @@ protected function _prepareShipment($invoice)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ * @codeCoverageIgnore
*/
public function execute()
{
@@ -155,23 +156,19 @@ public function execute()
/** @var \Magento\Sales\Model\Order $order */
$order = $this->_objectManager->create(\Magento\Sales\Model\Order::class)->load($orderId);
if (!$order->getId()) {
- throw new \Magento\Framework\Exception\LocalizedException(__('The order no longer exists.'));
+ throw new LocalizedException(__('The order no longer exists.'));
}
if (!$order->canInvoice()) {
- throw new \Magento\Framework\Exception\LocalizedException(
+ throw new LocalizedException(
__('The order does not allow an invoice to be created.')
);
}
$invoice = $this->invoiceService->prepareInvoice($order, $invoiceItems);
- if (!$invoice) {
- throw new LocalizedException(__("The invoice can't be saved at this time. Please try again later."));
- }
-
if (!$invoice->getTotalQty()) {
- throw new \Magento\Framework\Exception\LocalizedException(
+ throw new LocalizedException(
__("The invoice can't be created without products. Add products and try again.")
);
}
diff --git a/app/code/Magento/Sales/CustomerData/LastOrderedItems.php b/app/code/Magento/Sales/CustomerData/LastOrderedItems.php
index 5319e9d7e328b..91164da3662a6 100644
--- a/app/code/Magento/Sales/CustomerData/LastOrderedItems.php
+++ b/app/code/Magento/Sales/CustomerData/LastOrderedItems.php
@@ -3,62 +3,75 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Sales\CustomerData;
+use Magento\Catalog\Model\Product;
+use Magento\CatalogInventory\Api\StockRegistryInterface;
use Magento\Customer\CustomerData\SectionSourceInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\Customer\Model\Session;
+use Magento\Framework\App\Http\Context;
use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Sales\Model\Order;
+use Magento\Sales\Model\Order\Config;
+use Magento\Sales\Model\Order\Item;
+use Magento\Sales\Model\ResourceModel\Order\Collection;
+use Magento\Sales\Model\ResourceModel\Order\CollectionFactoryInterface;
+use Magento\Store\Model\StoreManagerInterface;
use Psr\Log\LoggerInterface;
/**
* Returns information for "Recently Ordered" widget.
* It contains list of 5 salable products from the last placed order.
* Qty of products to display is limited by LastOrderedItems::SIDEBAR_ORDER_LIMIT constant.
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class LastOrderedItems implements SectionSourceInterface
{
/**
- * Limit of orders in side bar
+ * Limit of orders in sidebar
*/
- const SIDEBAR_ORDER_LIMIT = 5;
+ public const SIDEBAR_ORDER_LIMIT = 5;
/**
- * @var \Magento\Sales\Model\ResourceModel\Order\CollectionFactoryInterface
+ * @var CollectionFactoryInterface
*/
protected $_orderCollectionFactory;
/**
- * @var \Magento\Sales\Model\Order\Config
+ * @var Config
*/
protected $_orderConfig;
/**
- * @var \Magento\Customer\Model\Session
+ * @var Session
*/
protected $_customerSession;
/**
- * @var \Magento\Framework\App\Http\Context
+ * @var Context
*/
protected $httpContext;
/**
- * @var \Magento\Sales\Model\ResourceModel\Order\Collection
+ * @var Collection
*/
protected $orders;
/**
- * @var \Magento\CatalogInventory\Api\StockRegistryInterface
+ * @var StockRegistryInterface
*/
protected $stockRegistry;
/**
- * @var \Magento\Store\Model\StoreManagerInterface
+ * @var StoreManagerInterface
*/
private $_storeManager;
/**
- * @var \Magento\Catalog\Api\ProductRepositoryInterface
+ * @var ProductRepositoryInterface
*/
private $productRepository;
@@ -68,20 +81,20 @@ class LastOrderedItems implements SectionSourceInterface
private $logger;
/**
- * @param \Magento\Sales\Model\ResourceModel\Order\CollectionFactoryInterface $orderCollectionFactory
- * @param \Magento\Sales\Model\Order\Config $orderConfig
- * @param \Magento\Customer\Model\Session $customerSession
- * @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
+ * @param CollectionFactoryInterface $orderCollectionFactory
+ * @param Config $orderConfig
+ * @param Session $customerSession
+ * @param StockRegistryInterface $stockRegistry
+ * @param StoreManagerInterface $storeManager
* @param ProductRepositoryInterface $productRepository
* @param LoggerInterface $logger
*/
public function __construct(
- \Magento\Sales\Model\ResourceModel\Order\CollectionFactoryInterface $orderCollectionFactory,
- \Magento\Sales\Model\Order\Config $orderConfig,
- \Magento\Customer\Model\Session $customerSession,
- \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
+ CollectionFactoryInterface $orderCollectionFactory,
+ Config $orderConfig,
+ Session $customerSession,
+ StockRegistryInterface $stockRegistry,
+ StoreManagerInterface $storeManager,
ProductRepositoryInterface $productRepository,
LoggerInterface $logger
) {
@@ -99,7 +112,7 @@ public function __construct(
*
* @return void
*/
- protected function initOrders()
+ protected function initOrders(): void
{
$customerId = $this->_customerSession->getCustomerId();
@@ -116,8 +129,9 @@ protected function initOrders()
* Get list of last ordered products
*
* @return array
+ * @throws NoSuchEntityException
*/
- protected function getItems()
+ protected function getItems(): array
{
$items = [];
$order = $this->getLastOrder();
@@ -125,9 +139,9 @@ protected function getItems()
if ($order) {
$website = $this->_storeManager->getStore()->getWebsiteId();
- /** @var \Magento\Sales\Model\Order\Item $item */
+ /** @var Item $item */
foreach ($order->getParentItemsRandomCollection($limit) as $item) {
- /** @var \Magento\Catalog\Model\Product $product */
+ /** @var Product $product */
try {
$product = $this->productRepository->getById(
$item->getProductId(),
@@ -145,6 +159,7 @@ protected function getItems()
'name' => $item->getName(),
'url' => $url,
'is_saleable' => $this->isItemAvailableForReorder($item),
+ 'product_id' => $item->getProductId(),
];
}
}
@@ -156,10 +171,10 @@ protected function getItems()
/**
* Check item product availability for reorder
*
- * @param \Magento\Sales\Model\Order\Item $orderItem
+ * @param Item $orderItem
* @return boolean
*/
- protected function isItemAvailableForReorder(\Magento\Sales\Model\Order\Item $orderItem)
+ protected function isItemAvailableForReorder(Item $orderItem)
{
try {
$stockItem = $this->stockRegistry->getStockItem(
@@ -175,7 +190,7 @@ protected function isItemAvailableForReorder(\Magento\Sales\Model\Order\Item $or
/**
* Last order getter
*
- * @return \Magento\Sales\Model\Order|void
+ * @return Order|void
*/
protected function getLastOrder()
{
@@ -190,7 +205,7 @@ protected function getLastOrder()
/**
* @inheritdoc
*/
- public function getSectionData()
+ public function getSectionData(): array
{
return ['items' => $this->getItems()];
}
diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php
index c566147ede35c..154ee6e845bc9 100644
--- a/app/code/Magento/Sales/Model/AdminOrder/Create.php
+++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php
@@ -968,7 +968,9 @@ public function applySidebarData($data)
$item = $this->getCustomerCart()->getItemById($itemId);
if ($item) {
$this->moveQuoteItem($item, 'order', $qty);
- $this->removeItem($itemId, 'cart');
+ $transferredItems = $this->_session->getTransferredItems() ?? [];
+ $transferredItems['cart'][] = $itemId;
+ $this->_session->setTransferredItems($transferredItems) ;
}
}
}
@@ -982,7 +984,9 @@ public function applySidebarData($data)
);
if ($item->getId()) {
$this->addProduct($item->getProduct(), $item->getBuyRequest()->toArray());
- $this->removeItem($itemId, 'wishlist');
+ $transferredItems = $this->_session->getTransferredItems() ?? [];
+ $transferredItems['wishlist'][] = $itemId;
+ $this->_session->setTransferredItems($transferredItems) ;
}
}
}
@@ -1175,6 +1179,7 @@ public function updateQuoteItems($items)
* @throws \Magento\Framework\Exception\LocalizedException
*
* @deprecated 101.0.0
+ * @see void
*/
protected function _parseOptions(\Magento\Quote\Model\Quote\Item $item, $additionalOptions)
{
@@ -1245,6 +1250,7 @@ protected function _parseOptions(\Magento\Quote\Model\Quote\Item $item, $additio
* @return $this
*
* @deprecated 101.0.0
+ * @see void
*/
protected function _assignOptionsToItem(\Magento\Quote\Model\Quote\Item $item, $options)
{
@@ -1484,8 +1490,12 @@ public function setShippingAsBilling($flag)
$tmpAddress->unsAddressId()->unsAddressType();
$data = $tmpAddress->getData();
$data['save_in_address_book'] = 0;
+ $shippingAddressTmp = $this->getShippingAddress()->getData();
// Do not duplicate address (billing address will do saving too)
$this->getShippingAddress()->addData($data);
+ if (array_key_exists('weight', $shippingAddressTmp) && !empty($shippingAddressTmp['weight'])) {
+ $this->getShippingAddress()->setWeight($shippingAddressTmp['weight']);
+ }
}
$this->getShippingAddress()->setSameAsBilling($flag);
$this->setRecollect(true);
@@ -1980,6 +1990,7 @@ public function createOrder()
$this->_prepareCustomer();
$this->_validate();
$quote = $this->getQuote();
+
$this->_prepareQuoteItems();
$orderData = [];
@@ -1999,7 +2010,6 @@ public function createOrder()
$quote->setReservedOrderId($orderData['increment_id']);
}
$order = $this->quoteManagement->submit($quote, $orderData);
-
if ($this->getSession()->getOrder()->getId()) {
$oldOrder = $this->getSession()->getOrder();
$oldOrder->setRelationChildId($order->getId());
@@ -2008,15 +2018,39 @@ public function createOrder()
$this->orderManagement->cancel($oldOrder->getEntityId());
$order->save();
}
- if ($this->getSendConfirmation()) {
+
+ if ($this->getSendConfirmation() && !$order->getEmailSent()) {
$this->emailSender->send($order);
}
$this->_eventManager->dispatch('checkout_submit_all_after', ['order' => $order, 'quote' => $quote]);
+ $this->removeTransferredItems();
+
return $order;
}
+ /**
+ * Remove items that were transferred into shopping cart from their original sources (cart, wishlist, ...)
+ *
+ * @return void
+ */
+ private function removeTransferredItems(): void
+ {
+ try {
+ if (is_array($this->getSession()->getTransferredItems())) {
+ foreach ($this->getSession()->getTransferredItems() as $from => $itemIds) {
+ foreach ($itemIds as $itemId) {
+ $this->removeItem($itemId, $from);
+ }
+ }
+ $this->recollectCart();
+ }
+ } catch (\Throwable $exception) {
+ $this->_logger->error($exception);
+ }
+ }
+
/**
* Validate quote data before order creation
*
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 881c2ae25537d..1bb796565ebea 100644
--- a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Shipping.php
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Shipping.php
@@ -6,6 +6,8 @@
namespace Magento\Sales\Model\Order\Creditmemo\Total;
use Magento\Framework\Pricing\PriceCurrencyInterface;
+use Magento\Tax\Model\Calculation as TaxCalculation;
+use Magento\Sales\Model\Order;
/**
* Order creditmemo shipping total calculation model
@@ -56,12 +58,9 @@ public function collect(\Magento\Sales\Model\Order\Creditmemo $creditmemo)
// amounts including tax
$orderShippingInclTax = $order->getShippingInclTax();
$orderBaseShippingInclTax = $order->getBaseShippingInclTax();
- $allowedTaxAmount = $order->getShippingTaxAmount() - $order->getShippingTaxRefunded();
- $allowedAmountInclTax = $allowedAmount + $allowedTaxAmount;
- $baseAllowedAmountInclTax = $orderBaseShippingInclTax
- - $order->getBaseShippingRefunded()
- - $order->getBaseShippingTaxRefunded();
- $baseAllowedAmountInclTax = max($baseAllowedAmountInclTax, 0);
+ $allowedAmountInclTax =$this->getAllowedAmountInclTax($order);
+ $baseAllowedAmountInclTax = $this->getBaseAllowedAmountInclTax($order);
+
// Check if the desired shipping amount to refund was specified (from invoice or another source).
if ($creditmemo->hasBaseShippingAmount()) {
// For the conditional logic, we will either use amounts that always include tax -OR- never include tax.
@@ -114,6 +113,61 @@ public function collect(\Magento\Sales\Model\Order\Creditmemo $creditmemo)
return $this;
}
+ /**
+ * Checks if shipping provided incl tax, tax applied after discount, and discount applied on shipping excl tax
+ *
+ * @param Order $order
+ * @return bool
+ */
+ private function isShippingIncludeTaxWithTaxAfterDiscount(Order $order): bool
+ {
+ $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 Order $order
+ * @return float
+ */
+ private function getAllowedAmountInclTax(Order $order): float
+ {
+ if ($this->isShippingIncludeTaxWithTaxAfterDiscount($order)) {
+ $result = $order->getShippingInclTax();
+ foreach ($order->getCreditmemosCollection() as $creditmemo) {
+ $result -= $creditmemo->getShippingInclTax();
+ }
+ } else {
+ $result = ($order->getShippingAmount() - $order->getShippingRefunded()) +
+ ($order->getShippingTaxAmount() - $order->getShippingTaxRefunded());
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get base allowed shipping amount to refund based on tax settings
+ *
+ * @param \Magento\Sales\Model\Order $order
+ * @return float
+ */
+ private function getBaseAllowedAmountInclTax(\Magento\Sales\Model\Order $order): float
+ {
+ $result = $order->getBaseShippingInclTax();
+ if ($this->isShippingIncludeTaxWithTaxAfterDiscount($order)) {
+ foreach ($order->getCreditmemosCollection() as $creditmemo) {
+ $result -= $creditmemo->getBaseShippingInclTax();
+ }
+ } else {
+ $result -= $order->getBaseShippingRefunded() + $order->getBaseShippingTaxRefunded();
+ }
+
+ return max($result, 0);
+ }
+
/**
* Returns whether the user specified a shipping amount that already includes tax
*
@@ -132,6 +186,7 @@ private function isSuppliedShippingAmountInclTax($order)
* @return \Magento\Tax\Model\Config
*
* @deprecated 100.1.0
+ * @see \Magento\Tax\Model\Config
*/
private function getTaxConfig()
{
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 faed11c4f718e..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,10 +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\Calculation as TaxCalculation;
+use Magento\Tax\Model\Config as TaxConfig;
/**
* Collects credit memo taxes.
@@ -20,18 +23,28 @@ class Tax extends AbstractTotal
*/
private $resourceInvoice;
+ /**
+ * Tax config from Tax model
+ *
+ * @var TaxConfig
+ */
+ private $taxConfig;
+
/**
* @param ResourceInvoice $resourceInvoice
* @param array $data
+ * @param TaxConfig|null $taxConfig
*/
- public function __construct(ResourceInvoice $resourceInvoice, array $data = [])
+ public function __construct(ResourceInvoice $resourceInvoice, array $data = [], ?TaxConfig $taxConfig = null)
{
$this->resourceInvoice = $resourceInvoice;
+ $this->taxConfig = $taxConfig ?: ObjectManager::getInstance()->get(TaxConfig::class);
parent::__construct($data);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
@@ -104,7 +117,9 @@ public function collect(Creditmemo $creditmemo)
$baseShippingTaxAmount = $creditmemo->roundPrice($baseShippingTaxAmount, 'base');
$totalDiscountTaxCompensation = $creditmemo->roundPrice($totalDiscountTaxCompensation);
$baseTotalDiscountTaxCompensation = $creditmemo->roundPrice($baseTotalDiscountTaxCompensation, 'base');
- if ($taxFactor < 1 && $invoice->getShippingTaxAmount() > 0) {
+ if ($taxFactor < 1 && $invoice->getShippingTaxAmount() > 0 ||
+ ($order->getShippingDiscountAmount() >= $order->getShippingAmount())
+ ) {
$isPartialShippingRefunded = true;
}
$totalTax += $shippingTaxAmount;
@@ -119,7 +134,8 @@ public function collect(Creditmemo $creditmemo)
$baseShippingDiscountTaxCompensationAmount = 0;
$shippingDelta = $baseOrderShippingAmount - $baseOrderShippingRefundedAmount;
- if ($shippingDelta > $creditmemo->getBaseShippingAmount()) {
+ if ($shippingDelta > $creditmemo->getBaseShippingAmount() ||
+ $this->isShippingIncludeTaxWithTaxAfterDiscount($order->getStoreId())) {
$part = $creditmemo->getShippingAmount() / $orderShippingAmount;
$basePart = $creditmemo->getBaseShippingAmount() / $baseOrderShippingAmount;
$shippingTaxAmount = $order->getShippingTaxAmount() * $part;
@@ -136,7 +152,9 @@ public function collect(Creditmemo $creditmemo)
$baseShippingDiscountTaxCompensationAmount,
'base'
);
- if ($part < 1 && $order->getShippingTaxAmount() > 0) {
+ if ($part < 1 && ($order->getShippingTaxAmount() > 0 ||
+ ($order->getShippingDiscountAmount() >= $order->getShippingAmount()))
+ ) {
$isPartialShippingRefunded = true;
}
} elseif ($shippingDelta == $creditmemo->getBaseShippingAmount()) {
@@ -187,7 +205,20 @@ public function collect(Creditmemo $creditmemo)
$creditmemo->getBaseGrandTotal() + $baseTotalTax + $baseTotalDiscountTaxCompensation
);
return $this;
+ }
+ /**
+ * Checks if shipping provided incl tax, tax applied after discount, and discount applied on shipping excl tax
+ *
+ * @param int|null $storeId
+ * @return bool
+ */
+ private function isShippingIncludeTaxWithTaxAfterDiscount(?int $storeId): bool
+ {
+ $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/Model/Order/Creditmemo/Validation/QuantityValidator.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/QuantityValidator.php
index 0a762235a975e..939b892ae8c47 100644
--- a/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/QuantityValidator.php
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Validation/QuantityValidator.php
@@ -88,7 +88,7 @@ public function validate($entity)
}
}
- if ($entity->getGrandTotal() <= 0) {
+ if (!$entity->isValidGrandTotal()) {
$messages[] = __('The credit memo\'s total must be positive.');
} elseif ($totalQuantity < 0 && !$this->canRefundShipping($order)) {
$messages[] = __('You can\'t create a creditmemo without products.');
diff --git a/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php b/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php
index e83dc6b76242a..331b1d760f9cc 100644
--- a/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php
+++ b/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php
@@ -34,6 +34,7 @@ class CreditmemoFactory
/**
* @var \Magento\Framework\Unserialize\Unserialize
* @deprecated 101.0.0
+ * @see \Magento\Framework\Unserialize\Unserialize
*/
protected $unserialize;
@@ -230,10 +231,10 @@ protected function initData($creditmemo, $data)
* Calculate product options.
*
* @param Item $orderItem
- * @param int $parentQty
- * @return int
+ * @param float $parentQty
+ * @return float
*/
- private function calculateProductOptions(Item $orderItem, int $parentQty): int
+ private function calculateProductOptions(Item $orderItem, float $parentQty): float
{
$qty = $parentQty;
$productOptions = $orderItem->getProductOptions();
diff --git a/app/code/Magento/Sales/Model/Order/Invoice/Plugin/AddressUpdate.php b/app/code/Magento/Sales/Model/Order/Invoice/Plugin/AddressUpdate.php
index 980cee0a11ba8..d28d6c9116152 100644
--- a/app/code/Magento/Sales/Model/Order/Invoice/Plugin/AddressUpdate.php
+++ b/app/code/Magento/Sales/Model/Order/Invoice/Plugin/AddressUpdate.php
@@ -18,25 +18,38 @@ class AddressUpdate
*/
private $attribute;
+ /**
+ * Global configuration storage.
+ *
+ * @var \Magento\Framework\App\Config\ScopeConfigInterface
+ */
+ private $globalConfig;
+
/**
* AddressUpdate constructor.
* @param \Magento\Sales\Model\ResourceModel\GridPool $gridPool
* @param \Magento\Sales\Model\ResourceModel\Attribute $attribute
+ * @param \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig
*/
public function __construct(
\Magento\Sales\Model\ResourceModel\GridPool $gridPool,
- \Magento\Sales\Model\ResourceModel\Attribute $attribute
+ \Magento\Sales\Model\ResourceModel\Attribute $attribute,
+ \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig
) {
$this->gridPool = $gridPool;
$this->attribute = $attribute;
+ $this->globalConfig = $globalConfig;
}
/**
+ * Attach addresses to invoices
+ *
* @param \Magento\Sales\Model\ResourceModel\Order\Handler\Address $subject
* @param \Magento\Sales\Model\ResourceModel\Order\Handler\Address $result
* @param \Magento\Sales\Model\Order $order
* @return void
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function afterProcess(
\Magento\Sales\Model\ResourceModel\Order\Handler\Address $subject,
@@ -68,8 +81,7 @@ public function afterProcess(
$this->attribute->saveAttribute($invoice, $invoiceAttributesForSave);
}
}
-
- if ($orderInvoiceHasChanges) {
+ if ($orderInvoiceHasChanges && !$this->globalConfig->getValue('dev/grid/async_indexing')) {
$this->gridPool->refreshByOrderId($order->getId());
}
}
diff --git a/app/code/Magento/Sales/Model/Order/Invoice/Validation/CanRefund.php b/app/code/Magento/Sales/Model/Order/Invoice/Validation/CanRefund.php
index d0028d78ae773..f73ba1b8c49c4 100644
--- a/app/code/Magento/Sales/Model/Order/Invoice/Validation/CanRefund.php
+++ b/app/code/Magento/Sales/Model/Order/Invoice/Validation/CanRefund.php
@@ -12,10 +12,9 @@
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Order\Invoice;
use Magento\Sales\Model\ValidatorInterface;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\ObjectManager;
-/**
- * Class CanRefund
- */
class CanRefund implements ValidatorInterface
{
/**
@@ -28,18 +27,26 @@ class CanRefund implements ValidatorInterface
*/
private $orderRepository;
+ /**
+ * @var ScopeConfigInterface
+ */
+ private $scopeConfig;
+
/**
* CanRefund constructor.
*
* @param OrderPaymentRepositoryInterface $paymentRepository
* @param OrderRepositoryInterface $orderRepository
+ * @param ScopeConfigInterface|null $scopeConfig
*/
public function __construct(
OrderPaymentRepositoryInterface $paymentRepository,
- OrderRepositoryInterface $orderRepository
+ OrderRepositoryInterface $orderRepository,
+ ?ScopeConfigInterface $scopeConfig = null
) {
$this->paymentRepository = $paymentRepository;
$this->orderRepository = $orderRepository;
+ $this->scopeConfig = $scopeConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class);
}
/**
@@ -58,6 +65,8 @@ public function validate($entity)
}
/**
+ * Validate if a refund is possible for the payment method
+ *
* @param InvoiceInterface $invoice
* @return bool
*/
@@ -69,19 +78,32 @@ private function isPaymentAllowRefund(InvoiceInterface $invoice)
return false;
}
$method = $payment->getMethodInstance();
- return $this->canPartialRefund($method, $payment) || $this->canFullRefund($invoice, $method);
+ if (!$method instanceof \Magento\Payment\Model\Method\Free) {
+ return $this->canPartialRefund($method, $payment) || $this->canFullRefund($invoice, $method);
+ }
+ return true;
}
/**
+ * Validate if available grand total is enough to be refunded
+ *
* @param InvoiceInterface $entity
* @return bool
*/
private function isGrandTotalEnoughToRefund(InvoiceInterface $entity)
{
- return abs($entity->getBaseGrandTotal() - $entity->getBaseTotalRefunded()) >= .0001;
+ $isAllowedZeroGrandTotal = $this->scopeConfig->getValue(
+ 'sales/zerograndtotal_creditmemo/allow_zero_grandtotal',
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+ );
+
+ return abs($entity->getBaseGrandTotal() - $entity->getBaseTotalRefunded()) >= .0001 ||
+ $isAllowedZeroGrandTotal;
}
/**
+ * Validate if partial refund is possible
+ *
* @param MethodInterface $method
* @param InfoInterface $payment
* @return bool
@@ -94,6 +116,8 @@ private function canPartialRefund(MethodInterface $method, InfoInterface $paymen
}
/**
+ * Validate if full refund is possible
+ *
* @param InvoiceInterface $invoice
* @param MethodInterface $method
* @return bool
diff --git a/app/code/Magento/Sales/Model/Order/ShipmentFactory.php b/app/code/Magento/Sales/Model/Order/ShipmentFactory.php
index aded09b889af9..cf8da654bbd88 100644
--- a/app/code/Magento/Sales/Model/Order/ShipmentFactory.php
+++ b/app/code/Magento/Sales/Model/Order/ShipmentFactory.php
@@ -38,7 +38,7 @@ class ShipmentFactory
protected $instanceName;
/**
- * Serializer
+ * Serializer data
*
* @var Json
*/
@@ -165,7 +165,7 @@ private function validateItem(\Magento\Sales\Model\Order\Item $orderItem, array
// Remove from shipment items without qty or with qty=0
if (!$orderItem->isDummy(true)
- && (!isset($items[$orderItem->getId()]) || (int) $items[$orderItem->getId()] <= 0)
+ && (!isset($items[$orderItem->getId()]) || (float) $items[$orderItem->getId()] <= 0)
) {
return false;
}
diff --git a/app/code/Magento/Sales/Model/Order/Validation/CanRefund.php b/app/code/Magento/Sales/Model/Order/Validation/CanRefund.php
index 0a10e27986133..4e84234fa8a6b 100644
--- a/app/code/Magento/Sales/Model/Order/Validation/CanRefund.php
+++ b/app/code/Magento/Sales/Model/Order/Validation/CanRefund.php
@@ -5,14 +5,13 @@
*/
namespace Magento\Sales\Model\Order\Validation;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Pricing\PriceCurrencyInterface;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\ValidatorInterface;
-/**
- * Class CanRefund
- */
class CanRefund implements ValidatorInterface
{
/**
@@ -20,14 +19,23 @@ class CanRefund implements ValidatorInterface
*/
private $priceCurrency;
+ /**
+ * @var ScopeConfigInterface
+ */
+ private $scopeConfig;
+
/**
* CanRefund constructor.
*
* @param PriceCurrencyInterface $priceCurrency
+ * @param ScopeConfigInterface|null $scopeConfig
*/
- public function __construct(PriceCurrencyInterface $priceCurrency)
- {
+ public function __construct(
+ PriceCurrencyInterface $priceCurrency,
+ ?ScopeConfigInterface $scopeConfig = null
+ ) {
$this->priceCurrency = $priceCurrency;
+ $this->scopeConfig = $scopeConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class);
}
/**
@@ -62,6 +70,13 @@ public function validate($entity)
*/
private function isTotalPaidEnoughForRefund(OrderInterface $order)
{
- return !abs($this->priceCurrency->round($order->getTotalPaid()) - $order->getTotalRefunded()) < .0001;
+ $isAllowedZeroGrandTotal = $this->scopeConfig->getValue(
+ 'sales/zerograndtotal_creditmemo/allow_zero_grandtotal',
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+ );
+
+ return !abs($this->priceCurrency->round($order->getTotalPaid()) - $order->getTotalRefunded()) < .0001 ||
+ $order->getTotalPaid() == 0 &&
+ $isAllowedZeroGrandTotal;
}
}
diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Customer/Collection.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Customer/Collection.php
index caf768999b4be..29f5b21368baa 100644
--- a/app/code/Magento/Sales/Model/ResourceModel/Order/Customer/Collection.php
+++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Customer/Collection.php
@@ -15,6 +15,7 @@
use Magento\Framework\DataObject\Copy\Config;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\Event\ManagerInterface;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot;
use Magento\Framework\Validator\UniversalFactory;
use Magento\Store\Model\StoreManagerInterface;
@@ -154,4 +155,36 @@ protected function beforeAddLoadedItem(DataObject $item): DataObject
return parent::beforeAddLoadedItem($item);
}
+
+ /**
+ * @inheritDoc
+ *
+ * @throws LocalizedException
+ */
+ public function addFieldToFilter($field, $condition = null)
+ {
+ if ($field === 'store_name') {
+ $this->joinField(
+ 'store_name',
+ 'store',
+ 'name',
+ 'store_id=store_id',
+ null,
+ 'left'
+ );
+ }
+
+ if ($field === 'website_name') {
+ $this->joinField(
+ 'website_name',
+ 'store_website',
+ 'name',
+ 'website_id=website_id',
+ null,
+ 'left'
+ );
+ }
+
+ return parent::addFieldToFilter($field, $condition);
+ }
}
diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php
index 47395b17afee8..51c45ed5e5a0d 100644
--- a/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php
+++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php
@@ -38,11 +38,64 @@ public function check(Order $order)
) {
$order->setState(Order::STATE_CLOSED)
->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_CLOSED));
- } elseif ($currentState === Order::STATE_PROCESSING && !$order->canShip()) {
+ } elseif ($currentState === Order::STATE_PROCESSING
+ && (!$order->canShip() || $this->isPartiallyRefundedOrderShipped($order))
+ ) {
$order->setState(Order::STATE_COMPLETE)
->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_COMPLETE));
+ } elseif ($order->getIsVirtual() && $order->getStatus() === Order::STATE_CLOSED) {
+ $order->setState(Order::STATE_CLOSED);
}
}
return $this;
}
+
+ /**
+ * Check if all items are remaining items after partially refunded are shipped
+ *
+ * @param Order $order
+ * @return bool
+ */
+ public function isPartiallyRefundedOrderShipped(Order $order): bool
+ {
+ $isPartiallyRefundedOrderShipped = false;
+ if ($this->getShippedItems($order) > 0
+ && $order->getTotalQtyOrdered() <= $this->getRefundedItems($order) + $this->getShippedItems($order)) {
+ $isPartiallyRefundedOrderShipped = true;
+ }
+
+ return $isPartiallyRefundedOrderShipped;
+ }
+
+ /**
+ * Get all refunded items number
+ *
+ * @param Order $order
+ * @return int
+ */
+ private function getRefundedItems(Order $order): int
+ {
+ $numOfRefundedItems = 0;
+ foreach ($order->getAllItems() as $item) {
+ if ($item->getProductType() == 'simple') {
+ $numOfRefundedItems += (int)$item->getQtyRefunded();
+ }
+ }
+ return $numOfRefundedItems;
+ }
+
+ /**
+ * Get all shipped items number
+ *
+ * @param Order $order
+ * @return int
+ */
+ private function getShippedItems(Order $order): int
+ {
+ $numOfShippedItems = 0;
+ foreach ($order->getAllItems() as $item) {
+ $numOfShippedItems += (int)$item->getQtyShipped();
+ }
+ return $numOfShippedItems;
+ }
}
diff --git a/app/code/Magento/Sales/Model/ResourceModel/Provider/Query/IdListBuilder.php b/app/code/Magento/Sales/Model/ResourceModel/Provider/Query/IdListBuilder.php
index bf624620c1336..65b11e1129b33 100644
--- a/app/code/Magento/Sales/Model/ResourceModel/Provider/Query/IdListBuilder.php
+++ b/app/code/Magento/Sales/Model/ResourceModel/Provider/Query/IdListBuilder.php
@@ -54,6 +54,18 @@ public function addAdditionalGridTable(string $table): IdListBuilder
return $this;
}
+ /**
+ * Reset added additional grid table where entities may already exist.
+ *
+ * @return $this
+ */
+ public function resetAdditionalGridTable(): IdListBuilder
+ {
+ $this->additionalGridTables = [];
+
+ return $this;
+ }
+
/**
* Returns connection.
*
diff --git a/app/code/Magento/Sales/Model/Service/CreditmemoService.php b/app/code/Magento/Sales/Model/Service/CreditmemoService.php
index 6302a71cab9ab..6a9826d0c22fa 100644
--- a/app/code/Magento/Sales/Model/Service/CreditmemoService.php
+++ b/app/code/Magento/Sales/Model/Service/CreditmemoService.php
@@ -3,11 +3,13 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Sales\Model\Service;
/**
* Class CreditmemoService
+ * Provides functionality to work with Creditmemo
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CreditmemoService implements \Magento\Sales\Api\CreditmemoManagementInterface
@@ -192,6 +194,12 @@ protected function validateForRefund(\Magento\Sales\Api\Data\CreditmemoInterface
);
}
+ if (!$creditmemo->getOrderId()) {
+ throw new \Magento\Framework\Exception\NoSuchEntityException(
+ __('We found an invalid order to refund.')
+ );
+ }
+
$baseOrderRefund = $this->priceCurrency->round(
$creditmemo->getOrder()->getBaseTotalRefunded() + $creditmemo->getBaseGrandTotal()
);
@@ -214,6 +222,7 @@ protected function validateForRefund(\Magento\Sales\Api\Data\CreditmemoInterface
*
* @return \Magento\Sales\Model\Order\RefundAdapterInterface
* @deprecated 100.1.3
+ * @see no alternatives
*/
private function getRefundAdapter()
{
@@ -229,6 +238,7 @@ private function getRefundAdapter()
*
* @return \Magento\Framework\App\ResourceConnection|mixed
* @deprecated 100.1.3
+ * @see no alternatives
*/
private function getResource()
{
@@ -244,6 +254,7 @@ private function getResource()
*
* @return \Magento\Sales\Api\OrderRepositoryInterface
* @deprecated 100.1.3
+ * @see no alternatives
*/
private function getOrderRepository()
{
@@ -259,6 +270,7 @@ private function getOrderRepository()
*
* @return \Magento\Sales\Api\InvoiceRepositoryInterface
* @deprecated 100.1.3
+ * @see no alternatives
*/
private function getInvoiceRepository()
{
diff --git a/app/code/Magento/Sales/Test/Fixture/Creditmemo.php b/app/code/Magento/Sales/Test/Fixture/Creditmemo.php
index 11e18adbfe498..7dd4ef9c46cf2 100644
--- a/app/code/Magento/Sales/Test/Fixture/Creditmemo.php
+++ b/app/code/Magento/Sales/Test/Fixture/Creditmemo.php
@@ -62,10 +62,13 @@ public function __construct(
* @param array $data Parameters. Same format as Creditmemo::DEFAULT_DATA.
* Fields structure fields:
* - $data['items']: can be supplied in following formats:
- * - array of arrays [{"sku":"$product1.sku","qty":1}, {"sku":"$product2.sku","qty":1}]
- * - array of SKUs ["$product1.sku", "$product2.sku"]
- * - array of order items IDs ["$item1.id", "$item2.id"]
- * - array of product instances ["$product1", "$product2"]
+ * - array of arrays [{"sku":"$product1.sku$","qty":1}, {"sku":"$product2.sku$","qty":1}]
+ * - array of arrays [{"order_item_id":"$oItem1.sku$","qty":1}, {"order_item_id":"$oItem2.sku$","qty":1}]
+ * - array of arrays [{"product_id":"$product1.id$","qty":1}, {"product_id":"$product2.id$","qty":1}]
+ * - array of arrays [{"quote_item_id":"$qItem1.id$","qty":1}, {"quote_item_id":"$qItem2.id$","qty":1}]
+ * - array of SKUs ["$product1.sku$", "$product2.sku$"]
+ * - array of order items IDs ["$oItem1.id$", "$oItem2.id$"]
+ * - array of product instances ["$product1$", "$product2$"]
*/
public function apply(array $data = []): ?DataObject
{
@@ -100,7 +103,7 @@ private function prepareData(array $data): array
}
/**
- * Prepare creditmemo items
+ * Prepare creditmemo item
*
* @param array $data
* @return array
@@ -110,29 +113,33 @@ private function prepareCreditmemoItems(array $data): array
$creditmemoItems = [];
$order = $this->orderRepository->get($data['order_id']);
$orderItemIdsBySku = [];
+ $orderItemIdsByProductIds = [];
+ $orderItemIdsByQuoteItemIds = [];
foreach ($order->getItems() as $item) {
$orderItemIdsBySku[$item->getSku()] = $item->getItemId();
+ $orderItemIdsByQuoteItemIds[$item->getQuoteItemId()] = $item->getItemId();
+ $orderItemIdsByProductIds[$item->getProductId()] = $item->getItemId();
}
foreach ($data['items'] as $itemToRefund) {
- $qty = 1;
- $orderItemId = 1;
- $sku = null;
+ $creditmemoItem = ['order_item_id' => null, 'qty' => 1];
if (is_numeric($itemToRefund)) {
- $orderItemId = $itemToRefund;
+ $creditmemoItem['order_item_id'] = $itemToRefund;
} elseif (is_string($itemToRefund)) {
- $sku = $itemToRefund;
+ $creditmemoItem['order_item_id'] = $orderItemIdsBySku[$itemToRefund];
} elseif ($itemToRefund instanceof ProductInterface) {
- $sku = $itemToRefund->getSku();
+ $creditmemoItem['order_item_id'] = $orderItemIdsBySku[$itemToRefund->getSku()];
} else {
- $qty = $itemToRefund['qty'] ?? $qty;
- $orderItemId = $itemToRefund['order_item_id'] ?? $qty;
- $sku = $itemToRefund['sku'] ?? $sku;
+ $creditmemoItem = array_intersect($itemToRefund, $creditmemoItem) + $creditmemoItem;
+ if (isset($itemToRefund['sku'])) {
+ $creditmemoItem['order_item_id'] = $orderItemIdsBySku[$itemToRefund['sku']];
+ } elseif (isset($itemToRefund['product_id'])) {
+ $creditmemoItem['order_item_id'] = $orderItemIdsByProductIds[$itemToRefund['product_id']];
+ } elseif (isset($itemToRefund['quote_item_id'])) {
+ $creditmemoItem['order_item_id'] = $orderItemIdsByQuoteItemIds[$itemToRefund['quote_item_id']];
+ }
}
- if (!$orderItemId && $sku) {
- $orderItemId = $orderItemIdsBySku[$sku];
- }
- $creditmemoItems[] = ['order_item_id' => $orderItemId, 'qty' => $qty];
+ $creditmemoItems[] = $creditmemoItem;
}
return $creditmemoItems;
diff --git a/app/code/Magento/Sales/Test/Fixture/Invoice.php b/app/code/Magento/Sales/Test/Fixture/Invoice.php
index 252b638bc274d..82f31540298b9 100644
--- a/app/code/Magento/Sales/Test/Fixture/Invoice.php
+++ b/app/code/Magento/Sales/Test/Fixture/Invoice.php
@@ -62,10 +62,13 @@ public function __construct(
* @param array $data Parameters. Same format as Invoice::DEFAULT_DATA.
* Fields structure fields:
* - $data['items']: can be supplied in following formats:
- * - array of arrays [{"sku":"$product1.sku","qty":1}, {"sku":"$product2.sku","qty":1}]
- * - array of SKUs ["$product1.sku", "$product2.sku"]
- * - array of order items IDs ["$item1.id", "$item2.id"]
- * - array of product instances ["$product1", "$product2"]
+ * - array of arrays [{"sku":"$product1.sku$","qty":1}, {"sku":"$product2.sku$","qty":1}]
+ * - array of arrays [{"order_item_id":"$oItem1.sku$","qty":1}, {"order_item_id":"$oItem2.sku$","qty":1}]
+ * - array of arrays [{"product_id":"$product1.id$","qty":1}, {"product_id":"$product2.id$","qty":1}]
+ * - array of arrays [{"quote_item_id":"$qItem1.id$","qty":1}, {"quote_item_id":"$qItem2.id$","qty":1}]
+ * - array of SKUs ["$product1.sku$", "$product2.sku$"]
+ * - array of order items IDs ["$oItem1.id$", "$oItem2.id$"]
+ * - array of product instances ["$product1$", "$product2$"]
*/
public function apply(array $data = []): ?DataObject
{
@@ -107,34 +110,38 @@ private function prepareData(array $data): array
*/
private function prepareInvoiceItems(array $data): array
{
- $items = [];
+ $invoiceItems = [];
$order = $this->orderRepository->get($data['order_id']);
$orderItemIdsBySku = [];
+ $orderItemIdsByProductIds = [];
+ $orderItemIdsByQuoteItemIds = [];
foreach ($order->getItems() as $item) {
$orderItemIdsBySku[$item->getSku()] = $item->getItemId();
+ $orderItemIdsByQuoteItemIds[$item->getQuoteItemId()] = $item->getItemId();
+ $orderItemIdsByProductIds[$item->getProductId()] = $item->getItemId();
}
foreach ($data['items'] as $itemToInvoice) {
- $qty = 1;
- $orderItemId = 1;
- $sku = null;
+ $invoiceItem = ['order_item_id' => null, 'qty' => 1];
if (is_numeric($itemToInvoice)) {
- $orderItemId = $itemToInvoice;
+ $invoiceItem['order_item_id'] = $itemToInvoice;
} elseif (is_string($itemToInvoice)) {
- $sku = $itemToInvoice;
+ $invoiceItem['order_item_id'] = $orderItemIdsBySku[$itemToInvoice];
} elseif ($itemToInvoice instanceof ProductInterface) {
- $sku = $itemToInvoice->getSku();
+ $invoiceItem['order_item_id'] = $orderItemIdsBySku[$itemToInvoice->getSku()];
} else {
- $qty = $itemToInvoice['qty'] ?? $qty;
- $orderItemId = $itemToInvoice['order_item_id'] ?? $qty;
- $sku = $itemToInvoice['sku'] ?? $sku;
+ $invoiceItem = array_intersect($itemToInvoice, $invoiceItem) + $invoiceItem;
+ if (isset($itemToInvoice['sku'])) {
+ $invoiceItem['order_item_id'] = $orderItemIdsBySku[$itemToInvoice['sku']];
+ } elseif (isset($itemToInvoice['product_id'])) {
+ $invoiceItem['order_item_id'] = $orderItemIdsByProductIds[$itemToInvoice['product_id']];
+ } elseif (isset($itemToInvoice['quote_item_id'])) {
+ $invoiceItem['order_item_id'] = $orderItemIdsByQuoteItemIds[$itemToInvoice['quote_item_id']];
+ }
}
- if (!$orderItemId && $sku) {
- $orderItemId = $orderItemIdsBySku[$sku];
- }
- $items[] = ['order_item_id' => $orderItemId, 'qty' => $qty];
+ $invoiceItems[] = $invoiceItem;
}
- return $items;
+ return $invoiceItems;
}
}
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/Fixture/Shipment.php b/app/code/Magento/Sales/Test/Fixture/Shipment.php
index 7107eb27a96ba..25df6cb62ee12 100644
--- a/app/code/Magento/Sales/Test/Fixture/Shipment.php
+++ b/app/code/Magento/Sales/Test/Fixture/Shipment.php
@@ -64,10 +64,13 @@ public function __construct(
* @param array $data Parameters. Same format as Shipment::DEFAULT_DATA.
* Fields structure fields:
* - $data['items']: can be supplied in following formats:
- * - array of arrays [{"sku":"$product1.sku","qty":1}, {"sku":"$product2.sku","qty":1}]
- * - array of SKUs ["$product1.sku", "$product2.sku"]
- * - array of order items IDs ["$item1.id", "$item2.id"]
- * - array of product instances ["$product1", "$product2"]
+ * - array of arrays [{"sku":"$product1.sku$","qty":1}, {"sku":"$product2.sku$","qty":1}]
+ * - array of arrays [{"order_item_id":"$oItem1.sku$","qty":1}, {"order_item_id":"$oItem2.sku$","qty":1}]
+ * - array of arrays [{"product_id":"$product1.id$","qty":1}, {"product_id":"$product2.id$","qty":1}]
+ * - array of arrays [{"quote_item_id":"$qItem1.id$","qty":1}, {"quote_item_id":"$qItem2.id$","qty":1}]
+ * - array of SKUs ["$product1.sku$", "$product2.sku$"]
+ * - array of order items IDs ["$oItem1.id$", "$oItem2.id$"]
+ * - array of product instances ["$product1$", "$product2$"]
*/
public function apply(array $data = []): ?DataObject
{
@@ -112,29 +115,33 @@ private function prepareShipmentItems(array $data): array
$shipmentItems = [];
$order = $this->orderRepository->get($data['order_id']);
$orderItemIdsBySku = [];
+ $orderItemIdsByProductIds = [];
+ $orderItemIdsByQuoteItemIds = [];
foreach ($order->getItems() as $item) {
$orderItemIdsBySku[$item->getSku()] = $item->getItemId();
+ $orderItemIdsByQuoteItemIds[$item->getQuoteItemId()] = $item->getItemId();
+ $orderItemIdsByProductIds[$item->getProductId()] = $item->getItemId();
}
foreach ($data['items'] as $itemToShip) {
- $qty = 1;
- $orderItemId = 1;
- $sku = null;
+ $shipmentItem = ['order_item_id' => null, 'qty' => 1];
if (is_numeric($itemToShip)) {
- $orderItemId = $itemToShip;
+ $shipmentItem['order_item_id'] = $itemToShip;
} elseif (is_string($itemToShip)) {
- $sku = $itemToShip;
+ $shipmentItem['order_item_id'] = $orderItemIdsBySku[$itemToShip];
} elseif ($itemToShip instanceof ProductInterface) {
- $sku = $itemToShip->getSku();
+ $shipmentItem['order_item_id'] = $orderItemIdsBySku[$itemToShip->getSku()];
} else {
- $qty = $itemToShip['qty'] ?? $qty;
- $orderItemId = $itemToShip['order_item_id'] ?? $qty;
- $sku = $itemToShip['sku'] ?? $sku;
+ $shipmentItem = array_intersect($itemToShip, $shipmentItem) + $shipmentItem;
+ if (isset($itemToShip['sku'])) {
+ $shipmentItem['order_item_id'] = $orderItemIdsBySku[$itemToShip['sku']];
+ } elseif (isset($itemToShip['product_id'])) {
+ $shipmentItem['order_item_id'] = $orderItemIdsByProductIds[$itemToShip['product_id']];
+ } elseif (isset($itemToShip['quote_item_id'])) {
+ $shipmentItem['order_item_id'] = $orderItemIdsByQuoteItemIds[$itemToShip['quote_item_id']];
+ }
}
- if (!$orderItemId && $sku) {
- $orderItemId = $orderItemIdsBySku[$sku];
- }
- $shipmentItems[] = ['order_item_id' => $orderItemId, 'qty' => $qty];
+ $shipmentItems[] = $shipmentItem;
}
return $shipmentItems;
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertInvoiceSingleTaxActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertInvoiceSingleTaxActionGroup.xml
new file mode 100644
index 0000000000000..d4315cdeb2598
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertInvoiceSingleTaxActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ Admin assert invoice single tax in detailed taxes
+
+
+
+
+
+
+ getSingleTax
+ {{expectedSingleTax}}
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertProductByNameInItemsOrderedGridOnCreateOrderPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertProductByNameInItemsOrderedGridOnCreateOrderPageActionGroup.xml
new file mode 100644
index 0000000000000..09d90272fc6f4
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertProductByNameInItemsOrderedGridOnCreateOrderPageActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ Asserts a Product is present in the Items Ordered Grid on the Create Order page
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertProductOnTheOrderViewPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertProductOnTheOrderViewPageActionGroup.xml
new file mode 100644
index 0000000000000..2262dec685b29
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertProductOnTheOrderViewPageActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ Asserts Product Name and subtotal on the Order View Page
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertTotalsOnOrderViewPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertTotalsOnOrderViewPageActionGroup.xml
new file mode 100644
index 0000000000000..9570414790c19
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminAssertTotalsOnOrderViewPageActionGroup.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ Asserts Totals on the Order View page
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminClickConfigureAndAddToOrderForConfigurableProductInWishListSectionOnCreateOrderPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminClickConfigureAndAddToOrderForConfigurableProductInWishListSectionOnCreateOrderPageActionGroup.xml
new file mode 100644
index 0000000000000..cdfc3769d2a96
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminClickConfigureAndAddToOrderForConfigurableProductInWishListSectionOnCreateOrderPageActionGroup.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+ Clicks the "Configure And Add To Order" button for a Configurable Product in the WishList Section on the Create Order Page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminClickOrdersItemOnCustomerEditPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminClickOrdersItemOnCustomerEditPageActionGroup.xml
new file mode 100644
index 0000000000000..271f2acce807b
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminClickOrdersItemOnCustomerEditPageActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ Clicks the "Orders" item on the Customer Edit page
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminClickUpdateItemsAndQuiantitesOnCreateOrderPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminClickUpdateItemsAndQuiantitesOnCreateOrderPageActionGroup.xml
new file mode 100644
index 0000000000000..cafca7d83d3cb
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminClickUpdateItemsAndQuiantitesOnCreateOrderPageActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ Clicks the "Update Items and Quantites" button on Create Order page
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOpenOrderByIdFromOrdersGridOnCustomerEditPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOpenOrderByIdFromOrdersGridOnCustomerEditPageActionGroup.xml
new file mode 100644
index 0000000000000..add9c96b84b9d
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOpenOrderByIdFromOrdersGridOnCustomerEditPageActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ Opens the Order View page from the Order Grid on the Customer Edit page
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSeeProductInWishlistOnCreateOrderPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSeeProductInWishlistOnCreateOrderPageActionGroup.xml
new file mode 100644
index 0000000000000..ab76a45fe517b
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSeeProductInWishlistOnCreateOrderPageActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ Checks if a Product is in the WishList section on the Create Order Page
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSelectAddToOrderCheckboxForSimpleProductInWishListSectionOnCreateOrderPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSelectAddToOrderCheckboxForSimpleProductInWishListSectionOnCreateOrderPageActionGroup.xml
new file mode 100644
index 0000000000000..68418d0aaa5e8
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSelectAddToOrderCheckboxForSimpleProductInWishListSectionOnCreateOrderPageActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ Selects the "Add To Order" checkbox for a Simple Product in the WishList Section on the Create Order Page
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSelectAddToOrderCheckboxInShoppingCartOnCreateOrderPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSelectAddToOrderCheckboxInShoppingCartOnCreateOrderPageActionGroup.xml
new file mode 100644
index 0000000000000..ef110f537fa84
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSelectAddToOrderCheckboxInShoppingCartOnCreateOrderPageActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ Selects the "Add to Order" checkbox for a Product in the "Shopping Cart" section on Create Order page.
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSelectValueFromActionSelectInItemsOrderedGridOnCreateOrderPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSelectValueFromActionSelectInItemsOrderedGridOnCreateOrderPageActionGroup.xml
new file mode 100644
index 0000000000000..7e171af405317
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminSelectValueFromActionSelectInItemsOrderedGridOnCreateOrderPageActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ Selects a value from the "Action" dropdown for specified Product in th Items Ordered Grid on the Create Order Page
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertAdminInvoiceTotalTaxActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertAdminInvoiceTotalTaxActionGroup.xml
new file mode 100644
index 0000000000000..e301e3ec9d1b2
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertAdminInvoiceTotalTaxActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ Admin assert invoice total tax
+
+
+
+
+
+
+ getTotalTax
+ {{expectedTotalTax}}
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/FilterAdminOrderGridByStatusActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/FilterAdminOrderGridByStatusActionGroup.xml
new file mode 100644
index 0000000000000..7e4d8f5e60f49
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/FilterAdminOrderGridByStatusActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ Filters the Admin Orders grid based on the provided Order Status.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Sales/Test/Mftf/Data/AdminMenuData.xml
index 4f6faccbb26d4..3012b01abc8af 100644
--- a/app/code/Magento/Sales/Test/Mftf/Data/AdminMenuData.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Data/AdminMenuData.xml
@@ -8,6 +8,11 @@
+
+ Sales
+ Sales
+ magento-sales-sales
+
Credit Memos
Credit Memos
diff --git a/app/code/Magento/Sales/Test/Mftf/Data/AsyncGridsIndexingConfigData.xml b/app/code/Magento/Sales/Test/Mftf/Data/AsyncGridsIndexingConfigData.xml
new file mode 100644
index 0000000000000..9b11326b290c7
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Data/AsyncGridsIndexingConfigData.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ dev/grid/async_indexing 1
+ dev/grid/async_indexing 0
+
+
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/Mftf/Helper/AdminProductPage.php b/app/code/Magento/Sales/Test/Mftf/Helper/AdminProductPage.php
new file mode 100644
index 0000000000000..dabf808e83301
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Helper/AdminProductPage.php
@@ -0,0 +1,37 @@
+getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver');
+ for ($i = 0; $i < $count; $i++) {
+ $webDriver->checkOption($context);
+ $webDriver->waitForLoadingMaskToDisappear();
+ $webDriver->waitForElementClickable($context);
+ $webDriver->uncheckOption($context);
+ $webDriver->waitForLoadingMaskToDisappear();
+ $webDriver->waitForElementClickable($context);
+ }
+ } catch (Exception $e) {
+ $this->fail($e->getMessage());
+ }
+ }
+}
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceTaxSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceTaxSection.xml
new file mode 100644
index 0000000000000..14fc2fdef4559
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceTaxSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceTotalSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceTotalSection.xml
index 1a78e920d41ba..abeddac6d7f1a 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceTotalSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceTotalSection.xml
@@ -16,5 +16,6 @@
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml
index 653b1d48686e3..752a3489d672d 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml
@@ -24,5 +24,7 @@
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderEditShippingAddressSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderEditShippingAddressSection.xml
new file mode 100644
index 0000000000000..423be5411f2bd
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderEditShippingAddressSection.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml
index 28573b4a68309..bd94e985fe3cf 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml
@@ -36,5 +36,7 @@
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormShippingAddressSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormShippingAddressSection.xml
index 0f1461b121e15..ae1b359113645 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormShippingAddressSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormShippingAddressSection.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
@@ -29,7 +29,6 @@
-
@@ -39,4 +38,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderTotalSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderTotalSection.xml
index 46193018fceeb..58fe442cdee6c 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderTotalSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderTotalSection.xml
@@ -15,5 +15,9 @@
+
+
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/OrdersGridSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/OrdersGridSection.xml
index 6434a4358711d..340448eded2d2 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/OrdersGridSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/OrdersGridSection.xml
@@ -30,5 +30,6 @@
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithZeroSubtotalCheckoutTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithZeroSubtotalCheckoutTest.xml
index 5078807ba3eba..a89d29a54e312 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithZeroSubtotalCheckoutTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithZeroSubtotalCheckoutTest.xml
@@ -20,6 +20,8 @@
+
+
@@ -39,6 +41,8 @@
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCheckingCreditMemoUpdateTotalsTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCheckingCreditMemoUpdateTotalsTest.xml
index b6ac42e7eaea8..94091114baed2 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCheckingCreditMemoUpdateTotalsTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCheckingCreditMemoUpdateTotalsTest.xml
@@ -20,6 +20,8 @@
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCheckingDateAfterChangeFrenchCanadaInterfaceLocaleTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCheckingDateAfterChangeFrenchCanadaInterfaceLocaleTest.xml
index 9e9eb02a5ddf1..e6ed8f0240bfe 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCheckingDateAfterChangeFrenchCanadaInterfaceLocaleTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCheckingDateAfterChangeFrenchCanadaInterfaceLocaleTest.xml
@@ -19,6 +19,9 @@
+
+ Skipped
+
@@ -81,7 +84,10 @@
-
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoForOrderWithCashOnDeliveryTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoForOrderWithCashOnDeliveryTest.xml
index 4e9c9a0257e33..ee11a140500f8 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoForOrderWithCashOnDeliveryTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoForOrderWithCashOnDeliveryTest.xml
@@ -19,8 +19,8 @@
+
-
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderForCustomerWithTwoAddressesTaxableAndNonTaxableTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderForCustomerWithTwoAddressesTaxableAndNonTaxableTest.xml
index 2f89573b00101..6475688daa46c 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderForCustomerWithTwoAddressesTaxableAndNonTaxableTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderForCustomerWithTwoAddressesTaxableAndNonTaxableTest.xml
@@ -74,6 +74,8 @@
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderSameAsBillingAddressCheckboxTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderSameAsBillingAddressCheckboxTest.xml
new file mode 100644
index 0000000000000..476eb161936fd
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderSameAsBillingAddressCheckboxTest.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{AdminOrderFormShippingAddressSection.SameAsBilling}}
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithLimitedNumberOfProductsInGridTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithLimitedNumberOfProductsInGridTest.xml
new file mode 100644
index 0000000000000..ac1d1dd841ef7
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithLimitedNumberOfProductsInGridTest.xml
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml
index 63adb3eb814bd..cb2d33420fb0c 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml
@@ -18,6 +18,8 @@
+
+
@@ -28,6 +30,8 @@
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfDiscountCouponReducesOrderTotalBelowThresholdTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfDiscountCouponReducesOrderTotalBelowThresholdTest.xml
new file mode 100644
index 0000000000000..3480fdc4dc9e6
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfDiscountCouponReducesOrderTotalBelowThresholdTest.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 102
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminInvoiceOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminInvoiceOrderTest.xml
index c33dd04aa1b2c..c9af87cdb1150 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminInvoiceOrderTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminInvoiceOrderTest.xml
@@ -19,6 +19,7 @@
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersCancelClosedAndProcessingTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersCancelClosedAndProcessingTest.xml
index 2671cd6989c51..853fa5822f799 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersCancelClosedAndProcessingTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersCancelClosedAndProcessingTest.xml
@@ -19,6 +19,7 @@
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersHoldOnProcessingAndPendingTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersHoldOnProcessingAndPendingTest.xml
index 97970cb5deed6..23e71dcb03a0e 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersHoldOnProcessingAndPendingTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersHoldOnProcessingAndPendingTest.xml
@@ -19,6 +19,7 @@
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersOnHoldAllPaginatorTwoPerPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersOnHoldAllPaginatorTwoPerPageTest.xml
new file mode 100644
index 0000000000000..49511a62e258a
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersOnHoldAllPaginatorTwoPerPageTest.xml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $orderNumber1
+
+
+
+
+
+
+
+
+
+
+ $orderNumber2
+
+
+
+
+
+
+
+
+
+
+ $orderNumber3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersReleasePendingOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersReleasePendingOrderTest.xml
index 58e2443c5d5c0..362b7c9794cc5 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersReleasePendingOrderTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminMassOrdersReleasePendingOrderTest.xml
@@ -20,7 +20,7 @@
-
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminOrdersMultiSelectActionAppliedToUncheckedNewlyCreatedOrdersTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminOrdersMultiSelectActionAppliedToUncheckedNewlyCreatedOrdersTest.xml
new file mode 100644
index 0000000000000..2299a12546849
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminOrdersMultiSelectActionAppliedToUncheckedNewlyCreatedOrdersTest.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml
index d1067ba39c6c9..a517d310f6a5b 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml
@@ -18,12 +18,16 @@
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CloseOrderInCaseItRefundedPartiallyTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CloseOrderInCaseItRefundedPartiallyTest.xml
new file mode 100644
index 0000000000000..337ef3889c12b
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/CloseOrderInCaseItRefundedPartiallyTest.xml
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml
index 22cc18d6ad647..1ae0388b206b4 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml
@@ -23,10 +23,8 @@
-
-
10.00
@@ -34,43 +32,36 @@
20.00
-
-
-
-
-
30.00
-
-
@@ -91,7 +82,6 @@
-
@@ -101,118 +91,154 @@
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/EndToEndB2CAdminTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/EndToEndB2CAdminTest.xml
index 80426cdb8c268..31e833f0eab7a 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/EndToEndB2CAdminTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/EndToEndB2CAdminTest.xml
@@ -226,13 +226,13 @@
-
-
+
+
-
-
+
+
@@ -264,7 +264,7 @@
-
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml
index f66ed3498c7a5..b5dfa255436a7 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml
@@ -157,9 +157,25 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml
index ba6e5f6c33b3b..2d4dd3220f777 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml
@@ -109,7 +109,15 @@
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedSimpleProductOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedSimpleProductOnOrderPageTest.xml
index 16e0f8ec87d96..87a6dbf8fdff0 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedSimpleProductOnOrderPageTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedSimpleProductOnOrderPageTest.xml
@@ -47,7 +47,7 @@
-
+
@@ -55,20 +55,23 @@
-
+
-
-
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedBundleFixedProductOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedBundleFixedProductOnOrderPageTest.xml
index a8caf2d4a5adc..004d2b72f8b30 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedBundleFixedProductOnOrderPageTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedBundleFixedProductOnOrderPageTest.xml
@@ -78,7 +78,7 @@
-
+
@@ -92,7 +92,7 @@
-
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveSimpleProductsInComparedOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveSimpleProductsInComparedOnOrderPageTest.xml
index e00f8ac53eff6..009037da2b50a 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/MoveSimpleProductsInComparedOnOrderPageTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveSimpleProductsInComparedOnOrderPageTest.xml
@@ -87,9 +87,25 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontOrderCommentWithHTMLTagsDisplayTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontOrderCommentWithHTMLTagsDisplayTest.xml
index 2175b6c796e97..1e97703acbe00 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontOrderCommentWithHTMLTagsDisplayTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontOrderCommentWithHTMLTagsDisplayTest.xml
@@ -20,6 +20,7 @@
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontReorderAsGuestCustomerTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontReorderAsGuestCustomerTest.xml
index 3cfd2bdadf059..474dc6f09ff02 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontReorderAsGuestCustomerTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontReorderAsGuestCustomerTest.xml
@@ -17,10 +17,12 @@
+
+
-
+
@@ -35,6 +37,8 @@
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifyOrderShipmentForDecimalQuantityTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifyOrderShipmentForDecimalQuantityTest.xml
index 0c617bce9e7bd..6f35b062aad9c 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifyOrderShipmentForDecimalQuantityTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifyOrderShipmentForDecimalQuantityTest.xml
@@ -20,6 +20,7 @@
+
diff --git a/app/code/Magento/Sales/Test/Unit/CustomerData/LastOrderedItemsTest.php b/app/code/Magento/Sales/Test/Unit/CustomerData/LastOrderedItemsTest.php
index a8950dda0e66e..24777a890201a 100644
--- a/app/code/Magento/Sales/Test/Unit/CustomerData/LastOrderedItemsTest.php
+++ b/app/code/Magento/Sales/Test/Unit/CustomerData/LastOrderedItemsTest.php
@@ -132,13 +132,15 @@ public function testGetSectionData(): void
'id' => 1,
'name' => 'Product Name 1',
'url' => 'http://example.com',
- 'is_saleable' => true
+ 'is_saleable' => true,
+ 'product_id' => 1
];
$expectedItem2 = [
'id' => 2,
'name' => 'Product Name 2',
'url' => null,
- 'is_saleable' => true
+ 'is_saleable' => true,
+ 'product_id' => 2
];
$productIdVisible = 1;
$productIdNotVisible = 2;
@@ -175,12 +177,12 @@ public function testGetSectionData(): void
$productNotVisible->expects($this->never())->method('getProductUrl');
$productNotVisible->expects($this->once())->method('getWebsiteIds')->willReturn([1, 4]);
$productNotVisible->expects($this->once())->method('getId')->willReturn($productIdNotVisible);
- $itemWithVisibleProduct->expects($this->once())->method('getProductId')->willReturn($productIdVisible);
+ $itemWithVisibleProduct->expects($this->any())->method('getProductId')->willReturn($productIdVisible);
$itemWithVisibleProduct->expects($this->once())->method('getProduct')->willReturn($productVisible);
$itemWithVisibleProduct->expects($this->once())->method('getId')->willReturn($expectedItem1['id']);
$itemWithVisibleProduct->expects($this->once())->method('getName')->willReturn($expectedItem1['name']);
$itemWithVisibleProduct->expects($this->once())->method('getStore')->willReturn($storeMock);
- $itemWithNotVisibleProduct->expects($this->once())->method('getProductId')->willReturn($productIdNotVisible);
+ $itemWithNotVisibleProduct->expects($this->any())->method('getProductId')->willReturn($productIdNotVisible);
$itemWithNotVisibleProduct->expects($this->once())->method('getProduct')->willReturn($productNotVisible);
$itemWithNotVisibleProduct->expects($this->once())->method('getId')->willReturn($expectedItem2['id']);
$itemWithNotVisibleProduct->expects($this->once())->method('getName')->willReturn($expectedItem2['name']);
@@ -255,7 +257,7 @@ public function testGetSectionDataWithNotExistingProduct(): void
->method('getParentItemsRandomCollection')
->with(LastOrderedItems::SIDEBAR_ORDER_LIMIT)
->willReturn([$orderItemMock]);
- $orderItemMock->expects($this->once())->method('getProductId')->willReturn($productId);
+ $orderItemMock->expects($this->any())->method('getProductId')->willReturn($productId);
$this->productRepositoryMock->expects($this->once())
->method('getById')
->with($productId, false, $storeId)
diff --git a/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php b/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php
index 36f5b7c9f4cdd..c63ad52cdfcdc 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/CreateTest.php
@@ -39,7 +39,7 @@
*/
class CreateTest extends TestCase
{
- const CUSTOMER_ID = 1;
+ public const CUSTOMER_ID = 1;
/**
* @var Create
@@ -461,4 +461,126 @@ public function testInitFromOrder()
$this->adminOrderCreate->initFromOrder($this->orderMock);
}
+
+ /**
+ * Test case for setShippingAsBilling
+ *
+ * @dataProvider setShippingAsBillingDataProvider
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
+ public function testSetShippingAsBilling(bool $flag, array $billingData, array $shippingData): void
+ {
+ $billingAddress = $this->createPartialMock(Address::class, ['getData']);
+ $shippingAddress = $this->createPartialMock(
+ Address::class,
+ [
+ 'addData',
+ 'setSameAsBilling',
+ 'getData',
+ ]
+ );
+ $billingAddress->expects($this->any())
+ ->method('getData')
+ ->willReturn($billingData);
+ $shippingAddress->expects($this->any())
+ ->method('getData')
+ ->willReturn($shippingData);
+ $shippingAddress->expects($this->any())
+ ->method('addData')
+ ->willReturnSelf();
+ $shippingAddress->expects($this->any())
+ ->method('setSameAsBilling')
+ ->with($flag)
+ ->willReturnSelf();
+ $quote = $this->getMockBuilder(Quote::class)
+ ->disableOriginalConstructor()
+ ->setMethods(
+ [
+ 'getBillingAddress',
+ 'getShippingAddress',
+ 'setRecollect'
+ ]
+ )
+ ->getMock();
+
+ $quote->expects($this->any())
+ ->method('getBillingAddress')
+ ->willReturn($billingAddress);
+ $quote->expects($this->any())
+ ->method('getShippingAddress')
+ ->willReturn($shippingAddress);
+ $quote->expects($this->any())
+ ->method('setRecollect')
+ ->willReturn(true);
+ $this->sessionQuote
+ ->method('getQuote')
+ ->willReturn($quote);
+ $this->adminOrderCreate->setShippingAsBilling($flag);
+ }
+
+ /**
+ * Data provider for setShippingAsBilling function
+ *
+ * @return array
+ */
+ public function setShippingAsBillingDataProvider(): array
+ {
+ return [
+ 'testcase when sameAsBillingFlag is false' => [
+ false,
+ [
+ 'quote_id' => 1,
+ 'entity_id' => 1,
+ 'same_as_billing' => 1,
+ 'customer_address_id' => null,
+ 'weight' => '0.0000',
+ 'free_shipping' => '0'
+ ],
+ [
+ 'quote_id' => 1,
+ 'entity_id' => 1,
+ 'same_as_billing' => 1,
+ 'customer_address_id' => null,
+ 'weight' => '0.0000',
+ 'free_shipping' => '0'
+ ]
+ ],
+ 'testcase when sameAsBillingFlag is true and there is no `weight` property' => [
+ true,
+ [
+ 'quote_id' => 1,
+ 'entity_id' => 1,
+ 'same_as_billing' => 1,
+ 'customer_address_id' => null,
+ 'free_shipping' => '0'
+ ],
+ [
+ 'quote_id' => 1,
+ 'entity_id' => 1,
+ 'same_as_billing' => 1,
+ 'customer_address_id' => null,
+ 'free_shipping' => '0'
+ ]
+ ],
+ 'testcase when sameAsBillingFlag is true and there is `weight` property' => [
+ false,
+ [
+ 'quote_id' => 1,
+ 'entity_id' => 1,
+ 'same_as_billing' => 1,
+ 'customer_address_id' => null,
+ 'weight' => '0.0000',
+ 'free_shipping' => '1'
+ ],
+ [
+ 'quote_id' => 1,
+ 'entity_id' => 1,
+ 'same_as_billing' => 1,
+ 'customer_address_id' => null,
+ 'weight' => '8.0000',
+ 'free_shipping' => '1'
+ ]
+ ]
+ ];
+ }
}
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 6ae12f9730f34..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
@@ -14,6 +14,7 @@
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Sales\Model\Order\Creditmemo;
use Magento\Sales\Model\Order\Creditmemo\Total\Shipping;
+use Magento\Tax\Model\Calculation as TaxCalculation;
use Magento\Tax\Model\Config;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -104,11 +105,11 @@ public function testCollectException()
->with($allowedShippingAmount, null, false)
->willReturn($allowedShippingAmount);
- $order = new DataObject(
+ $order = $this->getOrderMock(
[
- 'base_shipping_amount' => $orderShippingAmount,
- 'base_shipping_refunded' => $orderShippingRefunded,
- 'base_currency' => $currencyMock,
+ 'base_shipping_amount' => $orderShippingAmount,
+ 'base_shipping_refunded' => $orderShippingRefunded,
+ 'base_currency' => $currencyMock
]
);
@@ -126,6 +127,20 @@ public function testCollectException()
$this->shippingCollector->collect($this->creditmemoMock);
}
+ private function getOrderMock($data)
+ {
+ $orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ foreach ($data as $method => $returnValue) {
+ $orderMock
+ ->method('get' . str_replace('_', '', ucwords($method, '_')))
+ ->willReturn($returnValue);
+ }
+ return $orderMock;
+ }
+
/**
* situation: The admin user did *not* specify any desired refund amount
*
@@ -154,7 +169,7 @@ public function testCollectNoSpecifiedShippingAmount()
$this->taxConfig->expects($this->any())->method('displaySalesShippingInclTax')->willReturn(false);
- $order = new DataObject(
+ $order = $this->getOrderMock(
[
'shipping_amount' => $orderShippingAmount,
'shipping_refunded' => $orderShippingRefunded,
@@ -241,7 +256,7 @@ public function testCollectWithSpecifiedShippingAmount($ratio)
$this->taxConfig->expects($this->any())->method('displaySalesShippingInclTax')->willReturn(false);
- $order = new DataObject(
+ $order = $this->getOrderMock(
[
'shipping_amount' => $orderShippingAmount,
'shipping_refunded' => $orderShippingAmountRefunded,
@@ -346,7 +361,7 @@ public function testCollectUsingTaxInclShippingAmount()
$expectedGrandTotal = $grandTotalBefore + $expectedShippingAmount;
$expectedBaseGrandTtoal = $baseGrandTotalBefore + $expectedBaseShippingAmount;
- $order = new DataObject(
+ $order = $this->getOrderMock(
[
'shipping_amount' => $orderShippingAmount,
'base_shipping_amount' => $baseOrderShippingAmount,
@@ -405,6 +420,7 @@ public function testCollectUsingTaxInclShippingAmount()
$this->shippingCollector->collect($this->creditmemoMock);
}
+
/**
* situation: The admin user did *not* specify any desired refund amount
*
@@ -434,7 +450,7 @@ public function testCollectRefundShippingAmountIncTax()
$this->taxConfig->expects($this->any())->method('displaySalesShippingInclTax')->willReturn(false);
- $order = new DataObject(
+ $order = $this->getOrderMock(
[
'shipping_amount' => $orderShippingAmount,
'shipping_refunded' => $orderShippingRefunded,
@@ -489,4 +505,114 @@ public function testCollectRefundShippingAmountIncTax()
->willReturnSelf();
$this->shippingCollector->collect($this->creditmemoMock);
}
+
+ /**
+ * situation: The admin user specified the desired refund amount that has taxes and discount embedded within it
+ *
+ * @dataProvider calculationSequenceDataProvider
+ * @throws LocalizedException
+ */
+ public function testCollectUsingShippingInclTaxAndDiscountBeforeTax(string $calculationSequence)
+ {
+ $this->taxConfig->expects($this->any())->method('displaySalesShippingInclTax')->willReturn(true);
+ $this->taxConfig->expects($this->any())
+ ->method('getCalculationSequence')
+ ->willReturn($calculationSequence);
+
+ $orderShippingAmount = 14.55;
+ $shippingTaxAmount = 0.45;
+ $shippingDiscountAmount = 10;
+ $orderShippingInclTax = 15;
+ $orderShippingAmountRefunded = 7.27;
+ $orderShippingAmountInclTaxRefunded = 8;
+ $shippingTaxRefunded = 0.24;
+
+ $currencyMultiple = 2;
+ $baseOrderShippingAmount = $orderShippingAmount * $currencyMultiple;
+ $baseShippingTaxAmount = $shippingTaxAmount * $currencyMultiple;
+ $baseOrderShippingInclTax = $orderShippingInclTax * $currencyMultiple;
+ $baseOrderShippingAmountRefunded = $orderShippingAmountRefunded * $currencyMultiple;
+ $baseShippingTaxRefunded = $shippingTaxRefunded * $currencyMultiple;
+
+ //determine expected amounts
+ $expectedShippingAmount = $orderShippingAmount - $orderShippingAmountRefunded;
+ $expectedShippingAmountInclTax = $orderShippingInclTax - $orderShippingAmountInclTaxRefunded;
+
+ $expectedBaseShippingAmount = $expectedShippingAmount * $currencyMultiple;
+ $expectedBaseShippingAmountInclTax = $expectedShippingAmountInclTax * $currencyMultiple;
+
+ $grandTotalBefore = 27;
+ $baseGrandTotalBefore = $grandTotalBefore * $currencyMultiple;
+ $expectedGrandTotal = $grandTotalBefore + $expectedShippingAmount;
+ $expectedBaseGrandTotal = $baseGrandTotalBefore + $expectedBaseShippingAmount;
+
+ $order = $this->getOrderMock(
+ [
+ 'shipping_amount' => $orderShippingAmount,
+ 'base_shipping_amount' => $baseOrderShippingAmount,
+ 'shipping_refunded' => $orderShippingAmountRefunded,
+ 'base_shipping_refunded' => $baseOrderShippingAmountRefunded,
+ 'shipping_incl_tax' => $orderShippingInclTax,
+ 'base_shipping_incl_tax' => $baseOrderShippingInclTax,
+ 'shipping_tax_amount' => $shippingTaxAmount,
+ 'shipping_tax_refunded' => $shippingTaxRefunded,
+ 'base_shipping_tax_amount' => $baseShippingTaxAmount,
+ 'base_shipping_tax_refunded' => $baseShippingTaxRefunded,
+ 'shipping_discount_amount' => $shippingDiscountAmount
+ ]
+ );
+ $orderCreditMemo = $this->createMock(Creditmemo::class);
+ $orderCreditMemo->expects($this->atLeastOnce())
+ ->method('getShippingInclTax')
+ ->willReturn($orderShippingAmountInclTaxRefunded);
+ $orderCreditMemo->expects($this->atLeastOnce())
+ ->method('getBaseShippingInclTax')
+ ->willReturn($orderShippingAmountInclTaxRefunded * $currencyMultiple);
+ $order->expects($this->atLeastOnce())
+ ->method('getCreditmemosCollection')
+ ->willReturn([$orderCreditMemo]);
+
+ $this->creditmemoMock->expects($this->once())->method('getOrder')->willReturn($order);
+ $this->creditmemoMock->expects($this->once())->method('hasBaseShippingAmount')->willReturn(false);
+ $this->creditmemoMock->expects($this->once())->method('getGrandTotal')->willReturn($grandTotalBefore);
+ $this->creditmemoMock->expects($this->once())->method('getBaseGrandTotal')->willReturn($baseGrandTotalBefore);
+
+ //verify
+ $this->creditmemoMock->expects($this->once())
+ ->method('setShippingAmount')
+ ->with($expectedShippingAmount)
+ ->willReturnSelf();
+ $this->creditmemoMock->expects($this->once())
+ ->method('setBaseShippingAmount')
+ ->with($expectedBaseShippingAmount)
+ ->willReturnSelf();
+ $this->creditmemoMock->expects($this->once())
+ ->method('setShippingInclTax')
+ ->with($expectedShippingAmountInclTax)
+ ->willReturnSelf();
+ $this->creditmemoMock->expects($this->once())
+ ->method('setBaseShippingInclTax')
+ ->with($expectedBaseShippingAmountInclTax)
+ ->willReturnSelf();
+ $this->creditmemoMock->expects($this->once())
+ ->method('setGrandTotal')
+ ->with($expectedGrandTotal)
+ ->willReturnSelf();
+ $this->creditmemoMock->expects($this->once())
+ ->method('setBaseGrandTotal')
+ ->with($expectedBaseGrandTotal)
+ ->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/Creditmemo/Total/TaxTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Total/TaxTest.php
index f6d7c6fdba60a..ac88a01ce65af 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Total/TaxTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Total/TaxTest.php
@@ -744,6 +744,68 @@ public function collectDataProvider()
],
];
+ // scenario 7: 1 items, 1 invoiced, shipping covered by cart rule
+ // shipping is partially returned
+ $result['last_creditmemo_with_discount_for_entire_shipping_all_prices_including_tax'] = [
+ 'order_data' => [
+ 'data_fields' => [
+ 'shipping_tax_amount' => 0,
+ 'base_shipping_tax_amount' => 0,
+ 'shipping_discount_tax_compensation_amount' => 1.36,
+ 'base_shipping_discount_tax_compensation_amount' => 1.36,
+ 'tax_amount' => 1.22,
+ 'base_tax_amount' => 1.22,
+ 'tax_invoiced' => 1.22,
+ 'base_tax_invoiced' => 1.22,
+ 'shipping_amount' => 13.64,
+ 'shipping_discount_amount' => 15,
+ 'base_shipping_amount' => 13.64,
+ 'discount_tax_compensation_invoiced' => 1.73,
+ 'base_discount_tax_compensation_invoiced' => 1.73
+ ],
+ ],
+ 'creditmemo_data' => [
+ 'items' => [
+ 'item_1' => [
+ 'order_item' => [
+ 'qty_invoiced' => 1,
+ 'tax_invoiced' => 1.22,
+ 'base_tax_invoiced' => 1.22,
+ 'discount_tax_compensation_amount' => 1.73,
+ 'base_discount_tax_compensation_amount' => 1.73,
+ 'discount_tax_compensation_invoiced' => 1.73,
+ 'base_discount_tax_compensation_invoiced' => 1.73
+ ],
+ 'is_last' => true,
+ 'qty' => 1,
+ ],
+ ],
+ 'is_last' => true,
+ 'data_fields' => [
+ 'shipping_amount' => 0,
+ 'base_shipping_amount' => 0,
+ 'grand_total' => 10.45,
+ 'base_grand_total' => 10.45,
+ 'tax_amount' => 0,
+ 'base_tax_amount' => 0
+ ],
+ ],
+ 'expected_results' => [
+ 'creditmemo_items' => [
+ 'item_1' => [
+ 'tax_amount' => 1.22,
+ 'base_tax_amount' => 1.22,
+ ],
+ ],
+ 'creditmemo_data' => [
+ 'grand_total' => 13.4,
+ 'base_grand_total' => 13.4,
+ 'tax_amount' => 1.22,
+ 'base_tax_amount' => 1.22,
+ ],
+ ],
+ ];
+
return $result;
}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Validation/QuantityValidatorTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Validation/QuantityValidatorTest.php
index c1dedde7a9d5b..128f4664557e5 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Validation/QuantityValidatorTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Validation/QuantityValidatorTest.php
@@ -8,6 +8,8 @@
namespace Magento\Sales\Test\Unit\Model\Order\Creditmemo\Validation;
use Magento\Framework\Pricing\PriceCurrencyInterface;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Sales\Model\Order\Creditmemo;
use Magento\Sales\Api\Data\CreditmemoInterface;
use Magento\Sales\Api\Data\CreditmemoItemInterface;
use Magento\Sales\Api\Data\OrderInterface;
@@ -15,9 +17,13 @@
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Order\Creditmemo\Validation\QuantityValidator;
use Magento\Sales\Model\Order\Item;
+use Magento\Store\Api\Data\StoreConfigInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class QuantityValidatorTest extends TestCase
{
/**
@@ -68,6 +74,7 @@ public function testValidateWithoutItems()
{
$creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class)
->disableOriginalConstructor()
+ ->addMethods(['isValidGrandTotal'])
->getMockForAbstractClass();
$creditmemoMock->expects($this->exactly(2))->method('getOrderId')
->willReturn(1);
@@ -83,8 +90,8 @@ public function testValidateWithoutItems()
->method('get')
->with(1)
->willReturn($orderMock);
- $creditmemoMock->expects($this->once())->method('getGrandTotal')
- ->willReturn(0);
+ $creditmemoMock->expects($this->once())->method('isValidGrandTotal')
+ ->willReturn(false);
$this->assertEquals(
[
__('The credit memo\'s total must be positive.')
@@ -113,7 +120,10 @@ public function testValidateWithWrongItemId()
$orderItemId = 1;
$creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class)
->disableOriginalConstructor()
+ ->addMethods(['isValidGrandTotal'])
->getMockForAbstractClass();
+ $creditmemoMock->expects($this->once())->method('isValidGrandTotal')
+ ->willReturn(true);
$creditmemoMock->expects($this->exactly(2))->method('getOrderId')
->willReturn($orderId);
$creditmemoItemMock = $this->getMockBuilder(
@@ -138,8 +148,6 @@ public function testValidateWithWrongItemId()
->method('get')
->with($orderId)
->willReturn($orderMock);
- $creditmemoMock->expects($this->once())->method('getGrandTotal')
- ->willReturn(12);
$this->assertEquals(
[
@@ -152,6 +160,30 @@ public function testValidateWithWrongItemId()
);
}
+ private function getCreditMemoMockParams()
+ {
+ return [
+ $this->createMock(\Magento\Framework\Model\Context::class),
+ $this->createMock(\Magento\Framework\Registry::class),
+ $this->createMock(\Magento\Framework\Api\ExtensionAttributesFactory::class),
+ $this->createMock(\Magento\Framework\Api\AttributeValueFactory::class),
+ $this->createMock(\Magento\Sales\Model\Order\Creditmemo\Config::class),
+ $this->createMock(\Magento\Sales\Model\OrderFactory::class),
+ $this->createMock(\Magento\Sales\Model\ResourceModel\Order\Creditmemo\Item\CollectionFactory::class),
+ $this->createMock(\Magento\Framework\Math\CalculatorFactory::class),
+ $this->createMock(\Magento\Store\Model\StoreManagerInterface::class),
+ $this->createMock(\Magento\Sales\Model\Order\Creditmemo\CommentFactory::class),
+ $this->createMock(\Magento\Sales\Model\ResourceModel\Order\Creditmemo\Comment\CollectionFactory::class),
+ $this->createMock(\Magento\Framework\Pricing\PriceCurrencyInterface::class),
+ $this->createMock(\Magento\Framework\Model\ResourceModel\AbstractResource::class),
+ $this->createMock(\Magento\Framework\Data\Collection\AbstractDb::class),
+ [],
+ $this->createMock(\Magento\Sales\Model\Order\InvoiceFactory::class),
+ $this->createMock(ScopeConfigInterface::class),
+ $this->createMock(\Magento\Sales\Api\OrderRepositoryInterface::class)
+ ];
+ }
+
/**
* @param int $orderId
* @param int $orderItemId
@@ -161,6 +193,7 @@ public function testValidateWithWrongItemId()
* @param int $total
* @param array $expected
* @param bool $isQtyDecimalAllowed
+ * @param bool $isAllowZeroGrandTotal
* @dataProvider dataProviderForValidateQty
*/
public function testValidate(
@@ -171,15 +204,24 @@ public function testValidate(
$sku,
$total,
array $expected,
- bool $isQtyDecimalAllowed
+ bool $isQtyDecimalAllowed,
+ bool $isAllowZeroGrandTotal
) {
- $creditmemoMock = $this->getMockBuilder(CreditmemoInterface::class)
- ->disableOriginalConstructor()
+ $scopeConfig = $this->getMockForAbstractClass(ScopeConfigInterface::class);
+ $scopeConfig->expects($this->any())->method('getValue')->willReturn($isAllowZeroGrandTotal);
+ $creditMemoConstructorParams = $this->getCreditMemoMockParams();
+ $creditMemoConstructorParams[16] = $scopeConfig;
+
+ $creditmemoMock = $this->getMockBuilder(Creditmemo::class)
+ ->setConstructorArgs($creditMemoConstructorParams)
+ ->onlyMethods(['getOrderId', 'getItems', 'getGrandTotal', '_construct'])
->getMockForAbstractClass();
+
$creditmemoMock->expects($this->exactly(2))->method('getOrderId')
->willReturn($orderId);
$creditmemoMock->expects($this->once())->method('getGrandTotal')
->willReturn($total);
+
$creditmemoItemMock = $this->getMockBuilder(
CreditmemoItemInterface::class
)->disableOriginalConstructor()
@@ -239,7 +281,8 @@ public function dataProviderForValidateQty()
'sku',
'total' => 15,
'expected' => [],
- 'isQtyDecimalAllowed' => false
+ 'isQtyDecimalAllowed' => false,
+ 'isAllowZeroGrandTotal' => true
],
[
'orderId' => 1,
@@ -249,7 +292,8 @@ public function dataProviderForValidateQty()
'sku',
'total' => 15,
'expected' => [],
- 'isQtyDecimalAllowed' => false
+ 'isQtyDecimalAllowed' => false,
+ 'isAllowZeroGrandTotal' => true
],
[
'orderId' => 1,
@@ -264,7 +308,8 @@ public function dataProviderForValidateQty()
$sku
)
],
- 'isQtyDecimalAllowed' => false
+ 'isQtyDecimalAllowed' => false,
+ 'isAllowZeroGrandTotal' => true
],
[
'orderId' => 1,
@@ -281,8 +326,20 @@ public function dataProviderForValidateQty()
),
__('The credit memo\'s total must be positive.')
],
- 'isQtyDecimalAllowed' => false
+ 'isQtyDecimalAllowed' => false,
+ 'isAllowZeroGrandTotal' => false
],
+ [
+ 'orderId' => 1,
+ 'orderItemId' => 1,
+ 'qtyToRequest' => 1,
+ 'qtyToRefund' => 1,
+ 'sku',
+ 'total' => 0,
+ 'expected' => [],
+ 'isQtyDecimalAllowed' => false,
+ 'isAllowZeroGrandTotal' => true
+ ]
];
}
}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Plugin/AddressUpdateTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Plugin/AddressUpdateTest.php
index 44c3efa8523c6..3d3d3225c8879 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Plugin/AddressUpdateTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Plugin/AddressUpdateTest.php
@@ -7,6 +7,7 @@
namespace Magento\Sales\Test\Unit\Model\Order\Invoice\Plugin;
+use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Address;
use Magento\Sales\Model\Order\Invoice;
@@ -34,17 +35,27 @@ class AddressUpdateTest extends TestCase
*/
private $attributeMock;
+ /**
+ * @var MockObject
+ */
+ private $globalConfigMock;
+
protected function setUp(): void
{
$this->gripPoolMock = $this->createMock(GridPool::class);
$this->attributeMock = $this->createMock(Attribute::class);
+ $this->globalConfigMock = $this->createMock(ScopeConfigInterface::class);
$this->model = new AddressUpdate(
$this->gripPoolMock,
- $this->attributeMock
+ $this->attributeMock,
+ $this->globalConfigMock
);
}
- public function testAfterProcess()
+ /**
+ * @dataProvider dataProvider
+ */
+ public function testAfterProcess($asyncReindexEnabled, $expectedReindexCalledCount)
{
$billingId = 100;
$shippingId = 200;
@@ -69,7 +80,9 @@ public function testAfterProcess()
$orderMock->expects($this->once())->method('getBillingAddress')->willReturn($billingMock);
$orderMock->expects($this->once())->method('getShippingAddress')->willReturn($shippingMock);
$orderMock->expects($this->once())->method('getInvoiceCollection')->willReturn($invoiceCollectionMock);
- $orderMock->expects($this->once())->method('getId')->willReturn($orderId);
+ $orderMock->expects($this->exactly($expectedReindexCalledCount))
+ ->method('getId')
+ ->willReturn($orderId);
$invoiceMock->expects($this->once())->method('getBillingAddressId')->willReturn(null);
$invoiceMock->expects($this->once())->method('getShippingAddressId')->willReturn(null);
@@ -81,7 +94,15 @@ public function testAfterProcess()
->with($invoiceMock, ['billing_address_id', 'shipping_address_id'])
->willReturnSelf();
- $this->gripPoolMock->expects($this->once())->method('refreshByOrderId')->with($orderId)->willReturnSelf();
+ $this->gripPoolMock->expects($this->exactly($expectedReindexCalledCount))
+ ->method('refreshByOrderId')
+ ->with($orderId)
+ ->willReturnSelf();
+
+ $this->globalConfigMock->expects($this->once())
+ ->method('getValue')
+ ->with('dev/grid/async_indexing')
+ ->willReturn($asyncReindexEnabled);
$this->model->afterProcess(
$this->createMock(\Magento\Sales\Model\ResourceModel\Order\Handler\Address::class),
@@ -89,4 +110,18 @@ public function testAfterProcess()
$orderMock
);
}
+
+ public function dataProvider()
+ {
+ return [
+ 'Do not reindex when async is enabled' => [
+ true,
+ 0
+ ],
+ 'Reindex when async is disabled' => [
+ false,
+ 1
+ ],
+ ];
+ }
}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Pdf/Config/XsdTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Pdf/Config/XsdTest.php
index 671f629c86481..0ae4bddbe5acf 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Pdf/Config/XsdTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Pdf/Config/XsdTest.php
@@ -1,10 +1,12 @@
validate($schema, $actualErrors);
$this->assertEquals(empty($expectedErrors), $actualResult);
- $this->assertEquals($expectedErrors, $actualErrors);
+ foreach ($expectedErrors as $error) {
+ $this->assertContains($error, $actualErrors);
+ }
}
/**
@@ -172,8 +176,7 @@ protected function _getExemplarTestData()
' ',
[
'Element \'renderer\': [facet \'pattern\'] The value \'\' is not accepted ' .
- 'by the pattern \'[A-Z][a-zA-Z\d]*(\\\\[A-Z][a-zA-Z\d]*)*\'.',
- 'Element \'renderer\': \'\' is not a valid value of the atomic type \'classNameType\'.'
+ 'by the pattern \'[A-Z][a-zA-Z\d]*(\\\\[A-Z][a-zA-Z\d]*)*\'.'
],
],
'non-valid unknown node in page' => [
@@ -203,16 +206,14 @@ protected function _getExemplarTestData()
'foo ',
[
'Element \'title\': [facet \'minLength\'] The value has a length of \'0\'; ' .
- 'this underruns the allowed minimum length of \'1\'.',
- 'Element \'title\': \'\' is not a valid value of the atomic type \'nonEmptyString\'.'
+ 'this underruns the allowed minimum length of \'1\'.'
],
],
'non-valid totals empty source_field' => [
'Title ',
[
'Element \'source_field\': [facet \'pattern\'] The value \'\' is not accepted ' .
- 'by the pattern \'[a-z0-9_]+\'.',
- 'Element \'source_field\': \'\' is not a valid value of the atomic type \'fieldType\'.'
+ 'by the pattern \'[a-z0-9_]+\'.'
],
],
'non-valid totals empty title_source_field' => [
@@ -220,8 +221,7 @@ protected function _getExemplarTestData()
' ',
[
'Element \'title_source_field\': [facet \'pattern\'] The value \'\' is not accepted ' .
- 'by the pattern \'[a-z0-9_]+\'.',
- 'Element \'title_source_field\': \'\' is not a valid value of the atomic type \'fieldType\'.'
+ 'by the pattern \'[a-z0-9_]+\'.'
],
],
'non-valid totals bad model' => [
@@ -229,8 +229,7 @@ protected function _getExemplarTestData()
'a model ',
[
'Element \'model\': [facet \'pattern\'] The value \'a model\' is not accepted ' .
- 'by the pattern \'[A-Z][a-zA-Z\d]*(\\\\[A-Z][a-zA-Z\d]*)*\'.',
- 'Element \'model\': \'a model\' is not a valid value of the atomic type \'classNameType\'.'
+ 'by the pattern \'[A-Z][a-zA-Z\d]*(\\\\[A-Z][a-zA-Z\d]*)*\'.'
],
],
'valid totals title_source_field' => [
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 a3c2c0da3d6c2..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
);
}
@@ -171,6 +165,102 @@ public function testCreate($tracks)
$this->assertEquals($shipment, $this->subject->create($order, ['1' => 5], $tracks));
}
+ /**
+ * @param array|null $tracks
+ * @dataProvider createDataProvider
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
+ public function testCreateWithFloatQtyShipment(?array $tracks): void
+ {
+ $orderItem = $this->createPartialMock(
+ Item::class,
+ ['getId', 'getQtyOrdered', 'getParentItemId', 'getIsVirtual','getIsQtyDecimal']
+ );
+ $orderItem->expects($this->any())
+ ->method('getIsQtyDecimal')
+ ->willReturn(true);
+ $orderItem->expects($this->any())
+ ->method('getId')
+ ->willReturn(1);
+ $orderItem->expects($this->any())
+ ->method('getQtyOrdered')
+ ->willReturn(0.5);
+ $orderItem->expects($this->any())->method('getParentItemId')->willReturn(false);
+ $orderItem->expects($this->any())->method('getIsVirtual')->willReturn(false);
+
+ $shipmentItem = $this->createPartialMock(
+ \Magento\Sales\Model\Order\Shipment\Item::class,
+ ['setQty', 'getOrderItem', 'getQty']
+ );
+ $shipmentItem->expects($this->once())
+ ->method('setQty')
+ ->with(0.5);
+ $shipmentItem->expects($this->once())
+ ->method('getQty')
+ ->willReturn(0.5);
+
+ $shipmentItem->expects($this->atLeastOnce())->method('getOrderItem')->willReturn($orderItem);
+
+ $order = $this->createPartialMock(\Magento\Sales\Model\Order::class, ['getAllItems']);
+ $order->expects($this->any())
+ ->method('getAllItems')
+ ->willReturn([$orderItem]);
+
+ $shipment = $this->createPartialMock(
+ Shipment::class,
+ ['addItem', 'setTotalQty', 'addTrack']
+ );
+ $shipment->expects($this->once())
+ ->method('addItem')
+ ->with($shipmentItem);
+ $shipment->expects($this->once())
+ ->method('setTotalQty')
+ ->with(0.5)
+ ->willReturn($shipment);
+
+ $this->converter->expects($this->any())
+ ->method('toShipment')
+ ->with($order)
+ ->willReturn($shipment);
+ $this->converter->expects($this->any())
+ ->method('itemToShipmentItem')
+ ->with($orderItem)
+ ->willReturn($shipmentItem);
+
+ if ($tracks) {
+ $shipmentTrack = $this->createPartialMock(Track::class, ['addData']);
+
+ if (empty($tracks[0]['number'])) {
+ $shipmentTrack->expects($this->never())
+ ->method('addData');
+
+ $this->trackFactory->expects($this->never())
+ ->method('create');
+
+ $shipment->expects($this->never())
+ ->method('addTrack');
+
+ $this->expectException(
+ LocalizedException::class
+ );
+ } else {
+ $shipmentTrack->expects($this->once())
+ ->method('addData')
+ ->willReturnSelf();
+
+ $this->trackFactory->expects($this->once())
+ ->method('create')
+ ->willReturn($shipmentTrack);
+
+ $shipment->expects($this->once())
+ ->method('addTrack')
+ ->with($shipmentTrack);
+ }
+ }
+
+ $this->assertEquals($shipment, $this->subject->create($order, ['1' => 0.5], $tracks));
+ }
+
/**
* @return array
*/
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanRefundTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanRefundTest.php
index ac656640ccd88..8247557e57633 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanRefundTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Validation/CanRefundTest.php
@@ -102,7 +102,7 @@ public function testCanCreditmemoNoMoney()
$this->orderMock->expects($this->any())
->method('getState')
->willReturn(Order::STATE_PROCESSING);
- $this->orderMock->expects($this->once())
+ $this->orderMock->expects($this->any())
->method('getTotalPaid')
->willReturn(15);
$this->orderMock->expects($this->once())
diff --git a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php
index a48f0702f5a4b..4c46edb93cd49 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php
@@ -42,7 +42,10 @@ protected function setUp(): void
'canCreditmemo',
'getTotalRefunded',
'getConfig',
- 'getIsNotVirtual'
+ 'getIsVirtual',
+ 'getIsNotVirtual',
+ 'getStatus',
+ 'getAllItems'
]
)
->disableOriginalConstructor()
@@ -102,6 +105,14 @@ public function testCheck(
->willReturn($isInProcess);
$this->orderMock->method('getIsNotVirtual')
->willReturn($isNotVirtual);
+ $this->orderMock->method('getAllItems')
+ ->willReturn([]);
+ if (!$isNotVirtual) {
+ $this->orderMock->method('getIsVirtual')
+ ->willReturn(!$isNotVirtual);
+ $this->orderMock->method('getStatus')
+ ->willReturn($expectedState);
+ }
$this->state->check($this->orderMock);
$this->assertEquals($expectedState, $this->orderMock->getState());
}
@@ -126,7 +137,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 0,
'is_canceled' => false,
'can_unhold' => false,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'complete - !canCreditmemo,!canShip -> closed' => [
'can_credit_memo' => false,
@@ -139,7 +151,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 0,
'is_canceled' => false,
'can_unhold' => false,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'processing - !canCreditmemo,canShip -> processing' => [
'can_credit_memo' => false,
@@ -152,7 +165,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 0,
'is_canceled' => false,
'can_unhold' => false,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'complete - !canCreditmemo,canShip -> complete' => [
'can_credit_memo' => false,
@@ -165,7 +179,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 0,
'is_canceled' => false,
'can_unhold' => false,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'processing - canCreditmemo,!canShip -> complete' => [
'can_credit_memo' => true,
@@ -178,7 +193,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 0,
'is_canceled' => false,
'can_unhold' => false,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'complete - canCreditmemo,!canShip -> complete' => [
'can_credit_memo' => true,
@@ -191,7 +207,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 0,
'is_canceled' => false,
'can_unhold' => false,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'processing - canCreditmemo, canShip -> processing' => [
'can_credit_memo' => true,
@@ -204,7 +221,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 0,
'is_canceled' => false,
'can_unhold' => false,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'complete - canCreditmemo, canShip -> complete' => [
'can_credit_memo' => true,
@@ -217,7 +235,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 0,
'is_canceled' => false,
'can_unhold' => false,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'new - canCreditmemo, canShip, IsInProcess -> processing' => [
'can_credit_memo' => true,
@@ -230,7 +249,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 1,
'is_canceled' => false,
'can_unhold' => false,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'new - canCreditmemo, !canShip, IsInProcess -> processing' => [
'can_credit_memo' => true,
@@ -243,7 +263,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 1,
'is_canceled' => false,
'can_unhold' => false,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'new - canCreditmemo, canShip, !IsInProcess -> new' => [
'can_credit_memo' => true,
@@ -256,7 +277,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 1,
'is_canceled' => false,
'can_unhold' => false,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'hold - canUnhold -> hold' => [
'can_credit_memo' => true,
@@ -269,7 +291,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 0,
'is_canceled' => false,
'can_unhold' => true,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'payment_review - canUnhold -> payment_review' => [
'can_credit_memo' => true,
@@ -282,7 +305,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 0,
'is_canceled' => false,
'can_unhold' => true,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'pending_payment - canUnhold -> pending_payment' => [
'can_credit_memo' => true,
@@ -295,7 +319,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 0,
'is_canceled' => false,
'can_unhold' => true,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'cancelled - isCanceled -> cancelled' => [
'can_credit_memo' => true,
@@ -308,7 +333,8 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 0,
'is_canceled' => true,
'can_unhold' => false,
- 'is_not_virtual' => true
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
],
'processing - !canCreditmemo!canShip -> complete(virtual product)' => [
'can_credit_memo' => false,
@@ -321,7 +347,50 @@ public function stateCheckDataProvider()
'get_is_in_process_invoke_count' => 0,
'is_canceled' => false,
'can_unhold' => false,
- 'is_not_virtual' => false
+ 'is_not_virtual' => false,
+ 'isPartiallyRefundedOrderShipped' => false
+ ],
+ 'complete - !canCreditmemo, !canShip - closed(virtual product)' => [
+ 'can_credit_memo' => false,
+ 'can_credit_memo_invoke_count' => 1,
+ 'can_ship' => false,
+ 'call_can_skip_num' => 1,
+ 'current_state' => Order::STATE_COMPLETE,
+ 'expected_state' => Order::STATE_CLOSED,
+ 'is_in_process' => false,
+ 'get_is_in_process_invoke_count' => 0,
+ 'is_canceled' => false,
+ 'can_unhold' => false,
+ 'is_not_virtual' => false,
+ 'isPartiallyRefundedOrderShipped' => false
+ ],
+ 'processing - canCreditmemo, !canShip, !isPartiallyRefundedOrderShipped -> processing' => [
+ 'can_credit_memo' => true,
+ 'can_credit_memo_invoke_count' => 1,
+ 'can_ship' => true,
+ 'call_can_skip_num' => 1,
+ 'current_state' => Order::STATE_PROCESSING,
+ 'expected_state' => Order::STATE_PROCESSING,
+ 'is_in_process' => true,
+ 'get_is_in_process_invoke_count' => 0,
+ 'is_canceled' => false,
+ 'can_unhold' => false,
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => false
+ ],
+ 'processing - canCreditmemo, !canShip, isPartiallyRefundedOrderShipped -> complete' => [
+ 'can_credit_memo' => true,
+ 'can_credit_memo_invoke_count' => 1,
+ 'can_ship' => false,
+ 'call_can_skip_num' => 1,
+ 'current_state' => Order::STATE_PROCESSING,
+ 'expected_state' => Order::STATE_COMPLETE,
+ 'is_in_process' => true,
+ 'get_is_in_process_invoke_count' => 0,
+ 'is_canceled' => false,
+ 'can_unhold' => false,
+ 'is_not_virtual' => true,
+ 'isPartiallyRefundedOrderShipped' => true
],
];
}
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php b/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php
index 1cdcff9e5d384..88817ec2be9b8 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php
@@ -192,10 +192,13 @@ public function testNotify()
$this->assertEquals($returnValue, $this->creditmemoService->notify($id));
}
+ /**
+ * Run test notify method
+ */
public function testRefund()
{
$creditMemoMock = $this->getMockBuilder(CreditmemoInterface::class)
- ->setMethods(['getId', 'getOrder', 'getInvoice'])
+ ->setMethods(['getId', 'getOrder', 'getInvoice', 'getOrderId'])
->disableOriginalConstructor()
->getMockForAbstractClass();
$creditMemoMock->expects($this->once())->method('getId')->willReturn(null);
@@ -204,6 +207,7 @@ public function testRefund()
->getMock();
$creditMemoMock->expects($this->atLeastOnce())->method('getOrder')->willReturn($orderMock);
+ $creditMemoMock->expects($this->atLeastOnce())->method('getOrderId')->willReturn(1);
$orderMock->expects($this->once())->method('getBaseTotalRefunded')->willReturn(0);
$orderMock->expects($this->once())->method('getBaseTotalPaid')->willReturn(10);
$creditMemoMock->expects($this->once())->method('getBaseGrandTotal')->willReturn(10);
@@ -267,12 +271,14 @@ public function testRefund()
public function testRefundPendingCreditMemo()
{
$creditMemoMock = $this->getMockBuilder(CreditmemoInterface::class)
- ->setMethods(['getId', 'getOrder', 'getState', 'getInvoice'])
+ ->setMethods(['getId', 'getOrder', 'getState', 'getInvoice', 'getOrderId'])
->disableOriginalConstructor()
->getMockForAbstractClass();
$creditMemoMock->expects($this->once())->method('getId')->willReturn(444);
$creditMemoMock->expects($this->once())->method('getState')
->willReturn(Creditmemo::STATE_OPEN);
+ $creditMemoMock->expects($this->once())->method('getOrderId')
+ ->willReturn(1);
$orderMock = $this->getMockBuilder(Order::class)
->disableOriginalConstructor()
->getMock();
@@ -347,7 +353,7 @@ public function testRefundExpectsMoneyAvailableToReturn()
$baseTotalPaid = 10;
/** @var CreditmemoInterface|MockObject $creditMemo */
$creditMemo = $this->getMockBuilder(CreditmemoInterface::class)
- ->setMethods(['getId', 'getOrder'])
+ ->setMethods(['getId', 'getOrder', 'getOrderId'])
->getMockForAbstractClass();
$creditMemo->method('getId')
->willReturn(null);
@@ -357,6 +363,8 @@ public function testRefundExpectsMoneyAvailableToReturn()
->getMock();
$creditMemo->method('getOrder')
->willReturn($order);
+ $creditMemo->method('getOrderId')
+ ->willReturn(1);
$creditMemo->method('getBaseGrandTotal')
->willReturn($baseGrandTotal);
$order->method('getBaseTotalRefunded')
@@ -389,6 +397,18 @@ public function testRefundDoNotExpectsId()
$this->creditmemoService->refund($creditMemoMock, true);
}
+ public function testRefundDoNotExpectsOrderId()
+ {
+ $this->expectException('Magento\Framework\Exception\LocalizedException');
+ $this->expectExceptionMessage('We found an invalid order to refund.');
+ $creditMemoMock = $this->getMockBuilder(CreditmemoInterface::class)
+ ->setMethods(['getId', 'getOrderId'])
+ ->getMockForAbstractClass();
+ $creditMemoMock->expects($this->once())->method('getId')->willReturn(null);
+ $creditMemoMock->expects($this->once())->method('getOrderId')->willReturn(null);
+ $this->creditmemoService->refund($creditMemoMock, true);
+ }
+
public function testMultiCurrencyRefundExpectsMoneyAvailableToReturn()
{
$this->expectException('Magento\Framework\Exception\LocalizedException');
@@ -402,7 +422,7 @@ public function testMultiCurrencyRefundExpectsMoneyAvailableToReturn()
/** @var CreditmemoInterface|MockObject $creditMemo */
$creditMemo = $this->getMockBuilder(CreditmemoInterface::class)
- ->setMethods(['getId', 'getOrder'])
+ ->setMethods(['getId', 'getOrder', 'getOrderId'])
->getMockForAbstractClass();
$creditMemo->method('getId')
->willReturn(null);
@@ -412,6 +432,8 @@ public function testMultiCurrencyRefundExpectsMoneyAvailableToReturn()
->getMock();
$creditMemo->method('getOrder')
->willReturn($order);
+ $creditMemo->method('getOrderId')
+ ->willReturn(1);
$creditMemo->method('getBaseGrandTotal')
->willReturn($baseGrandTotal);
$creditMemo->method('getGrandTotal')
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/etc/adminhtml/di.xml b/app/code/Magento/Sales/etc/adminhtml/di.xml
index 078a7ef21c4f1..7ea46d60caeec 100644
--- a/app/code/Magento/Sales/etc/adminhtml/di.xml
+++ b/app/code/Magento/Sales/etc/adminhtml/di.xml
@@ -52,4 +52,9 @@
+
+
+ \Magento\Catalog\Ui\DataProvider\Product\ProductCollectionFactory
+
+
diff --git a/app/code/Magento/Sales/etc/adminhtml/system.xml b/app/code/Magento/Sales/etc/adminhtml/system.xml
index a3834b28e05a6..d19ee645a1d18 100644
--- a/app/code/Magento/Sales/etc/adminhtml/system.xml
+++ b/app/code/Magento/Sales/etc/adminhtml/system.xml
@@ -63,7 +63,7 @@
Invoice and Packing Slip Design
- Logo for PDF Print-outs (200x50)
+ Logo for PDF Print-outs
Magento\Config\Model\Config\Backend\Image\Pdf
sales/store/logo
sales/store/logo
diff --git a/app/code/Magento/Sales/etc/db_schema.xml b/app/code/Magento/Sales/etc/db_schema.xml
index 302d014625acb..112e927bf4c9d 100644
--- a/app/code/Magento/Sales/etc/db_schema.xml
+++ b/app/code/Magento/Sales/etc/db_schema.xml
@@ -475,7 +475,7 @@
-
+
+
+
+ Magento\Backend\ViewModel\LimitTotalNumberOfProductsInGrid
+
+
diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_data.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_data.xml
index a75b58bceb2ba..55ea314e6ea53 100644
--- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_data.xml
+++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_data.xml
@@ -46,6 +46,11 @@
+
+
+ Magento\Backend\ViewModel\LimitTotalNumberOfProductsInGrid
+
+
diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_search.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_search.xml
index 95434f09b884d..56ec72b9011c8 100644
--- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_search.xml
+++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_search.xml
@@ -11,6 +11,11 @@
+
+
+ Magento\Backend\ViewModel\LimitTotalNumberOfProductsInGrid
+
+
diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_search_grid.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_search_grid.xml
index 08849c7dbdfb1..9a04f38e075b5 100644
--- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_search_grid.xml
+++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_load_block_search_grid.xml
@@ -10,5 +10,10 @@
+
+
+ Magento\Backend\ViewModel\LimitTotalNumberOfProductsInGrid
+
+