= $block->escapeHtml(
@@ -12,7 +15,8 @@
); ?>
= $block->escapeHtml(
- __('Select values from each attribute to include in this product. Each unique combination of values creates a unique product SKU.')
+ __('Select values from each attribute to include in this product. ' .
+ 'Each unique combination of values creates a unique product SKU.')
);?>
@@ -72,7 +76,8 @@
-
@@ -120,8 +125,8 @@
"= /* @noEscape */ $block->getComponentName() ?>": {
"component": "Magento_ConfigurableProduct/js/variations/steps/attributes_values",
"appendTo": "= /* @noEscape */ $block->getParentComponentName() ?>",
- "optionsUrl": "= /* @noEscape */ $block->getUrl('catalog/product_attribute/getAttributes') ?>",
- "createOptionsUrl": "= /* @noEscape */ $block->getUrl('catalog/product_attribute/createOptions') ?>"
+ "optionsUrl": "= /* @noEscape */ $attributesUrl ?>",
+ "createOptionsUrl": "= /* @noEscape */ $optionsUrl ?>"
}
}
}
diff --git a/app/code/Magento/Customer/Model/Plugin/UpdateCustomer.php b/app/code/Magento/Customer/Model/Plugin/UpdateCustomer.php
new file mode 100644
index 0000000000000..fdde31e05fb2e
--- /dev/null
+++ b/app/code/Magento/Customer/Model/Plugin/UpdateCustomer.php
@@ -0,0 +1,81 @@
+request = $request;
+ }
+
+ /**
+ * Update customer by id from request if exist
+ *
+ * @param CustomerRepositoryInterface $customerRepository
+ * @param CustomerInterface $customer
+ * @param string|null $passwordHash
+ * @return array
+ */
+ public function beforeSave(
+ CustomerRepositoryInterface $customerRepository,
+ CustomerInterface $customer,
+ ?string $passwordHash = null
+ ): array {
+ $customerId = $this->request->getParam('customerId');
+
+ if ($customerId) {
+ $customer = $this->getUpdatedCustomer($customerRepository->getById($customerId), $customer);
+ }
+
+ return [$customer, $passwordHash];
+ }
+
+ /**
+ * Return updated customer
+ *
+ * @param CustomerInterface $originCustomer
+ * @param CustomerInterface $customer
+ * @return CustomerInterface
+ */
+ private function getUpdatedCustomer(
+ CustomerInterface $originCustomer,
+ CustomerInterface $customer
+ ): CustomerInterface {
+ $newCustomer = clone $originCustomer;
+ foreach ($customer->__toArray() as $name => $value) {
+ if ($name === CustomerInterface::CUSTOM_ATTRIBUTES) {
+ $value = $customer->getCustomAttributes();
+ } elseif ($name === CustomerInterface::EXTENSION_ATTRIBUTES_KEY) {
+ $value = $customer->getExtensionAttributes();
+ } elseif ($name === CustomerInterface::KEY_ADDRESSES) {
+ $value = $customer->getAddresses();
+ }
+
+ $newCustomer->setData($name, $value);
+ }
+
+ return $newCustomer;
+ }
+}
diff --git a/app/code/Magento/Customer/Observer/UpgradeOrderCustomerEmailObserver.php b/app/code/Magento/Customer/Observer/UpgradeOrderCustomerEmailObserver.php
new file mode 100644
index 0000000000000..c2b7189b808a3
--- /dev/null
+++ b/app/code/Magento/Customer/Observer/UpgradeOrderCustomerEmailObserver.php
@@ -0,0 +1,78 @@
+orderRepository = $orderRepository;
+ $this->searchCriteriaBuilder = $searchCriteriaBuilder;
+ }
+
+ /**
+ * Upgrade order customer email when customer has changed email
+ *
+ * @param Observer $observer
+ * @return void
+ */
+ public function execute(Observer $observer): void
+ {
+ /** @var Customer $originalCustomer */
+ $originalCustomer = $observer->getEvent()->getOrigCustomerDataObject();
+ if (!$originalCustomer) {
+ return;
+ }
+
+ /** @var Customer $customer */
+ $customer = $observer->getEvent()->getCustomerDataObject();
+ $customerEmail = $customer->getEmail();
+
+ if ($customerEmail === $originalCustomer->getEmail()) {
+ return;
+ }
+ $searchCriteria = $this->searchCriteriaBuilder
+ ->addFilter(OrderInterface::CUSTOMER_ID, $customer->getId())
+ ->create();
+
+ /**
+ * @var Collection $orders
+ */
+ $orders = $this->orderRepository->getList($searchCriteria);
+ $orders->setDataToAll(OrderInterface::CUSTOMER_EMAIL, $customerEmail);
+ $orders->save();
+ }
+}
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerDeleteWishlistItemActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerDeleteWishlistItemActionGroup.xml
new file mode 100644
index 0000000000000..b827cba8490b8
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerDeleteWishlistItemActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerFindWishlistItemActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerFindWishlistItemActionGroup.xml
new file mode 100644
index 0000000000000..bbdc4de330840
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerFindWishlistItemActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminNavigateCustomerWishlistTabActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminNavigateCustomerWishlistTabActionGroup.xml
new file mode 100644
index 0000000000000..66b464006aa0f
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminNavigateCustomerWishlistTabActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertAdminCustomerNoItemsInWishlistActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertAdminCustomerNoItemsInWishlistActionGroup.xml
new file mode 100644
index 0000000000000..16688be61171e
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertAdminCustomerNoItemsInWishlistActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/CreateCustomerOrderActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/CreateCustomerOrderActionGroup.xml
new file mode 100644
index 0000000000000..f287c728bb28d
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/CreateCustomerOrderActionGroup.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+ Create Order via API assigned to Customer.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAssertCustomerSidebarItemIsNotPresentActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAssertCustomerSidebarItemIsNotPresentActionGroup.xml
new file mode 100644
index 0000000000000..5dafe59bf3c48
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAssertCustomerSidebarItemIsNotPresentActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerWishlistSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerWishlistSection.xml
new file mode 100644
index 0000000000000..39a67968c66e4
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerWishlistSection.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml
index ec5141d84b1bd..61ce050aa3ef2 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml
@@ -17,5 +17,9 @@
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCustomerAccountOrderListTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCustomerAccountOrderListTest.xml
new file mode 100644
index 0000000000000..ba113c739d706
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCustomerAccountOrderListTest.xml
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Unit/Observer/UpgradeOrderCustomerEmailObserverTest.php b/app/code/Magento/Customer/Test/Unit/Observer/UpgradeOrderCustomerEmailObserverTest.php
new file mode 100644
index 0000000000000..d05c10c00e6c3
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Unit/Observer/UpgradeOrderCustomerEmailObserverTest.php
@@ -0,0 +1,222 @@
+orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class)
+ ->getMock();
+
+ $this->searchCriteriaBuilderMock = $this->getMockBuilder(SearchCriteriaBuilder::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->eventMock = $this->getMockBuilder(Event::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getCustomerDataObject', 'getOrigCustomerDataObject'])
+ ->getMock();
+
+ $this->observerMock = $this->getMockBuilder(Observer::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->observerMock->expects($this->any())->method('getEvent')->willReturn($this->eventMock);
+
+ $this->objectManagerHelper = new ObjectManagerHelper($this);
+
+ $this->orderCustomerEmailObserver = $this->objectManagerHelper->getObject(
+ UpgradeOrderCustomerEmailObserver::class,
+ [
+ 'orderRepository' => $this->orderRepositoryMock,
+ 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock,
+ ]
+ );
+ }
+
+ /**
+ * Verifying that the order email is not updated when the customer email is not updated
+ *
+ */
+ public function testUpgradeOrderCustomerEmailWhenMailIsNotChanged(): void
+ {
+ $customer = $this->createCustomerMock();
+ $originalCustomer = $this->createCustomerMock();
+
+ $this->setCustomerToEventMock($customer);
+ $this->setOriginalCustomerToEventMock($originalCustomer);
+
+ $this->setCustomerEmail($originalCustomer, self::ORIGINAL_CUSTOMER_EMAIL);
+ $this->setCustomerEmail($customer, self::ORIGINAL_CUSTOMER_EMAIL);
+
+ $this->whenOrderRepositoryGetListIsNotCalled();
+
+ $this->orderCustomerEmailObserver->execute($this->observerMock);
+ }
+
+ /**
+ * Verifying that the order email is updated after the customer updates their email
+ *
+ */
+ public function testUpgradeOrderCustomerEmail(): void
+ {
+ $customer = $this->createCustomerMock();
+ $originalCustomer = $this->createCustomerMock();
+ $orderCollectionMock = $this->createOrderMock();
+
+ $this->setCustomerToEventMock($customer);
+ $this->setOriginalCustomerToEventMock($originalCustomer);
+
+ $this->setCustomerEmail($originalCustomer, self::ORIGINAL_CUSTOMER_EMAIL);
+ $this->setCustomerEmail($customer, self::NEW_CUSTOMER_EMAIL);
+
+ $this->whenOrderRepositoryGetListIsCalled($orderCollectionMock);
+
+ $this->whenOrderCollectionSetDataToAllIsCalled($orderCollectionMock);
+
+ $this->whenOrderCollectionSaveIsCalled($orderCollectionMock);
+
+ $this->orderCustomerEmailObserver->execute($this->observerMock);
+ }
+
+ private function createCustomerMock(): MockObject
+ {
+ $customer = $this->getMockBuilder(CustomerInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ return $customer;
+ }
+
+ private function createOrderMock(): MockObject
+ {
+ $orderCollectionMock = $this->getMockBuilder(Collection::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ return $orderCollectionMock;
+ }
+
+ private function setCustomerToEventMock(MockObject $customer): void
+ {
+ $this->eventMock->expects($this->once())
+ ->method('getCustomerDataObject')
+ ->willReturn($customer);
+ }
+
+ private function setOriginalCustomerToEventMock(MockObject $originalCustomer): void
+ {
+ $this->eventMock->expects($this->once())
+ ->method('getOrigCustomerDataObject')
+ ->willReturn($originalCustomer);
+ }
+
+ private function setCustomerEmail(MockObject $originalCustomer, string $email): void
+ {
+ $originalCustomer->expects($this->once())
+ ->method('getEmail')
+ ->willReturn($email);
+ }
+
+ private function whenOrderRepositoryGetListIsCalled(MockObject $orderCollectionMock): void
+ {
+ $searchCriteriaMock = $this->getMockBuilder(SearchCriteria::class)
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->searchCriteriaBuilderMock->expects($this->once())
+ ->method('create')
+ ->willReturn($searchCriteriaMock);
+
+ $this->searchCriteriaBuilderMock->expects($this->once())
+ ->method('addFilter')
+ ->willReturn($this->searchCriteriaBuilderMock);
+
+ $this->orderRepositoryMock->expects($this->once())
+ ->method('getList')
+ ->with($searchCriteriaMock)
+ ->willReturn($orderCollectionMock);
+ }
+
+ private function whenOrderCollectionSetDataToAllIsCalled(MockObject $orderCollectionMock): void
+ {
+ $orderCollectionMock->expects($this->once())
+ ->method('setDataToAll')
+ ->with(OrderInterface::CUSTOMER_EMAIL, self::NEW_CUSTOMER_EMAIL);
+ }
+
+ private function whenOrderCollectionSaveIsCalled(MockObject $orderCollectionMock): void
+ {
+ $orderCollectionMock->expects($this->once())
+ ->method('save');
+ }
+
+ private function whenOrderRepositoryGetListIsNotCalled(): void
+ {
+ $this->searchCriteriaBuilderMock->expects($this->never())
+ ->method('addFilter');
+ $this->searchCriteriaBuilderMock->expects($this->never())
+ ->method('create');
+
+ $this->orderRepositoryMock->expects($this->never())
+ ->method('getList');
+ }
+}
diff --git a/app/code/Magento/Customer/etc/events.xml b/app/code/Magento/Customer/etc/events.xml
index 2a724498a0359..0194f91c591f5 100644
--- a/app/code/Magento/Customer/etc/events.xml
+++ b/app/code/Magento/Customer/etc/events.xml
@@ -16,6 +16,7 @@
+
diff --git a/app/code/Magento/Customer/etc/webapi_rest/di.xml b/app/code/Magento/Customer/etc/webapi_rest/di.xml
index f2457963a5f3d..426df2bbaa128 100644
--- a/app/code/Magento/Customer/etc/webapi_rest/di.xml
+++ b/app/code/Magento/Customer/etc/webapi_rest/di.xml
@@ -19,4 +19,7 @@
+
+
+
diff --git a/app/code/Magento/Developer/Model/Logger/Handler/Debug.php b/app/code/Magento/Developer/Model/Logger/Handler/Debug.php
index ba98524bb665e..fc659c773c0af 100644
--- a/app/code/Magento/Developer/Model/Logger/Handler/Debug.php
+++ b/app/code/Magento/Developer/Model/Logger/Handler/Debug.php
@@ -21,11 +21,6 @@ class Debug extends \Magento\Framework\Logger\Handler\Debug
*/
private $state;
- /**
- * @var ScopeConfigInterface
- */
- private $scopeConfig;
-
/**
* @var DeploymentConfig
*/
@@ -34,7 +29,6 @@ class Debug extends \Magento\Framework\Logger\Handler\Debug
/**
* @param DriverInterface $filesystem
* @param State $state
- * @param ScopeConfigInterface $scopeConfig
* @param DeploymentConfig $deploymentConfig
* @param string $filePath
* @throws \Exception
@@ -42,14 +36,12 @@ class Debug extends \Magento\Framework\Logger\Handler\Debug
public function __construct(
DriverInterface $filesystem,
State $state,
- ScopeConfigInterface $scopeConfig,
DeploymentConfig $deploymentConfig,
$filePath = null
) {
parent::__construct($filesystem, $filePath);
$this->state = $state;
- $this->scopeConfig = $scopeConfig;
$this->deploymentConfig = $deploymentConfig;
}
diff --git a/app/code/Magento/Developer/Model/Logger/Handler/Syslog.php b/app/code/Magento/Developer/Model/Logger/Handler/Syslog.php
index 3f5ff58640313..c6ee70fb9ce40 100644
--- a/app/code/Magento/Developer/Model/Logger/Handler/Syslog.php
+++ b/app/code/Magento/Developer/Model/Logger/Handler/Syslog.php
@@ -29,13 +29,10 @@ class Syslog extends \Magento\Framework\Logger\Handler\Syslog
private $deploymentConfig;
/**
- * @param ScopeConfigInterface $scopeConfig Scope config
* @param DeploymentConfig $deploymentConfig Deployment config
* @param string $ident The string ident to be added to each message
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct(
- ScopeConfigInterface $scopeConfig,
DeploymentConfig $deploymentConfig,
string $ident
) {
diff --git a/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/SyslogTest.php b/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/SyslogTest.php
index 8bb0b1f176313..5e824e43764de 100644
--- a/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/SyslogTest.php
+++ b/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/SyslogTest.php
@@ -44,7 +44,6 @@ protected function setUp(): void
$this->deploymentConfigMock = $this->createMock(DeploymentConfig::class);
$this->model = new Syslog(
- $this->scopeConfigMock,
$this->deploymentConfigMock,
'Magento'
);
diff --git a/app/code/Magento/Downloadable/Controller/Download/LinkSample.php b/app/code/Magento/Downloadable/Controller/Download/LinkSample.php
index c0bc825a8285b..c449f8f54872f 100644
--- a/app/code/Magento/Downloadable/Controller/Download/LinkSample.php
+++ b/app/code/Magento/Downloadable/Controller/Download/LinkSample.php
@@ -7,8 +7,9 @@
namespace Magento\Downloadable\Controller\Download;
-use Magento\Catalog\Model\Product\SalabilityChecker;
use Magento\Downloadable\Helper\Download as DownloadHelper;
+use Magento\Downloadable\Model\Link as LinkModel;
+use Magento\Downloadable\Model\RelatedProductRetriever;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\ResponseInterface;
@@ -20,20 +21,21 @@
class LinkSample extends \Magento\Downloadable\Controller\Download
{
/**
- * @var SalabilityChecker
+ * @var RelatedProductRetriever
*/
- private $salabilityChecker;
+ private $relatedProductRetriever;
/**
* @param Context $context
- * @param SalabilityChecker|null $salabilityChecker
+ * @param RelatedProductRetriever $relatedProductRetriever
*/
public function __construct(
Context $context,
- SalabilityChecker $salabilityChecker = null
+ RelatedProductRetriever $relatedProductRetriever
) {
parent::__construct($context);
- $this->salabilityChecker = $salabilityChecker ?: $this->_objectManager->get(SalabilityChecker::class);
+
+ $this->relatedProductRetriever = $relatedProductRetriever;
}
/**
@@ -44,9 +46,10 @@ public function __construct(
public function execute()
{
$linkId = $this->getRequest()->getParam('link_id', 0);
- /** @var \Magento\Downloadable\Model\Link $link */
- $link = $this->_objectManager->create(\Magento\Downloadable\Model\Link::class)->load($linkId);
- if ($link->getId() && $this->salabilityChecker->isSalable($link->getProductId())) {
+ /** @var LinkModel $link */
+ $link = $this->_objectManager->create(LinkModel::class);
+ $link->load($linkId);
+ if ($link->getId() && $this->isProductSalable($link)) {
$resource = '';
$resourceType = '';
if ($link->getSampleType() == DownloadHelper::LINK_TYPE_URL) {
@@ -74,4 +77,16 @@ public function execute()
return $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl());
}
+
+ /**
+ * Check is related product salable.
+ *
+ * @param LinkModel $link
+ * @return bool
+ */
+ private function isProductSalable(LinkModel $link): bool
+ {
+ $product = $this->relatedProductRetriever->getProduct((int) $link->getProductId());
+ return $product ? $product->isSalable() : false;
+ }
}
diff --git a/app/code/Magento/Downloadable/Controller/Download/Sample.php b/app/code/Magento/Downloadable/Controller/Download/Sample.php
index b95ec510fdd9b..e2561092a7592 100644
--- a/app/code/Magento/Downloadable/Controller/Download/Sample.php
+++ b/app/code/Magento/Downloadable/Controller/Download/Sample.php
@@ -7,8 +7,9 @@
namespace Magento\Downloadable\Controller\Download;
-use Magento\Catalog\Model\Product\SalabilityChecker;
use Magento\Downloadable\Helper\Download as DownloadHelper;
+use Magento\Downloadable\Model\RelatedProductRetriever;
+use Magento\Downloadable\Model\Sample as SampleModel;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\ResponseInterface;
@@ -20,20 +21,21 @@
class Sample extends \Magento\Downloadable\Controller\Download
{
/**
- * @var SalabilityChecker
+ * @var RelatedProductRetriever
*/
- private $salabilityChecker;
+ private $relatedProductRetriever;
/**
* @param Context $context
- * @param SalabilityChecker|null $salabilityChecker
+ * @param RelatedProductRetriever $relatedProductRetriever
*/
public function __construct(
Context $context,
- SalabilityChecker $salabilityChecker = null
+ RelatedProductRetriever $relatedProductRetriever
) {
parent::__construct($context);
- $this->salabilityChecker = $salabilityChecker ?: $this->_objectManager->get(SalabilityChecker::class);
+
+ $this->relatedProductRetriever = $relatedProductRetriever;
}
/**
@@ -44,9 +46,10 @@ public function __construct(
public function execute()
{
$sampleId = $this->getRequest()->getParam('sample_id', 0);
- /** @var \Magento\Downloadable\Model\Sample $sample */
- $sample = $this->_objectManager->create(\Magento\Downloadable\Model\Sample::class)->load($sampleId);
- if ($sample->getId() && $this->salabilityChecker->isSalable($sample->getProductId())) {
+ /** @var SampleModel $sample */
+ $sample = $this->_objectManager->create(SampleModel::class);
+ $sample->load($sampleId);
+ if ($sample->getId() && $this->isProductSalable($sample)) {
$resource = '';
$resourceType = '';
if ($sample->getSampleType() == DownloadHelper::LINK_TYPE_URL) {
@@ -71,4 +74,16 @@ public function execute()
return $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl());
}
+
+ /**
+ * Check is related product salable.
+ *
+ * @param SampleModel $sample
+ * @return bool
+ */
+ private function isProductSalable(SampleModel $sample): bool
+ {
+ $product = $this->relatedProductRetriever->getProduct((int) $sample->getProductId());
+ return $product ? $product->isSalable() : false;
+ }
}
diff --git a/app/code/Magento/Downloadable/Model/RelatedProductRetriever.php b/app/code/Magento/Downloadable/Model/RelatedProductRetriever.php
new file mode 100644
index 0000000000000..f701f96b910e7
--- /dev/null
+++ b/app/code/Magento/Downloadable/Model/RelatedProductRetriever.php
@@ -0,0 +1,68 @@
+productRepository = $productRepository;
+ $this->searchCriteriaBuilder = $searchCriteriaBuilder;
+ $this->metadataPool = $metadataPool;
+ }
+
+ /**
+ * Get related product.
+ *
+ * @param int $productId
+ * @return ProductInterface|null
+ */
+ public function getProduct(int $productId): ?ProductInterface
+ {
+ $productMetadata = $this->metadataPool->getMetadata(ProductInterface::class);
+
+ $searchCriteria = $this->searchCriteriaBuilder->addFilter($productMetadata->getLinkField(), $productId)
+ ->create();
+ $items = $this->productRepository->getList($searchCriteria)
+ ->getItems();
+ $product = $items ? array_shift($items) : null;
+
+ return $product;
+ }
+}
diff --git a/app/code/Magento/Downloadable/Model/ResourceModel/Sample.php b/app/code/Magento/Downloadable/Model/ResourceModel/Sample.php
index 8d30322745b8d..b7b079d208d97 100644
--- a/app/code/Magento/Downloadable/Model/ResourceModel/Sample.php
+++ b/app/code/Magento/Downloadable/Model/ResourceModel/Sample.php
@@ -24,7 +24,7 @@ class Sample extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
/**
* @param \Magento\Framework\Model\ResourceModel\Db\Context $context
* @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
- * @param null $connectionName
+ * @param string|null $connectionName
*/
public function __construct(
\Magento\Framework\Model\ResourceModel\Db\Context $context,
@@ -126,7 +126,7 @@ public function getSearchableData($productId, $storeId)
)->join(
['cpe' => $this->getTable('catalog_product_entity')],
sprintf(
- 'cpe.entity_id = m.product_id',
+ 'cpe.%s = m.product_id',
$this->metadataPool->getMetadata(ProductInterface::class)->getLinkField()
),
[]
diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithInvalidDomainLinkUrlTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithInvalidDomainLinkUrlTest.xml
index 9ad20385519d1..34b9701f2dca5 100644
--- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithInvalidDomainLinkUrlTest.xml
+++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithInvalidDomainLinkUrlTest.xml
@@ -37,7 +37,7 @@
-
+
diff --git a/app/code/Magento/Downloadable/Test/Unit/Controller/Download/LinkSampleTest.php b/app/code/Magento/Downloadable/Test/Unit/Controller/Download/LinkSampleTest.php
deleted file mode 100644
index 725c06004f117..0000000000000
--- a/app/code/Magento/Downloadable/Test/Unit/Controller/Download/LinkSampleTest.php
+++ /dev/null
@@ -1,237 +0,0 @@
-objectManagerHelper = new ObjectManagerHelper($this);
-
- $this->request = $this->getMockForAbstractClass(RequestInterface::class);
- $this->response = $this->getMockBuilder(ResponseInterface::class)
- ->addMethods(['setHttpResponseCode', 'clearBody', 'sendHeaders', 'setHeader', 'setRedirect'])
- ->onlyMethods(['sendResponse'])
- ->getMockForAbstractClass();
-
- $this->helperData = $this->createPartialMock(
- Data::class,
- ['getIsShareable']
- );
- $this->downloadHelper = $this->createPartialMock(
- Download::class,
- [
- 'setResource',
- 'getFilename',
- 'getContentType',
- 'getFileSize',
- 'getContentDisposition',
- 'output'
- ]
- );
- $this->product = $this->getMockBuilder(Product::class)
- ->addMethods(['_wakeup'])
- ->onlyMethods(['load', 'getId', 'getProductUrl', 'getName'])
- ->disableOriginalConstructor()
- ->getMock();
- $this->messageManager = $this->getMockForAbstractClass(ManagerInterface::class);
- $this->redirect = $this->getMockForAbstractClass(RedirectInterface::class);
- $this->urlInterface = $this->getMockForAbstractClass(UrlInterface::class);
- $this->salabilityCheckerMock = $this->createMock(SalabilityChecker::class);
- $this->objectManager = $this->createPartialMock(
- \Magento\Framework\ObjectManager\ObjectManager::class,
- ['create', 'get']
- );
- $this->linkSample = $this->objectManagerHelper->getObject(
- LinkSample::class,
- [
- 'objectManager' => $this->objectManager,
- 'request' => $this->request,
- 'response' => $this->response,
- 'messageManager' => $this->messageManager,
- 'redirect' => $this->redirect,
- 'salabilityChecker' => $this->salabilityCheckerMock,
- ]
- );
- }
-
- /**
- * Execute Download link's sample action with Url link.
- *
- * @return void
- */
- public function testExecuteLinkTypeUrl()
- {
- $linkMock = $this->getMockBuilder(Link::class)
- ->disableOriginalConstructor()
- ->setMethods(['getId', 'load', 'getSampleType', 'getSampleUrl'])
- ->getMock();
-
- $this->request->expects($this->once())->method('getParam')->with('link_id', 0)->willReturn('some_link_id');
- $this->objectManager->expects($this->once())
- ->method('create')
- ->with(Link::class)
- ->willReturn($linkMock);
- $linkMock->expects($this->once())->method('load')->with('some_link_id')->willReturnSelf();
- $linkMock->expects($this->once())->method('getId')->willReturn('some_link_id');
- $this->salabilityCheckerMock->expects($this->once())->method('isSalable')->willReturn(true);
- $linkMock->expects($this->once())->method('getSampleType')->willReturn(
- Download::LINK_TYPE_URL
- );
- $linkMock->expects($this->once())->method('getSampleUrl')->willReturn('sample_url');
- $this->objectManager->expects($this->at(1))
- ->method('get')
- ->with(Download::class)
- ->willReturn($this->downloadHelper);
- $this->response->expects($this->once())->method('setHttpResponseCode')->with(200)->willReturnSelf();
- $this->response->expects($this->any())->method('setHeader')->willReturnSelf();
- $this->downloadHelper->expects($this->once())->method('output')->willThrowException(new \Exception());
- $this->messageManager->expects($this->once())
- ->method('addError')
- ->with('Sorry, there was an error getting requested content. Please contact the store owner.')
- ->willReturnSelf();
- $this->redirect->expects($this->once())->method('getRedirectUrl')->willReturn('redirect_url');
- $this->response->expects($this->once())->method('setRedirect')->with('redirect_url')->willReturnSelf();
-
- $this->assertEquals($this->response, $this->linkSample->execute());
- }
-
- /**
- * Execute Download link's sample action with File link.
- *
- * @return void
- */
- public function testExecuteLinkTypeFile()
- {
- $linkMock = $this->getMockBuilder(Link::class)
- ->disableOriginalConstructor()
- ->setMethods(['getId', 'load', 'getSampleType', 'getSampleUrl', 'getBaseSamplePath'])
- ->getMock();
- $fileMock = $this->getMockBuilder(File::class)
- ->disableOriginalConstructor()
- ->setMethods(['getFilePath', 'load', 'getSampleType', 'getSampleUrl'])
- ->getMock();
-
- $this->request->expects($this->once())->method('getParam')->with('link_id', 0)->willReturn('some_link_id');
- $this->objectManager->expects($this->at(0))
- ->method('create')
- ->with(Link::class)
- ->willReturn($linkMock);
- $linkMock->expects($this->once())->method('load')->with('some_link_id')->willReturnSelf();
- $linkMock->expects($this->once())->method('getId')->willReturn('some_link_id');
- $this->salabilityCheckerMock->expects($this->once())->method('isSalable')->willReturn(true);
- $linkMock->expects($this->any())->method('getSampleType')->willReturn(
- Download::LINK_TYPE_FILE
- );
- $this->objectManager->expects($this->at(1))
- ->method('get')
- ->with(File::class)
- ->willReturn($fileMock);
- $this->objectManager->expects($this->at(2))
- ->method('get')
- ->with(Link::class)
- ->willReturn($linkMock);
- $linkMock->expects($this->once())->method('getBaseSamplePath')->willReturn('downloadable/files/link_samples');
- $this->objectManager->expects($this->at(3))
- ->method('get')
- ->with(Download::class)
- ->willReturn($this->downloadHelper);
- $this->response->expects($this->once())->method('setHttpResponseCode')->with(200)->willReturnSelf();
- $this->response->expects($this->any())->method('setHeader')->willReturnSelf();
- $this->downloadHelper->expects($this->once())->method('output')->willThrowException(new \Exception());
- $this->messageManager->expects($this->once())
- ->method('addError')
- ->with('Sorry, there was an error getting requested content. Please contact the store owner.')
- ->willReturnSelf();
- $this->redirect->expects($this->once())->method('getRedirectUrl')->willReturn('redirect_url');
- $this->response->expects($this->once())->method('setRedirect')->with('redirect_url')->willReturnSelf();
-
- $this->assertEquals($this->response, $this->linkSample->execute());
- }
-}
diff --git a/app/code/Magento/Downloadable/Test/Unit/Controller/Download/SampleTest.php b/app/code/Magento/Downloadable/Test/Unit/Controller/Download/SampleTest.php
deleted file mode 100644
index 6dcd09a91dd2e..0000000000000
--- a/app/code/Magento/Downloadable/Test/Unit/Controller/Download/SampleTest.php
+++ /dev/null
@@ -1,232 +0,0 @@
-objectManagerHelper = new ObjectManagerHelper($this);
-
- $this->request = $this->getMockForAbstractClass(RequestInterface::class);
- $this->response = $this->getMockBuilder(ResponseInterface::class)
- ->addMethods(['setHttpResponseCode', 'clearBody', 'sendHeaders', 'setHeader', 'setRedirect'])
- ->onlyMethods(['sendResponse'])
- ->getMockForAbstractClass();
-
- $this->helperData = $this->createPartialMock(
- Data::class,
- ['getIsShareable']
- );
- $this->downloadHelper = $this->createPartialMock(
- Download::class,
- [
- 'setResource',
- 'getFilename',
- 'getContentType',
- 'getFileSize',
- 'getContentDisposition',
- 'output'
- ]
- );
- $this->product = $this->getMockBuilder(Product::class)
- ->addMethods(['_wakeup'])
- ->onlyMethods(['load', 'getId', 'getProductUrl', 'getName'])
- ->disableOriginalConstructor()
- ->getMock();
- $this->messageManager = $this->getMockForAbstractClass(ManagerInterface::class);
- $this->redirect = $this->getMockForAbstractClass(RedirectInterface::class);
- $this->urlInterface = $this->getMockForAbstractClass(UrlInterface::class);
- $this->salabilityCheckerMock = $this->createMock(SalabilityChecker::class);
- $this->objectManager = $this->createPartialMock(
- \Magento\Framework\ObjectManager\ObjectManager::class,
- ['create', 'get']
- );
- $this->sample = $this->objectManagerHelper->getObject(
- Sample::class,
- [
- 'objectManager' => $this->objectManager,
- 'request' => $this->request,
- 'response' => $this->response,
- 'messageManager' => $this->messageManager,
- 'redirect' => $this->redirect,
- 'salabilityChecker' => $this->salabilityCheckerMock,
- ]
- );
- }
-
- /**
- * Execute Download sample action with Sample Url.
- *
- * @return void
- */
- public function testExecuteSampleWithUrlType()
- {
- $sampleMock = $this->getMockBuilder(\Magento\Downloadable\Model\Sample::class)
- ->disableOriginalConstructor()
- ->setMethods(['getId', 'load', 'getSampleType', 'getSampleUrl'])
- ->getMock();
-
- $this->request->expects($this->once())->method('getParam')->with('sample_id', 0)->willReturn('some_sample_id');
- $this->objectManager->expects($this->once())
- ->method('create')
- ->with(\Magento\Downloadable\Model\Sample::class)
- ->willReturn($sampleMock);
- $sampleMock->expects($this->once())->method('load')->with('some_sample_id')->willReturnSelf();
- $sampleMock->expects($this->once())->method('getId')->willReturn('some_link_id');
- $this->salabilityCheckerMock->expects($this->once())->method('isSalable')->willReturn(true);
- $sampleMock->expects($this->once())->method('getSampleType')->willReturn(
- Download::LINK_TYPE_URL
- );
- $sampleMock->expects($this->once())->method('getSampleUrl')->willReturn('sample_url');
- $this->objectManager->expects($this->at(1))
- ->method('get')
- ->with(Download::class)
- ->willReturn($this->downloadHelper);
- $this->response->expects($this->once())->method('setHttpResponseCode')->with(200)->willReturnSelf();
- $this->response->expects($this->any())->method('setHeader')->willReturnSelf();
- $this->downloadHelper->expects($this->once())->method('output')->willThrowException(new \Exception());
- $this->messageManager->expects($this->once())
- ->method('addError')
- ->with('Sorry, there was an error getting requested content. Please contact the store owner.')
- ->willReturnSelf();
- $this->redirect->expects($this->once())->method('getRedirectUrl')->willReturn('redirect_url');
- $this->response->expects($this->once())->method('setRedirect')->with('redirect_url')->willReturnSelf();
-
- $this->assertEquals($this->response, $this->sample->execute());
- }
-
- /**
- * Execute Download sample action with Sample File.
- *
- * @return void
- */
- public function testExecuteSampleWithFileType()
- {
- $sampleMock = $this->getMockBuilder(\Magento\Downloadable\Model\Sample::class)
- ->disableOriginalConstructor()
- ->setMethods(['getId', 'load', 'getSampleType', 'getSampleUrl', 'getBaseSamplePath'])
- ->getMock();
- $fileHelperMock = $this->getMockBuilder(File::class)
- ->disableOriginalConstructor()
- ->setMethods(['getFilePath'])
- ->getMock();
-
- $this->request->expects($this->once())->method('getParam')->with('sample_id', 0)->willReturn('some_sample_id');
- $this->objectManager->expects($this->at(0))
- ->method('create')
- ->with(\Magento\Downloadable\Model\Sample::class)
- ->willReturn($sampleMock);
- $sampleMock->expects($this->once())->method('load')->with('some_sample_id')->willReturnSelf();
- $sampleMock->expects($this->once())->method('getId')->willReturn('some_sample_id');
- $this->salabilityCheckerMock->expects($this->once())->method('isSalable')->willReturn(true);
- $sampleMock->expects($this->any())->method('getSampleType')->willReturn(
- Download::LINK_TYPE_FILE
- );
- $this->objectManager->expects($this->at(1))
- ->method('get')
- ->with(File::class)
- ->willReturn($fileHelperMock);
- $fileHelperMock->expects($this->once())->method('getFilePath')->willReturn('file_path');
- $this->objectManager->expects($this->at(2))
- ->method('get')
- ->with(Download::class)
- ->willReturn($this->downloadHelper);
- $this->response->expects($this->once())->method('setHttpResponseCode')->with(200)->willReturnSelf();
- $this->response->expects($this->any())->method('setHeader')->willReturnSelf();
- $this->downloadHelper->expects($this->once())->method('output')->willThrowException(new \Exception());
- $this->messageManager->expects($this->once())
- ->method('addError')
- ->with('Sorry, there was an error getting requested content. Please contact the store owner.')
- ->willReturnSelf();
- $this->redirect->expects($this->once())->method('getRedirectUrl')->willReturn('redirect_url');
- $this->response->expects($this->once())->method('setRedirect')->with('redirect_url')->willReturnSelf();
-
- $this->assertEquals($this->response, $this->sample->execute());
- }
-}
diff --git a/app/code/Magento/Eav/Model/Validator/Attribute/Data.php b/app/code/Magento/Eav/Model/Validator/Attribute/Data.php
index 15dcea077c887..7e434166a15be 100644
--- a/app/code/Magento/Eav/Model/Validator/Attribute/Data.php
+++ b/app/code/Magento/Eav/Model/Validator/Attribute/Data.php
@@ -23,12 +23,12 @@ class Data extends \Magento\Framework\Validator\AbstractValidator
/**
* @var array
*/
- protected $_attributesWhiteList = [];
+ protected $allowedAttributesList = [];
/**
* @var array
*/
- protected $_attributesBlackList = [];
+ protected $deniedAttributesList = [];
/**
* @var array
@@ -68,9 +68,9 @@ public function setAttributes(array $attributes)
* @param array $attributesCodes
* @return $this
*/
- public function setAttributesWhiteList(array $attributesCodes)
+ public function setAllowedAttributesList(array $attributesCodes)
{
- $this->_attributesWhiteList = $attributesCodes;
+ $this->allowedAttributesList = $attributesCodes;
return $this;
}
@@ -82,9 +82,9 @@ public function setAttributesWhiteList(array $attributesCodes)
* @param array $attributesCodes
* @return $this
*/
- public function setAttributesBlackList(array $attributesCodes)
+ public function setDeniedAttributesList(array $attributesCodes)
{
- $this->_attributesBlackList = $attributesCodes;
+ $this->deniedAttributesList = $attributesCodes;
return $this;
}
@@ -171,11 +171,11 @@ protected function _getAttributes($entity)
$attributesCodes[] = $attributeCode;
}
- $ignoreAttributes = $this->_attributesBlackList;
- if ($this->_attributesWhiteList) {
+ $ignoreAttributes = $this->deniedAttributesList;
+ if ($this->allowedAttributesList) {
$ignoreAttributes = array_merge(
$ignoreAttributes,
- array_diff($attributesCodes, $this->_attributesWhiteList)
+ array_diff($attributesCodes, $this->allowedAttributesList)
);
}
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 774b968f1b697..a8ecbb8371ac9 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
@@ -249,10 +249,10 @@ public function testIsValidAttributesFromCollection()
}
/**
- * @dataProvider whiteBlackListProvider
+ * @dataProvider allowDenyListProvider
* @param callable $callback
*/
- public function testIsValidBlackListWhiteListChecks($callback)
+ public function testIsValidExclusionInclusionListChecks($callback)
{
$attribute = $this->_getAttributeMock(
[
@@ -302,19 +302,19 @@ public function testIsValidBlackListWhiteListChecks($callback)
/**
* @return array
*/
- public function whiteBlackListProvider()
+ public function allowDenyListProvider()
{
- $whiteCallback = function ($validator) {
- $validator->setAttributesWhiteList(['attribute']);
+ $allowedCallbackList = function ($validator) {
+ $validator->setAllowedAttributesList(['attribute']);
};
- $blackCallback = function ($validator) {
- $validator->setAttributesBlackList(['attribute2']);
+ $deniedCallbackList = function ($validator) {
+ $validator->setDeniedAttributesList(['attribute2']);
};
- return ['white_list' => [$whiteCallback], 'black_list' => [$blackCallback]];
+ return ['allowed' => [$allowedCallbackList], 'denied' => [$deniedCallbackList]];
}
- public function testSetAttributesWhiteList()
+ public function testSetAttributesAllowedList()
{
$this->markTestSkipped('Skipped in #27500 due to testing protected/private methods and properties');
@@ -328,12 +328,14 @@ public function testSetAttributesWhiteList()
)
->getMock();
$validator = new Data($attrDataFactory);
- $result = $validator->setAttributesWhiteList($attributes);
- $this->assertAttributeEquals($attributes, '_attributesWhiteList', $validator);
+ $result = $validator->setIncludedAttributesList($attributes);
+
+ // phpstan:ignore
+ $this->assertAttributeEquals($attributes, '_attributesAllowed', $validator);
$this->assertEquals($validator, $result);
}
- public function testSetAttributesBlackList()
+ public function testSetAttributesDeniedList()
{
$this->markTestSkipped('Skipped in #27500 due to testing protected/private methods and properties');
@@ -347,8 +349,9 @@ public function testSetAttributesBlackList()
)
->getMock();
$validator = new Data($attrDataFactory);
- $result = $validator->setAttributesBlackList($attributes);
- $this->assertAttributeEquals($attributes, '_attributesBlackList', $validator);
+ $result = $validator->setDeniedAttributesList($attributes);
+ // phpstan:ignore
+ $this->assertAttributeEquals($attributes, '_attributesDenied', $validator);
$this->assertEquals($validator, $result);
}
diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml
index 633889e70591b..633e67dfe698e 100644
--- a/app/code/Magento/Elasticsearch/etc/di.xml
+++ b/app/code/Magento/Elasticsearch/etc/di.xml
@@ -537,7 +537,7 @@
-
+
- Elasticsearch 2
diff --git a/app/code/Magento/Fedex/etc/wsdl/RateService_v10.wsdl b/app/code/Magento/Fedex/etc/wsdl/RateService_v10.wsdl
index 62795f07239a6..3629bb424f207 100644
--- a/app/code/Magento/Fedex/etc/wsdl/RateService_v10.wsdl
+++ b/app/code/Magento/Fedex/etc/wsdl/RateService_v10.wsdl
@@ -472,7 +472,7 @@
- For international multiple piece shipments, commodity information must be passed in the Master and on each child transaction.
+ For international multiple piece shipments, commodity information must be passed in the Main and on each child transaction.
If this shipment contains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request.
@@ -983,7 +983,7 @@
- The total customs value for the shipment. This total will rrepresent th esum of the values of all commodities, and may include freight, miscellaneous, and insurance charges. Must contain 2 explicit decimal positions with a max length of 17 including the decimal. For Express International MPS, the Total Customs Value is in the master transaction and all child transactions
+ The total customs value for the shipment. This total will rrepresent th esum of the values of all commodities, and may include freight, miscellaneous, and insurance charges. Must contain 2 explicit decimal positions with a max length of 17 including the decimal. For Express International MPS, the Total Customs Value is in the main transaction and all child transactions
@@ -1005,7 +1005,7 @@
- For international multiple piece shipments, commodity information must be passed in the Master and on each child transaction.
+ For international multiple piece shipments, commodity information must be passed in the Main and on each child transaction.
If this shipment contains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request.
@@ -4867,4 +4867,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Fedex/etc/wsdl/RateService_v9.wsdl b/app/code/Magento/Fedex/etc/wsdl/RateService_v9.wsdl
index 17a6f74cc09b8..2f3feecb58084 100644
--- a/app/code/Magento/Fedex/etc/wsdl/RateService_v9.wsdl
+++ b/app/code/Magento/Fedex/etc/wsdl/RateService_v9.wsdl
@@ -471,7 +471,7 @@
- For international multiple piece shipments, commodity information must be passed in the Master and on each child transaction.
+ For international multiple piece shipments, commodity information must be passed in the Main and on each child transaction.
If this shipment commitment more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request.
@@ -983,7 +983,7 @@
- The total customs value for the shipment. This total will rrepresent th esum of the values of all commodities, and may include freight, miscellaneous, and insurance charges. Must contain 2 explicit decimal positions with a max length of 17 including the decimal. For Express International MPS, the Total Customs Value is in the master transaction and all child transactions
+ The total customs value for the shipment. This total will rrepresent th esum of the values of all commodities, and may include freight, miscellaneous, and insurance charges. Must contain 2 explicit decimal positions with a max length of 17 including the decimal. For Express International MPS, the Total Customs Value is in the main transaction and all child transactions
@@ -1005,7 +1005,7 @@
- For international multiple piece shipments, commodity information must be passed in the Master and on each child transaction.
+ For international multiple piece shipments, commodity information must be passed in the Main and on each child transaction.
If this shipment contains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request.
diff --git a/app/code/Magento/Fedex/etc/wsdl/ShipService_v10.wsdl b/app/code/Magento/Fedex/etc/wsdl/ShipService_v10.wsdl
index 54bb57d490c76..439d032a61fd0 100644
--- a/app/code/Magento/Fedex/etc/wsdl/ShipService_v10.wsdl
+++ b/app/code/Magento/Fedex/etc/wsdl/ShipService_v10.wsdl
@@ -497,7 +497,7 @@
- For international multiple piece shipments, commodity information must be passed in the Master and on each child transaction.
+ For international multiple piece shipments, commodity information must be passed in the Main and on each child transaction.
If this shipment contains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request.
@@ -724,7 +724,7 @@
- The master tracking number and form id of this multiple piece shipment. This information is to be provided for each subsequent of a multiple piece shipment.
+ The main tracking number and form id of this multiple piece shipment. This information is to be provided for each subsequent of a multiple piece shipment.
diff --git a/app/code/Magento/Fedex/etc/wsdl/ShipService_v9.wsdl b/app/code/Magento/Fedex/etc/wsdl/ShipService_v9.wsdl
index d8dc0fdfed4ab..a449bf41dbd68 100644
--- a/app/code/Magento/Fedex/etc/wsdl/ShipService_v9.wsdl
+++ b/app/code/Magento/Fedex/etc/wsdl/ShipService_v9.wsdl
@@ -497,7 +497,7 @@
- For international multiple piece shipments, commodity information must be passed in the Master and on each child transaction.
+ For international multiple piece shipments, commodity information must be passed in the Main and on each child transaction.
If this shipment contains more than four commodity line items, the four highest valued should be included in the first 4 occurrences for this request.
@@ -724,7 +724,7 @@
- The master tracking number and form id of this multiple piece shipment. This information is to be provided for each subsequent of a multiple piece shipment.
+ The main tracking number and form id of this multiple piece shipment. This information is to be provided for each subsequent of a multiple piece shipment.
diff --git a/app/code/Magento/GraphQl/composer.json b/app/code/Magento/GraphQl/composer.json
index 904d41c97953e..401e77a787acf 100644
--- a/app/code/Magento/GraphQl/composer.json
+++ b/app/code/Magento/GraphQl/composer.json
@@ -5,10 +5,10 @@
"require": {
"php": "~7.3.0||~7.4.0",
"magento/module-eav": "*",
- "magento/framework": "*"
+ "magento/framework": "*",
+ "magento/module-webapi": "*"
},
"suggest": {
- "magento/module-webapi": "*",
"magento/module-graph-ql-cache": "*"
},
"license": [
diff --git a/app/code/Magento/GraphQl/etc/schema.graphqls b/app/code/Magento/GraphQl/etc/schema.graphqls
index fccde015c3388..0212d32db0f2f 100644
--- a/app/code/Magento/GraphQl/etc/schema.graphqls
+++ b/app/code/Magento/GraphQl/etc/schema.graphqls
@@ -79,6 +79,12 @@ input FilterMatchTypeInput @doc(description: "Defines a filter that performs a f
match: String @doc(description: "One or more words to filter on")
}
+input FilterStringTypeInput @doc(description: "Defines a filter for an input string.") {
+ in: [String] @doc(description: "Filters items that are exactly the same as entries specified in an array of strings.")
+ eq: String @doc(description: "Filters items that are exactly the same as the specified string.")
+ match: String @doc(description: "Defines a filter that performs a fuzzy search using the specified string.")
+}
+
type SearchResultPageInfo @doc(description: "SearchResultPageInfo provides navigation for the query response") {
page_size: Int @doc(description: "Specifies the maximum number of items to return")
current_page: Int @doc(description: "Specifies which page of results to return")
diff --git a/app/code/Magento/ImportExport/Model/Import/AbstractEntity.php b/app/code/Magento/ImportExport/Model/Import/AbstractEntity.php
index 5bd956c1bc322..9bf5b945c8fbd 100644
--- a/app/code/Magento/ImportExport/Model/Import/AbstractEntity.php
+++ b/app/code/Magento/ImportExport/Model/Import/AbstractEntity.php
@@ -15,6 +15,7 @@
/**
* Import entity abstract model
*
+ * phpcs:disable Magento2.Classes.AbstractApi
* @api
*
* @SuppressWarnings(PHPMD.TooManyFields)
@@ -335,6 +336,8 @@ public function __construct(
}
/**
+ * Returns Error aggregator
+ *
* @return ProcessingErrorAggregatorInterface
*/
public function getErrorAggregator()
@@ -413,7 +416,7 @@ protected function _saveValidatedBunches()
$source->rewind();
$this->_dataSourceModel->cleanBunches();
- $masterAttributeCode = $this->getMasterAttributeCode();
+ $mainAttributeCode = $this->getMasterAttributeCode();
while ($source->valid() || count($bunchRows) || isset($entityGroup)) {
if ($startNewBunch || !$source->valid()) {
@@ -453,7 +456,7 @@ protected function _saveValidatedBunches()
continue;
}
- if (isset($rowData[$masterAttributeCode]) && trim($rowData[$masterAttributeCode])) {
+ if (isset($rowData[$mainAttributeCode]) && trim($rowData[$mainAttributeCode])) {
/* Add entity group that passed validation to bunch */
if (isset($entityGroup)) {
foreach ($entityGroup as $key => $value) {
@@ -590,6 +593,7 @@ public function getBehavior(array $rowData = null)
* Get default import behavior
*
* @return string
+ * phpcs:disable Magento2.Functions.StaticFunction
*/
public static function getDefaultBehavior()
{
@@ -652,7 +656,9 @@ public function isAttributeParticular($attributeCode)
}
/**
- * @return string the master attribute code to use in an import
+ * Returns the master attribute code to use in an import
+ *
+ * @return string
*/
public function getMasterAttributeCode()
{
diff --git a/app/code/Magento/MediaGallery/Model/Asset.php b/app/code/Magento/MediaGallery/Model/Asset.php
index 78b9477a70b08..7a4e51709dc0a 100644
--- a/app/code/Magento/MediaGallery/Model/Asset.php
+++ b/app/code/Magento/MediaGallery/Model/Asset.php
@@ -32,11 +32,21 @@ class Asset implements AssetInterface
*/
private $title;
+ /**
+ * @var string|null
+ */
+ private $description;
+
/**
* @var string|null
*/
private $source;
+ /**
+ * @var string|null
+ */
+ private $hash;
+
/**
* @var string
*/
@@ -80,7 +90,9 @@ class Asset implements AssetInterface
* @param int $size
* @param int|null $id
* @param string|null $title
+ * @param string|null $description
* @param string|null $source
+ * @param string|null $hash
* @param string|null $createdAt
* @param string|null $updatedAt
* @param AssetExtensionInterface|null $extensionAttributes
@@ -93,7 +105,9 @@ public function __construct(
int $size,
?int $id = null,
?string $title = null,
+ ?string $description = null,
?string $source = null,
+ ?string $hash = null,
?string $createdAt = null,
?string $updatedAt = null,
?AssetExtensionInterface $extensionAttributes = null
@@ -105,7 +119,9 @@ public function __construct(
$this->size = $size;
$this->id = $id;
$this->title = $title;
+ $this->description = $description;
$this->source = $source;
+ $this->hash = $hash;
$this->createdAt = $createdAt;
$this->updatedAt = $updatedAt;
$this->extensionAttributes = $extensionAttributes;
@@ -135,6 +151,14 @@ public function getTitle(): ?string
return $this->title;
}
+ /**
+ * @inheritdoc
+ */
+ public function getDescription(): ?string
+ {
+ return $this->description;
+ }
+
/**
* @inheritdoc
*/
@@ -143,6 +167,14 @@ public function getSource(): ?string
return $this->source;
}
+ /**
+ * @inheritdoc
+ */
+ public function getHash(): ?string
+ {
+ return $this->hash;
+ }
+
/**
* @inheritdoc
*/
diff --git a/app/code/Magento/MediaGallery/Model/Asset/Command/GetById.php b/app/code/Magento/MediaGallery/Model/Asset/Command/GetById.php
index b2f900233e46a..71e2cb70663f3 100644
--- a/app/code/Magento/MediaGallery/Model/Asset/Command/GetById.php
+++ b/app/code/Magento/MediaGallery/Model/Asset/Command/GetById.php
@@ -94,7 +94,9 @@ public function execute(int $mediaAssetId): AssetInterface
'id' => $mediaAssetData['id'],
'path' => $mediaAssetData['path'],
'title' => $mediaAssetData['title'],
+ 'description' => $mediaAssetData['description'],
'source' => $mediaAssetData['source'],
+ 'hash' => $mediaAssetData['hash'],
'contentType' => $mediaAssetData['content_type'],
'width' => $mediaAssetData['width'],
'height' => $mediaAssetData['height'],
diff --git a/app/code/Magento/MediaGallery/Model/Asset/Command/GetByPath.php b/app/code/Magento/MediaGallery/Model/Asset/Command/GetByPath.php
index d9faad62b2cd1..02512a12f9d07 100644
--- a/app/code/Magento/MediaGallery/Model/Asset/Command/GetByPath.php
+++ b/app/code/Magento/MediaGallery/Model/Asset/Command/GetByPath.php
@@ -86,7 +86,9 @@ public function execute(string $path): AssetInterface
'id' => $data['id'],
'path' => $data['path'],
'title' => $data['title'],
+ 'description' => $data['description'],
'source' => $data['source'],
+ 'hash' => $data['hash'],
'contentType' => $data['content_type'],
'width' => $data['width'],
'height' => $data['height'],
diff --git a/app/code/Magento/MediaGallery/Model/Directory/Command/CreateByPaths.php b/app/code/Magento/MediaGallery/Model/Directory/Command/CreateByPaths.php
index 4d87c1aa95285..f33c22a18b4b8 100644
--- a/app/code/Magento/MediaGallery/Model/Directory/Command/CreateByPaths.php
+++ b/app/code/Magento/MediaGallery/Model/Directory/Command/CreateByPaths.php
@@ -10,7 +10,7 @@
use Magento\Cms\Model\Wysiwyg\Images\Storage;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\MediaGalleryApi\Api\CreateDirectoriesByPathsInterface;
-use Magento\MediaGalleryApi\Api\IsPathBlacklistedInterface;
+use Magento\MediaGalleryApi\Api\IsPathExcludedInterface;
use Psr\Log\LoggerInterface;
/**
@@ -29,23 +29,23 @@ class CreateByPaths implements CreateDirectoriesByPathsInterface
private $storage;
/**
- * @var IsPathBlacklistedInterface
+ * @var IsPathExcludedInterface
*/
- private $isPathBlacklisted;
+ private $isPathExcluded;
/**
* @param LoggerInterface $logger
* @param Storage $storage
- * @param IsPathBlacklistedInterface $isPathBlacklisted
+ * @param IsPathExcludedInterface $isPathExcluded
*/
public function __construct(
LoggerInterface $logger,
Storage $storage,
- IsPathBlacklistedInterface $isPathBlacklisted
+ IsPathExcludedInterface $isPathExcluded
) {
$this->logger = $logger;
$this->storage = $storage;
- $this->isPathBlacklisted = $isPathBlacklisted;
+ $this->isPathExcluded = $isPathExcluded;
}
/**
@@ -55,7 +55,7 @@ public function execute(array $paths): void
{
$failedPaths = [];
foreach ($paths as $path) {
- if ($this->isPathBlacklisted->execute($path)) {
+ if ($this->isPathExcluded->execute($path)) {
$failedPaths[] = $path;
continue;
}
diff --git a/app/code/Magento/MediaGallery/Model/Directory/Command/DeleteByPaths.php b/app/code/Magento/MediaGallery/Model/Directory/Command/DeleteByPaths.php
index d46fb854fff22..2e45000c07225 100644
--- a/app/code/Magento/MediaGallery/Model/Directory/Command/DeleteByPaths.php
+++ b/app/code/Magento/MediaGallery/Model/Directory/Command/DeleteByPaths.php
@@ -10,7 +10,7 @@
use Magento\Cms\Model\Wysiwyg\Images\Storage;
use Magento\Framework\Exception\CouldNotDeleteException;
use Magento\MediaGalleryApi\Api\DeleteDirectoriesByPathsInterface;
-use Magento\MediaGalleryApi\Api\IsPathBlacklistedInterface;
+use Magento\MediaGalleryApi\Api\IsPathExcludedInterface;
use Psr\Log\LoggerInterface;
/**
@@ -29,23 +29,23 @@ class DeleteByPaths implements DeleteDirectoriesByPathsInterface
private $storage;
/**
- * @var IsPathBlacklistedInterface
+ * @var IsPathExcludedInterface
*/
- private $isPathBlacklisted;
+ private $isPathExcluded;
/**
* @param LoggerInterface $logger
* @param Storage $storage
- * @param IsPathBlacklistedInterface $isPathBlacklisted
+ * @param IsPathExcludedInterface $isPathExcluded
*/
public function __construct(
LoggerInterface $logger,
Storage $storage,
- IsPathBlacklistedInterface $isPathBlacklisted
+ IsPathExcludedInterface $isPathExcluded
) {
$this->logger = $logger;
$this->storage = $storage;
- $this->isPathBlacklisted = $isPathBlacklisted;
+ $this->isPathExcluded = $isPathExcluded;
}
/**
@@ -55,7 +55,7 @@ public function execute(array $paths): void
{
$failedPaths = [];
foreach ($paths as $path) {
- if ($this->isPathBlacklisted->execute($path)) {
+ if ($this->isPathExcluded->execute($path)) {
$failedPaths[] = $path;
continue;
}
diff --git a/app/code/Magento/MediaGallery/Model/Directory/Config/Converter.php b/app/code/Magento/MediaGallery/Model/Directory/Config/Converter.php
index 91f16d246f636..3d9911c805efb 100644
--- a/app/code/Magento/MediaGallery/Model/Directory/Config/Converter.php
+++ b/app/code/Magento/MediaGallery/Model/Directory/Config/Converter.php
@@ -15,9 +15,9 @@
class Converter implements ConverterInterface
{
/**
- * Blacklist tag name
+ * Excluded list tag name
*/
- private const BLACKLIST_TAG_NAME = 'blacklist';
+ private const EXCLUDED_LIST_TAG_NAME = 'exclude';
/**
* Patterns tag name
@@ -43,12 +43,12 @@ public function convert($source): array
throw new \InvalidArgumentException('The source should be instance of DOMDocument');
}
- foreach ($source->getElementsByTagName(self::BLACKLIST_TAG_NAME) as $blacklist) {
- $result[self::BLACKLIST_TAG_NAME] = [];
- foreach ($blacklist->getElementsByTagName(self::PATTERNS_TAG_NAME) as $patterns) {
- $result[self::BLACKLIST_TAG_NAME][self::PATTERNS_TAG_NAME] = [];
+ foreach ($source->getElementsByTagName(self::EXCLUDED_LIST_TAG_NAME) as $excludedList) {
+ $result[self::EXCLUDED_LIST_TAG_NAME] = [];
+ foreach ($excludedList->getElementsByTagName(self::PATTERNS_TAG_NAME) as $patterns) {
+ $result[self::EXCLUDED_LIST_TAG_NAME][self::PATTERNS_TAG_NAME] = [];
foreach ($patterns->getElementsByTagName(self::PATTERN_TAG_NAME) as $pattern) {
- $result[self::BLACKLIST_TAG_NAME][self::PATTERNS_TAG_NAME]
+ $result[self::EXCLUDED_LIST_TAG_NAME][self::PATTERNS_TAG_NAME]
[$pattern->attributes->getNamedItem('name')->nodeValue] = $pattern->nodeValue;
}
}
diff --git a/app/code/Magento/MediaGallery/Model/Directory/BlacklistPatternsConfig.php b/app/code/Magento/MediaGallery/Model/Directory/ExcludedPatternsConfig.php
similarity index 68%
rename from app/code/Magento/MediaGallery/Model/Directory/BlacklistPatternsConfig.php
rename to app/code/Magento/MediaGallery/Model/Directory/ExcludedPatternsConfig.php
index 8fdd4f70d5060..29ed5fbf04ecd 100644
--- a/app/code/Magento/MediaGallery/Model/Directory/BlacklistPatternsConfig.php
+++ b/app/code/Magento/MediaGallery/Model/Directory/ExcludedPatternsConfig.php
@@ -8,14 +8,14 @@
namespace Magento\MediaGallery\Model\Directory;
use Magento\Framework\Config\DataInterface;
-use Magento\MediaGalleryApi\Model\BlacklistPatternsConfigInterface;
+use Magento\MediaGalleryApi\Model\ExcludedPatternsConfigInterface;
/**
* Media gallery directory config
*/
-class BlacklistPatternsConfig implements BlacklistPatternsConfigInterface
+class ExcludedPatternsConfig implements ExcludedPatternsConfigInterface
{
- private const XML_PATH_BLACKLIST_PATTERNS = 'blacklist/patterns';
+ private const XML_PATH_EXCLUDED_PATTERNS = 'exclude/patterns';
/**
* @var DataInterface
@@ -37,6 +37,6 @@ public function __construct(DataInterface $data)
*/
public function get() : array
{
- return $this->data->get(self::XML_PATH_BLACKLIST_PATTERNS);
+ return $this->data->get(self::XML_PATH_EXCLUDED_PATTERNS);
}
}
diff --git a/app/code/Magento/MediaGallery/Model/Directory/IsBlacklisted.php b/app/code/Magento/MediaGallery/Model/Directory/IsExcluded.php
similarity index 61%
rename from app/code/Magento/MediaGallery/Model/Directory/IsBlacklisted.php
rename to app/code/Magento/MediaGallery/Model/Directory/IsExcluded.php
index 0191b357aaefa..8fb0e03b76548 100644
--- a/app/code/Magento/MediaGallery/Model/Directory/IsBlacklisted.php
+++ b/app/code/Magento/MediaGallery/Model/Directory/IsExcluded.php
@@ -7,23 +7,23 @@
namespace Magento\MediaGallery\Model\Directory;
-use Magento\MediaGalleryApi\Api\IsPathBlacklistedInterface;
-use Magento\MediaGalleryApi\Model\BlacklistPatternsConfigInterface;
+use Magento\MediaGalleryApi\Api\IsPathExcludedInterface;
+use Magento\MediaGalleryApi\Model\ExcludedPatternsConfigInterface;
/**
- * Check if the path is blacklisted for media gallery. Directory path may be blacklisted if it's reserved by the system
+ * Check if the path is excluded for media gallery. Directory path may be blacklisted if it's reserved by the system
*/
-class IsBlacklisted implements IsPathBlacklistedInterface
+class IsExcluded implements IsPathExcludedInterface
{
/**
- * @var BlacklistPatternsConfigInterface
+ * @var ExcludedPatternsConfigInterface
*/
private $config;
/**
- * @param BlacklistPatternsConfigInterface $config
+ * @param ExcludedPatternsConfigInterface $config
*/
- public function __construct(BlacklistPatternsConfigInterface $config)
+ public function __construct(ExcludedPatternsConfigInterface $config)
{
$this->config = $config;
}
diff --git a/app/code/Magento/MediaGallery/Model/ResourceModel/GetAssetsByIds.php b/app/code/Magento/MediaGallery/Model/ResourceModel/GetAssetsByIds.php
index 53185939b2283..f73162b775683 100644
--- a/app/code/Magento/MediaGallery/Model/ResourceModel/GetAssetsByIds.php
+++ b/app/code/Magento/MediaGallery/Model/ResourceModel/GetAssetsByIds.php
@@ -65,7 +65,9 @@ public function execute(array $ids): array
'id' => $assetData['id'],
'path' => $assetData['path'],
'title' => $assetData['title'],
+ 'description' => $assetData['description'],
'source' => $assetData['source'],
+ 'hash' => $assetData['hash'],
'contentType' => $assetData['content_type'],
'width' => $assetData['width'],
'height' => $assetData['height'],
diff --git a/app/code/Magento/MediaGallery/Model/ResourceModel/GetAssetsByPaths.php b/app/code/Magento/MediaGallery/Model/ResourceModel/GetAssetsByPaths.php
index 5593083d9673a..b25d2e22aabd4 100644
--- a/app/code/Magento/MediaGallery/Model/ResourceModel/GetAssetsByPaths.php
+++ b/app/code/Magento/MediaGallery/Model/ResourceModel/GetAssetsByPaths.php
@@ -66,7 +66,9 @@ public function execute(array $paths): array
'id' => $assetData['id'],
'path' => $assetData['path'],
'title' => $assetData['title'],
+ 'description' => $assetData['description'],
'source' => $assetData['source'],
+ 'hash' => $assetData['hash'],
'contentType' => $assetData['content_type'],
'width' => $assetData['width'],
'height' => $assetData['height'],
diff --git a/app/code/Magento/MediaGallery/Model/ResourceModel/SaveAssets.php b/app/code/Magento/MediaGallery/Model/ResourceModel/SaveAssets.php
index ec08addf93462..801279aa7fd7d 100644
--- a/app/code/Magento/MediaGallery/Model/ResourceModel/SaveAssets.php
+++ b/app/code/Magento/MediaGallery/Model/ResourceModel/SaveAssets.php
@@ -60,7 +60,9 @@ public function execute(array $assets): void
'id' => $asset->getId(),
'path' => $asset->getPath(),
'title' => $asset->getTitle(),
+ 'description' => $asset->getDescription(),
'source' => $asset->getSource(),
+ 'hash' => $asset->getHash(),
'content_type' => $asset->getContentType(),
'width' => $asset->getWidth(),
'height' => $asset->getHeight(),
diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionDuringMediaAssetInitializationTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionDuringMediaAssetInitializationTest.php
index 09ce7ffe8ff20..5f99163db8f12 100644
--- a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionDuringMediaAssetInitializationTest.php
+++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionDuringMediaAssetInitializationTest.php
@@ -28,7 +28,9 @@ class GetByIdExceptionDuringMediaAssetInitializationTest extends TestCase
'id' => 45,
'path' => 'img.jpg',
'title' => 'Img',
+ 'description' => 'Img Description',
'source' => 'Adobe Stock',
+ 'hash' => 'hash',
'content_type' => 'image/jpeg',
'width' => 420,
'height' => 240,
diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionOnGetDataTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionOnGetDataTest.php
index 89efae07360b4..3b47b0036224b 100644
--- a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionOnGetDataTest.php
+++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionOnGetDataTest.php
@@ -29,7 +29,9 @@ class GetByIdExceptionOnGetDataTest extends TestCase
'id' => 45,
'path' => 'img.jpg',
'title' => 'Img',
+ 'description' => 'Img Description',
'source' => 'Adobe Stock',
+ 'hash' => 'hash',
'content_type' => 'image/jpeg',
'width' => 420,
'height' => 240,
diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdSuccessfulTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdSuccessfulTest.php
index 8b805d0256e37..2c24899746473 100644
--- a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdSuccessfulTest.php
+++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdSuccessfulTest.php
@@ -29,7 +29,9 @@ class GetByIdSuccessfulTest extends TestCase
'id' => 45,
'path' => 'img.jpg',
'title' => 'Img',
+ 'description' => 'Img Description',
'source' => 'Adobe Stock',
+ 'hash' => 'hash',
'content_type' => 'image/jpeg',
'width' => 420,
'height' => 240,
diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Directory/IsBlacklistedTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Directory/IsExcludedTest.php
similarity index 70%
rename from app/code/Magento/MediaGallery/Test/Unit/Model/Directory/IsBlacklistedTest.php
rename to app/code/Magento/MediaGallery/Test/Unit/Model/Directory/IsExcludedTest.php
index c96fd2ee54512..cc57b043954d7 100644
--- a/app/code/Magento/MediaGallery/Test/Unit/Model/Directory/IsBlacklistedTest.php
+++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Directory/IsExcludedTest.php
@@ -8,45 +8,45 @@
namespace Magento\MediaGallery\Test\Unit\Model\Directory;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
-use Magento\MediaGallery\Model\Directory\IsBlacklisted;
-use Magento\MediaGalleryApi\Model\BlacklistPatternsConfigInterface;
+use Magento\MediaGallery\Model\Directory\IsExcluded;
+use Magento\MediaGalleryApi\Model\ExcludedPatternsConfigInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
/**
- * Test for IsBlacklisted
+ * Test for IsExcluded
*/
-class IsBlacklistedTest extends TestCase
+class IsExcludedTest extends TestCase
{
/**
- * @var IsBlacklisted
+ * @var IsExcluded
*/
private $object;
/**
- * @var BlacklistPatternsConfigInterface|MockObject
+ * @var ExcludedPatternsConfigInterface|MockObject
*/
- private $config;
+ private $configMock;
/**
* Initialize basic test class mocks
*/
protected function setUp(): void
{
- $this->config = $this->getMockBuilder(BlacklistPatternsConfigInterface::class)
+ $this->configMock = $this->getMockBuilder(ExcludedPatternsConfigInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
- $this->config->expects($this->at(0))->method('get')->willReturn([
+ $this->configMock->expects($this->at(0))->method('get')->willReturn([
'tmp' => '/pub\/media\/tmp/',
'captcha' => '/pub\/media\/captcha/'
]);
- $this->object = (new ObjectManager($this))->getObject(IsBlacklisted::class, [
- 'config' => $this->config
+ $this->object = (new ObjectManager($this))->getObject(IsExcluded::class, [
+ 'config' => $this->configMock
]);
}
/**
- * Test if the directory path is blacklisted
+ * Test if the directory path is excluded
*
* @param string $path
* @param bool $isExcluded
diff --git a/app/code/Magento/MediaGallery/etc/db_schema.xml b/app/code/Magento/MediaGallery/etc/db_schema.xml
index 31a764ef00c4d..1001737daa8a7 100644
--- a/app/code/Magento/MediaGallery/etc/db_schema.xml
+++ b/app/code/Magento/MediaGallery/etc/db_schema.xml
@@ -10,7 +10,9 @@
+
+
diff --git a/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json b/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json
index 8f5098caa9753..b32dfbf082175 100644
--- a/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json
+++ b/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json
@@ -4,7 +4,9 @@
"id": true,
"path": true,
"title": true,
+ "description": true,
"source": true,
+ "hash": true,
"content_type": true,
"width": true,
"height": true,
diff --git a/app/code/Magento/MediaGallery/etc/di.xml b/app/code/Magento/MediaGallery/etc/di.xml
index a85c26e275226..bedb78758786b 100644
--- a/app/code/Magento/MediaGallery/etc/di.xml
+++ b/app/code/Magento/MediaGallery/etc/di.xml
@@ -21,7 +21,7 @@
-
+
@@ -40,7 +40,7 @@
Magento\MediaGallery\Model\Directory\Config\Converter
Magento\MediaGallery\Model\Directory\Config\SchemaLocator
- - name
+ - name
@@ -50,11 +50,10 @@
Media_Gallery_Patterns_CacheId
-
+
Magento\MediaGallery\Model\Directory\Config\Data
-
-
+
diff --git a/app/code/Magento/MediaGallery/etc/directory.xml b/app/code/Magento/MediaGallery/etc/directory.xml
index 92f50b2dd0a30..42094aff72640 100644
--- a/app/code/Magento/MediaGallery/etc/directory.xml
+++ b/app/code/Magento/MediaGallery/etc/directory.xml
@@ -6,7 +6,7 @@
*/
-->
-
+
/^captcha/
/^customer/
@@ -17,5 +17,5 @@
/^tmp/
/^\./
-
+
diff --git a/app/code/Magento/MediaGalleryApi/Api/Data/AssetInterface.php b/app/code/Magento/MediaGalleryApi/Api/Data/AssetInterface.php
index 5df420a274933..a747cb963baab 100644
--- a/app/code/Magento/MediaGalleryApi/Api/Data/AssetInterface.php
+++ b/app/code/Magento/MediaGalleryApi/Api/Data/AssetInterface.php
@@ -38,6 +38,13 @@ public function getPath(): string;
*/
public function getTitle(): ?string;
+ /**
+ * Get description
+ *
+ * @return string|null
+ */
+ public function getDescription(): ?string;
+
/**
* Get the name of the channel/stock/integration file was retrieved from. null if not identified.
*
@@ -45,6 +52,13 @@ public function getTitle(): ?string;
*/
public function getSource(): ?string;
+ /**
+ * Get file hash
+ *
+ * @return string|null
+ */
+ public function getHash(): ?string;
+
/**
* Get content type
*
diff --git a/app/code/Magento/MediaGalleryApi/Api/IsPathBlacklistedInterface.php b/app/code/Magento/MediaGalleryApi/Api/IsPathExcludedInterface.php
similarity index 71%
rename from app/code/Magento/MediaGalleryApi/Api/IsPathBlacklistedInterface.php
rename to app/code/Magento/MediaGalleryApi/Api/IsPathExcludedInterface.php
index cbd23ec3fbde7..1e41debb1b1c5 100644
--- a/app/code/Magento/MediaGalleryApi/Api/IsPathBlacklistedInterface.php
+++ b/app/code/Magento/MediaGalleryApi/Api/IsPathExcludedInterface.php
@@ -8,12 +8,12 @@
namespace Magento\MediaGalleryApi\Api;
/**
- * Check if the path is blacklisted for media gallery.
+ * Check if the path is excluded for media gallery.
*
- * Directory path may be blacklisted if it's reserved by the system.
+ * Directory path may be excluded if it's reserved by the system.
* @api
*/
-interface IsPathBlacklistedInterface
+interface IsPathExcludedInterface
{
/**
* Check if the path is excluded from displaying and processing in the media gallery
diff --git a/app/code/Magento/MediaGalleryApi/Model/BlacklistPatternsConfigInterface.php b/app/code/Magento/MediaGalleryApi/Model/ExcludedPatternsConfigInterface.php
similarity index 75%
rename from app/code/Magento/MediaGalleryApi/Model/BlacklistPatternsConfigInterface.php
rename to app/code/Magento/MediaGalleryApi/Model/ExcludedPatternsConfigInterface.php
index b4710f32e0c46..dd82f87780a49 100644
--- a/app/code/Magento/MediaGalleryApi/Model/BlacklistPatternsConfigInterface.php
+++ b/app/code/Magento/MediaGalleryApi/Model/ExcludedPatternsConfigInterface.php
@@ -7,9 +7,9 @@
namespace Magento\MediaGalleryApi\Model;
/**
- * Returns list of blacklist regexp patterns
+ * Returns list of excluded regexp patterns
*/
-interface BlacklistPatternsConfigInterface
+interface ExcludedPatternsConfigInterface
{
/**
* Get regexp patterns
diff --git a/app/code/Magento/MediaGalleryApi/etc/directory.xsd b/app/code/Magento/MediaGalleryApi/etc/directory.xsd
index 2ad76c8fcc9f2..2fb4fed028469 100644
--- a/app/code/Magento/MediaGalleryApi/etc/directory.xsd
+++ b/app/code/Magento/MediaGalleryApi/etc/directory.xsd
@@ -11,14 +11,14 @@
-
+
-
+
- Blacklist used for excluding directories from media gallery rendering and operations
+ List used for excluding directories from media gallery rendering and operations
diff --git a/app/code/Magento/MediaGalleryCatalog/etc/directory.xml b/app/code/Magento/MediaGalleryCatalog/etc/directory.xml
index eaced3f642f70..f1ec76a877368 100644
--- a/app/code/Magento/MediaGalleryCatalog/etc/directory.xml
+++ b/app/code/Magento/MediaGalleryCatalog/etc/directory.xml
@@ -6,9 +6,9 @@
*/
-->
-
+
/^catalog\/product/
-
+
diff --git a/app/code/Magento/Quote/Model/Quote/Address.php b/app/code/Magento/Quote/Model/Quote/Address.php
index 39148f990b714..4366ef7aaf969 100644
--- a/app/code/Magento/Quote/Model/Quote/Address.php
+++ b/app/code/Magento/Quote/Model/Quote/Address.php
@@ -1019,6 +1019,13 @@ public function collectShippingRates()
*/
public function requestShippingRates(AbstractItem $item = null)
{
+ $storeId = $this->getQuote()->getStoreId() ?: $this->storeManager->getStore()->getId();
+ $taxInclude = $this->_scopeConfig->getValue(
+ 'tax/calculation/price_includes_tax',
+ ScopeInterface::SCOPE_STORE,
+ $storeId
+ );
+
/** @var $request RateRequest */
$request = $this->_rateRequestFactory->create();
$request->setAllItems($item ? [$item] : $this->getAllItems());
@@ -1028,9 +1035,11 @@ public function requestShippingRates(AbstractItem $item = null)
$request->setDestStreet($this->getStreetFull());
$request->setDestCity($this->getCity());
$request->setDestPostcode($this->getPostcode());
- $request->setPackageValue($item ? $item->getBaseRowTotal() : $this->getBaseSubtotal());
+ $baseSubtotal = $taxInclude ? $this->getBaseSubtotalTotalInclTax() : $this->getBaseSubtotal();
+ $request->setPackageValue($item ? $item->getBaseRowTotal() : $baseSubtotal);
+ $baseSubtotalWithDiscount = $baseSubtotal + $this->getBaseDiscountAmount();
$packageWithDiscount = $item ? $item->getBaseRowTotal() -
- $item->getBaseDiscountAmount() : $this->getBaseSubtotalWithDiscount();
+ $item->getBaseDiscountAmount() : $baseSubtotalWithDiscount;
$request->setPackageValueWithDiscount($packageWithDiscount);
$request->setPackageWeight($item ? $item->getRowWeight() : $this->getWeight());
$request->setPackageQty($item ? $item->getQty() : $this->getItemQty());
@@ -1038,8 +1047,7 @@ public function requestShippingRates(AbstractItem $item = null)
/**
* Need for shipping methods that use insurance based on price of physical products
*/
- $packagePhysicalValue = $item ? $item->getBaseRowTotal() : $this->getBaseSubtotal() -
- $this->getBaseVirtualAmount();
+ $packagePhysicalValue = $item ? $item->getBaseRowTotal() : $baseSubtotal - $this->getBaseVirtualAmount();
$request->setPackagePhysicalValue($packagePhysicalValue);
$request->setFreeMethodWeight($item ? 0 : $this->getFreeMethodWeight());
@@ -1047,12 +1055,10 @@ public function requestShippingRates(AbstractItem $item = null)
/**
* Store and website identifiers specified from StoreManager
*/
+ $request->setStoreId($storeId);
if ($this->getQuote()->getStoreId()) {
- $storeId = $this->getQuote()->getStoreId();
- $request->setStoreId($storeId);
$request->setWebsiteId($this->storeManager->getStore($storeId)->getWebsiteId());
} else {
- $request->setStoreId($this->storeManager->getStore()->getId());
$request->setWebsiteId($this->storeManager->getWebsite()->getId());
}
$request->setFreeShipping($this->getFreeShipping());
diff --git a/app/code/Magento/Quote/Test/Mftf/Data/CustomerCartData.xml b/app/code/Magento/Quote/Test/Mftf/Data/CustomerCartData.xml
new file mode 100755
index 0000000000000..a14be3b533fa8
--- /dev/null
+++ b/app/code/Magento/Quote/Test/Mftf/Data/CustomerCartData.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+ ShippingAddressTX
+ BillingAddressTX
+ flatrate
+ flatrate
+
+
+
+
+ PaymentMethodCheckMoneyOrder
+ BillingAddressTX
+
+
diff --git a/app/code/Magento/Quote/Test/Mftf/Data/CustomerCartItemData.xml b/app/code/Magento/Quote/Test/Mftf/Data/CustomerCartItemData.xml
new file mode 100644
index 0000000000000..3681245311188
--- /dev/null
+++ b/app/code/Magento/Quote/Test/Mftf/Data/CustomerCartItemData.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ 1
+
+
diff --git a/app/code/Magento/Quote/Test/Mftf/Metadata/CustomerCartItemMeta.xml b/app/code/Magento/Quote/Test/Mftf/Metadata/CustomerCartItemMeta.xml
new file mode 100644
index 0000000000000..f5555394f8d4d
--- /dev/null
+++ b/app/code/Magento/Quote/Test/Mftf/Metadata/CustomerCartItemMeta.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ application/json
+
+ string
+ string
+ integer
+
+
+
diff --git a/app/code/Magento/Quote/Test/Mftf/Metadata/CustomerCartMeta.xml b/app/code/Magento/Quote/Test/Mftf/Metadata/CustomerCartMeta.xml
new file mode 100644
index 0000000000000..f233954f2cdcf
--- /dev/null
+++ b/app/code/Magento/Quote/Test/Mftf/Metadata/CustomerCartMeta.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+ application/json
+ string
+
+
+
+ application/json
+ string
+
+
+ string
+ string
+ string
+ integer
+ string
+
+ string
+
+ string
+ string
+ string
+ string
+ string
+
+
+ string
+ string
+ string
+ integer
+ string
+
+ string
+
+ string
+ string
+ string
+ string
+ string
+
+ string
+ string
+
+
+
+
+ application/json
+ string
+
+ string
+
+
+
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 a8fd794c08757..d4f6778a2ccb8 100644
--- a/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php
+++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php
@@ -352,10 +352,40 @@ public function testRequestShippingRates()
$currentCurrencyCode = 'UAH';
+ $this->quote->expects($this->any())
+ ->method('getStoreId')
+ ->willReturn($storeId);
+
+ $this->storeManager->expects($this->at(0))
+ ->method('getStore')
+ ->with($storeId)
+ ->willReturn($this->store);
+ $this->store->expects($this->any())
+ ->method('getWebsiteId')
+ ->willReturn($webSiteId);
+
+ $this->scopeConfig->expects($this->exactly(1))
+ ->method('getValue')
+ ->with(
+ 'tax/calculation/price_includes_tax',
+ ScopeInterface::SCOPE_STORE,
+ $storeId
+ )
+ ->willReturn(1);
+
/** @var RateRequest */
$request = $this->getMockBuilder(RateRequest::class)
->disableOriginalConstructor()
- ->setMethods(['setStoreId', 'setWebsiteId', 'setBaseCurrency', 'setPackageCurrency'])
+ ->setMethods(
+ [
+ 'setStoreId',
+ 'setWebsiteId',
+ 'setBaseCurrency',
+ 'setPackageCurrency',
+ 'getBaseSubtotalTotalInclTax',
+ 'getBaseSubtotal'
+ ]
+ )
->getMock();
/** @var Collection */
@@ -434,13 +464,6 @@ public function testRequestShippingRates()
$this->storeManager->method('getStore')
->willReturn($this->store);
- $this->storeManager->expects($this->once())
- ->method('getWebsite')
- ->willReturn($this->website);
-
- $this->store->method('getId')
- ->willReturn($storeId);
-
$this->store->method('getBaseCurrency')
->willReturn($baseCurrency);
@@ -452,10 +475,6 @@ public function testRequestShippingRates()
->method('getCurrentCurrencyCode')
->willReturn($currentCurrencyCode);
- $this->website->expects($this->once())
- ->method('getId')
- ->willReturn($webSiteId);
-
$this->addressRateFactory->expects($this->once())
->method('create')
->willReturn($rate);
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CustomerCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CustomerCart.php
index 0be95eccc39e5..e8aa8d612c670 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CustomerCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CustomerCart.php
@@ -7,17 +7,12 @@
namespace Magento\QuoteGraphQl\Model\Resolver;
-use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\QuoteGraphQl\Model\Cart\CreateEmptyCartForCustomer;
use Magento\GraphQl\Model\Query\ContextInterface;
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
-use Magento\Quote\Api\CartManagementInterface;
-use Magento\Quote\Model\QuoteIdMaskFactory;
-use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
-use Magento\Quote\Model\ResourceModel\Quote\QuoteIdMask as QuoteIdMaskResourceModel;
+use Magento\Quote\Model\Cart\CustomerCartResolver;
/**
* Get cart for the customer
@@ -25,48 +20,19 @@
class CustomerCart implements ResolverInterface
{
/**
- * @var CreateEmptyCartForCustomer
+ * @var CustomerCartResolver
*/
- private $createEmptyCartForCustomer;
+ private $customerCartResolver;
/**
- * @var CartManagementInterface
- */
- private $cartManagement;
-
- /**
- * @var QuoteIdMaskFactory
- */
- private $quoteIdMaskFactory;
-
- /**
- * @var QuoteIdMaskResourceModel
- */
- private $quoteIdMaskResourceModel;
- /**
- * @var QuoteIdToMaskedQuoteIdInterface
- */
- private $quoteIdToMaskedQuoteId;
-
- /**
- * @param CreateEmptyCartForCustomer $createEmptyCartForCustomer
- * @param CartManagementInterface $cartManagement
- * @param QuoteIdMaskFactory $quoteIdMaskFactory
- * @param QuoteIdMaskResourceModel $quoteIdMaskResourceModel
- * @param QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedQuoteId
+ * CustomerCart constructor.
+ *
+ * @param CustomerCartResolver $customerCartResolver
*/
public function __construct(
- CreateEmptyCartForCustomer $createEmptyCartForCustomer,
- CartManagementInterface $cartManagement,
- QuoteIdMaskFactory $quoteIdMaskFactory,
- QuoteIdMaskResourceModel $quoteIdMaskResourceModel,
- QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedQuoteId
+ CustomerCartResolver $customerCartResolver
) {
- $this->createEmptyCartForCustomer = $createEmptyCartForCustomer;
- $this->cartManagement = $cartManagement;
- $this->quoteIdMaskFactory = $quoteIdMaskFactory;
- $this->quoteIdMaskResourceModel = $quoteIdMaskResourceModel;
- $this->quoteIdToMaskedQuoteId = $quoteIdToMaskedQuoteId;
+ $this->customerCartResolver = $customerCartResolver;
}
/**
@@ -76,22 +42,17 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
{
$currentUserId = $context->getUserId();
- /** @var ContextInterface $context */
+ /**
+ * @var ContextInterface $context
+ */
if (false === $context->getExtensionAttributes()->getIsCustomer()) {
throw new GraphQlAuthorizationException(__('The request is allowed for logged in customer'));
}
- try {
- $cart = $this->cartManagement->getCartForCustomer($currentUserId);
- } catch (NoSuchEntityException $e) {
- $this->createEmptyCartForCustomer->execute($currentUserId, null);
- $cart = $this->cartManagement->getCartForCustomer($currentUserId);
- }
- $maskedId = $this->quoteIdToMaskedQuoteId->execute((int) $cart->getId());
- if (empty($maskedId)) {
- $quoteIdMask = $this->quoteIdMaskFactory->create();
- $quoteIdMask->setQuoteId((int) $cart->getId());
- $this->quoteIdMaskResourceModel->save($quoteIdMask);
+ try {
+ $cart = $this->customerCartResolver->resolve($currentUserId);
+ } catch (\Exception $e) {
+ $cart = null;
}
return [
diff --git a/app/code/Magento/Rule/Test/Mftf/Helper/RuleHelper.php b/app/code/Magento/Rule/Test/Mftf/Helper/RuleHelper.php
new file mode 100644
index 0000000000000..a8a9f78df7f28
--- /dev/null
+++ b/app/code/Magento/Rule/Test/Mftf/Helper/RuleHelper.php
@@ -0,0 +1,62 @@
+getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver');
+ /** @var FacebookWebDriver $webDriver */
+ $webDriver = $magentoWebDriver->webDriver;
+ $rows = $webDriver->findElements(WebDriverBy::cssSelector($firstNotEmptyRow));
+ while (!empty($rows)) {
+ $rows[0]->click();
+ $magentoWebDriver->waitForPageLoad(30);
+ $magentoWebDriver->click($deleteButton);
+ $magentoWebDriver->waitForPageLoad(30);
+ $magentoWebDriver->waitForElementVisible($modalAcceptButton, 10);
+ $magentoWebDriver->waitForPageLoad(60);
+ $magentoWebDriver->click($modalAcceptButton);
+ $magentoWebDriver->waitForPageLoad(60);
+ $magentoWebDriver->waitForLoadingMaskToDisappear();
+ $magentoWebDriver->waitForElementVisible($successMessageContainer, 10);
+ $magentoWebDriver->see($successMessage, $successMessageContainer);
+ $rows = $webDriver->findElements(WebDriverBy::cssSelector($firstNotEmptyRow));
+ }
+ } catch (\Exception $e) {
+ $this->fail($e->getMessage());
+ }
+ }
+}
diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Rss/OrderStatus.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Rss/OrderStatus.php
index 19d9b6f300eba..b1d2deb248ba1 100644
--- a/app/code/Magento/Sales/Model/ResourceModel/Order/Rss/OrderStatus.php
+++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Rss/OrderStatus.php
@@ -43,13 +43,13 @@ public function getAllCommentCollection($orderId)
$commentSelects = [];
foreach (['invoice', 'shipment', 'creditmemo'] as $entityTypeCode) {
$mainTable = $resource->getTableName('sales_' . $entityTypeCode);
- $slaveTable = $resource->getTableName('sales_' . $entityTypeCode . '_comment');
+ $commentTable = $resource->getTableName('sales_' . $entityTypeCode . '_comment');
$select = $read->select()->from(
['main' => $mainTable],
['entity_id' => 'order_id', 'entity_type_code' => new \Zend_Db_Expr("'{$entityTypeCode}'")]
)->join(
- ['slave' => $slaveTable],
- 'main.entity_id = slave.parent_id',
+ ['comment' => $commentTable],
+ 'main.entity_id = comment.parent_id',
$fields
)->where(
'main.order_id = ?',
diff --git a/app/code/Magento/Sales/view/frontend/email/order_new_guest.html b/app/code/Magento/Sales/view/frontend/email/order_new_guest.html
index dc3a8e9f69aca..0529c66a04d8c 100644
--- a/app/code/Magento/Sales/view/frontend/email/order_new_guest.html
+++ b/app/code/Magento/Sales/view/frontend/email/order_new_guest.html
@@ -8,7 +8,7 @@
+
+
+
+
+ - Magento\SalesGraphQl\Model\OrderItemTypeResolver
+
+
+
+
+
+
+ - Magento\SalesGraphQl\Model\InvoiceItemTypeResolver
+
+
+
+
diff --git a/app/code/Magento/SalesGraphQl/etc/schema.graphqls b/app/code/Magento/SalesGraphQl/etc/schema.graphqls
index f823c25cf2d9f..099a3ffb959c4 100644
--- a/app/code/Magento/SalesGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/SalesGraphQl/etc/schema.graphqls
@@ -2,20 +2,7 @@
# See COPYING.txt for license details.
type Query {
- customerOrders: CustomerOrders @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\Orders") @doc(description: "List of customer orders") @cache(cacheable: false)
-}
-
-type CustomerOrder @doc(description: "Order mapping fields") {
- id: Int
- increment_id: String @deprecated(reason: "Use the order_number instead.")
- order_number: String! @doc(description: "The order number")
- created_at: String
- grand_total: Float
- status: String
-}
-
-type CustomerOrders {
- items: [CustomerOrder] @doc(description: "Array of orders")
+ customerOrders: CustomerOrders @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\Orders") @deprecated(reason: "Use orders from customer instead") @cache(cacheable: false)
}
type Mutation {
@@ -33,6 +20,190 @@ type CheckoutUserInputError @doc(description:"An error encountered while adding
code: CheckoutUserInputErrorCodes! @doc(description: "Checkout-specific error code")
}
+type Customer {
+ orders (
+ filter: CustomerOrdersFilterInput @doc(description: "Defines the filter to use for searching customer orders"),
+ currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1"),
+ pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. The default value is 20"),
+ ): CustomerOrders @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\CustomerOrders") @cache(cacheable: false)
+}
+
+input CustomerOrdersFilterInput @doc(description: "Identifies the filter to use for filtering orders.") {
+ number: FilterStringTypeInput @doc(description: "Filters by order number.")
+}
+
+type CustomerOrders @doc(description: "The collection of orders that match the conditions defined in the filter") {
+ items: [CustomerOrder]! @doc(description: "An array of customer orders")
+ page_info: SearchResultPageInfo @doc(description: "An object that includes the current_page, page_info, and page_size values specified in the query")
+ total_count: Int @doc(description: "The total count of customer orders")
+}
+
+type CustomerOrder @doc(description: "Contains details about each of the customer's orders") {
+ id: ID! @doc(description: "Unique identifier for the order")
+ order_date: String! @doc(description: "The date the order was placed")
+ status: String! @doc(description: "The current status of the order")
+ number: String! @doc(description: "The order number")
+ items: [OrderItemInterface] @doc(description: "An array containing the items purchased in this order") @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\OrderItems")
+ total: OrderTotal @doc(description: "Contains details about the calculated totals for this order") @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\OrderTotal")
+ invoices: [Invoice]! @doc(description: "A list of invoices for the order") @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\Invoices")
+ shipments: [OrderShipment] @doc(description: "A list of shipments for the order")
+ payment_methods: [PaymentMethod] @doc(description: "Payment details for the order")
+ shipping_address: CustomerAddress @doc(description: "The shipping address for the order")
+ billing_address: CustomerAddress @doc(description: "The billing address for the order")
+ carrier: String @doc(description: "The shipping carrier for the order delivery")
+ shipping_method: String @doc(description: "The delivery method for the order")
+ comments: [CommentItem] @doc(description: "Comments about the order")
+ increment_id: String @deprecated(reason: "Use the id attribute instead")
+ order_number: String! @deprecated(reason: "Use the number attribute instead")
+ created_at: String @deprecated(reason: "Use the order_date attribute instead")
+ grand_total: Float @deprecated(reason: "Use the totals.grand_total attribute instead")
+}
+
+interface OrderItemInterface @doc(description: "Order item details") @typeResolver(class: "Magento\\SalesGraphQl\\Model\\OrderItemTypeResolver") {
+ id: ID! @doc(description: "The unique identifier of the order item")
+ product_name: String @doc(description: "The name of the base product")
+ product_sku: String! @doc(description: "The SKU of the base product")
+ product_url_key: String @doc(description: "URL key of the base product")
+ product_type: String @doc(description: "The type of product, such as simple, configurable, or bundle")
+ status: String @doc(description: "The status of the order item")
+ product_sale_price: Money! @doc(description: "The sale price of the base product, including selected options")
+ discounts: [Discount] @doc(description: "The final discount information for the product")
+ selected_options: [OrderItemOption] @doc(description: "The selected options for the base product, such as color or size")
+ entered_options: [OrderItemOption] @doc(description: "The entered option for the base product, such as a logo or image")
+ quantity_ordered: Float @doc(description: "The number of units ordered for this item")
+ quantity_shipped: Float @doc(description: "The number of shipped items")
+ quantity_refunded: Float @doc(description: "The number of refunded items")
+ quantity_invoiced: Float @doc(description: "The number of invoiced items")
+ quantity_canceled: Float @doc(description: "The number of canceled items")
+ quantity_returned: Float @doc(description: "The number of returned items")
+}
+
+type OrderItem implements OrderItemInterface {
+}
+
+type BundleOrderItem implements OrderItemInterface {
+ bundle_options: [ItemSelectedBundleOption] @doc(description: "A list of bundle options that are assigned to the bundle product") @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\BundleOptions")
+}
+
+type ItemSelectedBundleOption @doc(description: "A list of options of the selected bundle product") {
+ id: ID! @doc(description: "The unique identifier of the option")
+ label: String! @doc(description: "The label of the option")
+ values: [ItemSelectedBundleOptionValue] @doc(description: "A list of products that represent the values of the parent option")
+}
+
+type ItemSelectedBundleOptionValue @doc(description: "A list of values for the selected bundle product") {
+ id: ID! @doc(description: "The unique identifier of the value")
+ product_name: String! @doc(description: "The name of the child bundle product")
+ product_sku: String! @doc(description: "The SKU of the child bundle product")
+ quantity: Float! @doc(description: "Indicates how many of this bundle product were ordered")
+ price: Money! @doc(description: "The price of the child bundle product")
+}
+
+type OrderItemOption @doc(description: "Represents order item options like selected or entered") {
+ id: String! @doc(description: "The name of the option")
+ value: String! @doc(description: "The value of the option")
+}
+
+type TaxItem @doc(description: "The tax item details") {
+ amount: Money! @doc(description: "The amount of tax applied to the item")
+ title: String! @doc(description: "A title that describes the tax")
+ rate: Float! @doc(description: "The rate used to calculate the tax")
+}
+
+type OrderTotal @doc(description: "Contains details about the sales total amounts used to calculate the final price") {
+ subtotal: Money! @doc(description: "The subtotal of the order, excluding shipping, discounts, and taxes")
+ discounts: [Discount] @doc(description: "The applied discounts to the order")
+ total_tax: Money! @doc(description: "The amount of tax applied to the order")
+ taxes: [TaxItem] @doc(description: "The order tax details")
+ grand_total: Money! @doc(description: "The final total amount, including shipping, discounts, and taxes")
+ base_grand_total: Money! @doc(description: "The final base grand total amount in the base currency")
+ total_shipping: Money! @doc(description: "The shipping amount for the order")
+ shipping_handling: ShippingHandling @doc(description: "Contains details about the shipping and handling costs for the order")
+}
+
+type Invoice @doc(description: "Invoice details") {
+ id: ID! @doc(description: "The ID of the invoice, used for API purposes")
+ number: String! @doc(description: "Sequential invoice number")
+ total: InvoiceTotal @doc(description: "Invoice total amount details") @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\InvoiceTotal")
+ items: [InvoiceItemInterface] @doc(description: "Invoiced product details") @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\InvoiceItems")
+ comments: [CommentItem] @doc(description: "Comments on the invoice")
+}
+
+interface InvoiceItemInterface @doc(description: "Invoice item details") @typeResolver(class: "Magento\\SalesGraphQl\\Model\\InvoiceItemTypeResolver") {
+ id: ID! @doc(description: "The unique ID of the invoice item")
+ order_item: OrderItemInterface @doc(description: "Contains details about an individual order item")
+ product_name: String @doc(description: "The name of the base product")
+ product_sku: String! @doc(description: "The SKU of the base product")
+ product_sale_price: Money! @doc(description: "The sale price for the base product including selected options")
+ discounts: [Discount] @doc(description: "Contains information about the final discount amount for the base product, including discounts on options")
+ quantity_invoiced: Float @doc(description: "The number of invoiced items")
+}
+
+type InvoiceItem implements InvoiceItemInterface {
+}
+
+type BundleInvoiceItem implements InvoiceItemInterface{
+ bundle_options: [ItemSelectedBundleOption] @doc(description: "A list of bundle options that are assigned to the bundle product") @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\BundleOptions")
+}
+
+type InvoiceTotal @doc(description: "Contains price details from an invoice"){
+ subtotal: Money! @doc(description: "The subtotal of the invoice, excluding shipping, discounts, and taxes")
+ discounts: [Discount] @doc(description: "The applied discounts to the invoice")
+ total_tax: Money! @doc(description: "The amount of tax applied to the invoice")
+ taxes: [TaxItem] @doc(description: "The order tax details")
+ grand_total: Money! @doc(description: "The final total amount, including shipping, discounts, and taxes")
+ base_grand_total: Money! @doc(description: "The final base grand total amount in the base currency")
+ total_shipping: Money! @doc(description: "The shipping amount for the invoice")
+ shipping_handling: ShippingHandling @doc(description: "Contains details about the shipping and handling costs for the invoice")
+}
+
+type ShippingHandling @doc(description: "The Shipping handling details") {
+ total_amount: Money! @doc(description: "The total amount for shipping")
+ amount_including_tax: Money @doc(description: "The shipping amount, including tax")
+ amount_excluding_tax: Money @doc(description: "The shipping amount, excluding tax")
+ taxes: [TaxItem] @doc(description: "Contains details about taxes applied for shipping")
+ discounts: [Discount] @doc(description: "The applied discounts to the shipping")
+}
+
+type OrderShipment @doc(description: "Order shipment details") {
+ id: ID! @doc(description: "The unique ID of the shipment")
+ number: String! @doc(description: "The sequential credit shipment number")
+ tracking: [ShipmentTracking] @doc(description: "Contains shipment tracking details")
+ items: [ShipmentItem] @doc(description: "Contains items included in the shipment")
+ comments: [CommentItem] @doc(description: "Comments added to the shipment")
+}
+
+type CommentItem @doc(description: "Comment item details") {
+ timestamp: String! @doc(description: "The timestamp of the comment")
+ message: String! @doc(description: "The text of the message")
+}
+
+type ShipmentItem @doc(description: "Order shipment item details") {
+ id: ID! @doc(description: "The unique ID of the shipment item")
+ order_item: OrderItemInterface @doc(description: "The shipped order item")
+ product_name: String @doc(description: "The name of the base product")
+ product_sku: String! @doc(description: "The SKU of the base product")
+ product_sale_price: Money! @doc(description: "The sale price for the base product")
+ quantity_shipped: Float! @doc(description: "The number of shipped items")
+}
+
+type ShipmentTracking @doc(description: "Order shipment tracking details") {
+ title: String! @doc(description: "The shipment tracking title")
+ carrier: String! @doc(description: "The shipping carrier for the order delivery")
+ number: String @doc(description: "The tracking number of the order shipment")
+}
+
+type PaymentMethod @doc(description: "Contains details about the payment method used to pay for the order") {
+ name: String! @doc(description: "The label that describes the payment method")
+ type: String! @doc(description: "The payment method code that indicates how the order was paid for")
+ additional_data: [KeyValue] @doc(description: "Additional data per payment method type")
+}
+
+type KeyValue @doc(description: "The key-value type") {
+ name: String @doc(description: "The name part of the name/value pair")
+ value: String @doc(description: "The value part of the name/value pair")
+}
+
enum CheckoutUserInputErrorCodes {
REORDER_NOT_AVAILABLE
PRODUCT_NOT_FOUND
diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleDeleteAllActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleDeleteAllActionGroup.xml
new file mode 100644
index 0000000000000..85437650efc35
--- /dev/null
+++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleDeleteAllActionGroup.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+ Open Cart Price Rule grid and delete all rules one by one. Need to avoid interference with other tests that test cart price rules.
+
+
+
+
+
+
+
+ {{AdminDataGridTableSection.firstNotEmptyRow}}
+ {{AdminConfirmationModalSection.ok}}
+ {{AdminMainActionsSection.delete}}
+ {{AdminMessagesSection.success}}
+ You deleted the rule.
+
+
+
+
+
diff --git a/app/code/Magento/SampleData/README.md b/app/code/Magento/SampleData/README.md
index c71439b929013..e0666ba73fe24 100644
--- a/app/code/Magento/SampleData/README.md
+++ b/app/code/Magento/SampleData/README.md
@@ -11,7 +11,7 @@ You can deploy sample data from one of the following sources:
* From the Magento composer repository, optionally using Magento CLI
* From the Magento GitHub repository
-If your Magento code base was cloned from the `master` branch, you can use either source of the sample data. If it was cloned from the `develop` branch, use the GitHub repository and choose to get sample data modules from the `develop` branch.
+If your Magento code base was cloned from the mainline branch, you can use either source of the sample data. If it was cloned from the `develop` branch, use the GitHub repository and choose to get sample data modules from the `develop` branch.
### Deploy Sample Data from Composer Repository
@@ -46,7 +46,7 @@ Each package corresponds to a sample data module. The complete list of available
To deploy sample data from the GitHub repository:
-1. Clone sample data from `https://github.com/magento/magento2-sample-data`. If your Magento instance was cloned from the `master` branch, choose the `master` branch when cloning sample data; choose the `develop` branch if Magento was cloned from `develop`.
+1. Clone sample data from `https://github.com/magento/magento2-sample-data`. If your Magento instance was cloned from the mainline branch, choose the mainline branch when cloning sample data; choose the `develop` branch if Magento was cloned from `develop`.
2. Link the sample data and your Magento instance by running: `# php -f /dev/tools/build-sample-data.php -- --ce-source=""`
## Install Sample Data
diff --git a/app/code/Magento/Search/Model/SearchEngine/Validator.php b/app/code/Magento/Search/Model/SearchEngine/Validator.php
index f4fc8a9a62e0e..264e7c69dd520 100644
--- a/app/code/Magento/Search/Model/SearchEngine/Validator.php
+++ b/app/code/Magento/Search/Model/SearchEngine/Validator.php
@@ -22,7 +22,7 @@ class Validator implements ValidatorInterface
/**
* @var array
*/
- private $engineBlacklist = ['mysql' => 'MySQL'];
+ private $excludedEngineList = ['mysql' => 'MySQL'];
/**
* @var ValidatorInterface[]
@@ -32,16 +32,16 @@ class Validator implements ValidatorInterface
/**
* @param ScopeConfigInterface $scopeConfig
* @param array $engineValidators
- * @param array $engineBlacklist
+ * @param array $excludedEngineList
*/
public function __construct(
ScopeConfigInterface $scopeConfig,
array $engineValidators = [],
- array $engineBlacklist = []
+ array $excludedEngineList = []
) {
$this->scopeConfig = $scopeConfig;
$this->engineValidators = $engineValidators;
- $this->engineBlacklist = array_merge($this->engineBlacklist, $engineBlacklist);
+ $this->excludedEngineList = array_merge($this->excludedEngineList, $excludedEngineList);
}
/**
@@ -51,9 +51,9 @@ public function validate(): array
{
$errors = [];
$currentEngine = $this->scopeConfig->getValue('catalog/search/engine');
- if (isset($this->engineBlacklist[$currentEngine])) {
- $blacklistedEngine = $this->engineBlacklist[$currentEngine];
- $errors[] = "Your current search engine, '{$blacklistedEngine}', is not supported."
+ if (isset($this->excludedEngineList[$currentEngine])) {
+ $excludedEngine = $this->excludedEngineList[$currentEngine];
+ $errors[] = "Your current search engine, '{$excludedEngine}', is not supported."
. " You must install a supported search engine before upgrading."
. " See the System Upgrade Guide for more information.";
}
diff --git a/app/code/Magento/Search/Test/Unit/Model/SearchEngine/ValidatorTest.php b/app/code/Magento/Search/Test/Unit/Model/SearchEngine/ValidatorTest.php
index c91c0fce9dd47..cc272ccb60162 100644
--- a/app/code/Magento/Search/Test/Unit/Model/SearchEngine/ValidatorTest.php
+++ b/app/code/Magento/Search/Test/Unit/Model/SearchEngine/ValidatorTest.php
@@ -34,7 +34,7 @@ protected function setUp(): void
[
'scopeConfig' => $this->scopeConfigMock,
'engineValidators' => ['otherEngine' => $this->otherEngineValidatorMock],
- 'engineBlacklist' => ['badEngine' => 'Bad Engine']
+ 'excludedEngineList' => ['badEngine' => 'Bad Engine']
]
);
}
@@ -54,7 +54,7 @@ public function testValidateValid()
$this->assertEquals($expectedErrors, $this->validator->validate());
}
- public function testValidateBlacklist()
+ public function testValidateExcludedList()
{
$this->scopeConfigMock
->expects($this->once())
diff --git a/app/code/Magento/Security/Model/Plugin/Auth.php b/app/code/Magento/Security/Model/Plugin/Auth.php
index 833b4e4c1b774..b388ef6115867 100644
--- a/app/code/Magento/Security/Model/Plugin/Auth.php
+++ b/app/code/Magento/Security/Model/Plugin/Auth.php
@@ -35,6 +35,8 @@ public function __construct(
}
/**
+ * Add warning message if other sessions terminated
+ *
* @param \Magento\Backend\Model\Auth $authModel
* @return void
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
@@ -43,11 +45,13 @@ public function afterLogin(\Magento\Backend\Model\Auth $authModel)
{
$this->sessionsManager->processLogin();
if ($this->sessionsManager->getCurrentSession()->isOtherSessionsTerminated()) {
- $this->messageManager->addWarning(__('All other open sessions for this account were terminated.'));
+ $this->messageManager->addWarningMessage(__('All other open sessions for this account were terminated.'));
}
}
/**
+ * Handle logout process
+ *
* @param \Magento\Backend\Model\Auth $authModel
* @return void
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
diff --git a/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthTest.php b/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthTest.php
index c431f1ecda332..dd86b3b574ead 100644
--- a/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthTest.php
+++ b/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthTest.php
@@ -64,7 +64,7 @@ protected function setUp(): void
$this->messageManager = $this->getMockForAbstractClass(
ManagerInterface::class,
- ['addWarning'],
+ ['addWarningMessage'],
'',
false
);
@@ -100,7 +100,7 @@ public function testAfterLogin()
->method('isOtherSessionsTerminated')
->willReturn(true);
$this->messageManager->expects($this->once())
- ->method('addWarning')
+ ->method('addWarningMessage')
->with($warningMessage);
$this->model->afterLogin($this->authMock);
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreActionGroup.xml
new file mode 100644
index 0000000000000..4a403364a91e3
--- /dev/null
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ Switch the Storefront to the provided Store.
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Theme/Block/Html/Pager.php b/app/code/Magento/Theme/Block/Html/Pager.php
index 5798b94e31a70..764b2e9ca42f0 100644
--- a/app/code/Magento/Theme/Block/Html/Pager.php
+++ b/app/code/Magento/Theme/Block/Html/Pager.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Theme\Block\Html;
/**
@@ -466,7 +467,26 @@ public function getPageUrl($page)
*/
public function getLimitUrl($limit)
{
- return $this->getPagerUrl([$this->getLimitVarName() => $limit]);
+ return $this->getPagerUrl($this->getPageLimitParams($limit));
+ }
+
+ /**
+ * Return page limit params
+ *
+ * @param int $limit
+ * @return array
+ */
+ private function getPageLimitParams(int $limit): array
+ {
+ $data = [$this->getLimitVarName() => $limit];
+
+ $currentPage = $this->getCurrentPage();
+ $availableCount = (int) ceil($this->getTotalNum() / $limit);
+ if ($currentPage !== 1 && $availableCount < $currentPage) {
+ $data = array_merge($data, [$this->getPageVarName() => $availableCount === 1 ? null : $availableCount]);
+ }
+
+ return $data;
}
/**
diff --git a/app/code/Magento/Theme/Model/Config/Customization.php b/app/code/Magento/Theme/Model/Config/Customization.php
index 6a6872d794b1b..7430730451110 100644
--- a/app/code/Magento/Theme/Model/Config/Customization.php
+++ b/app/code/Magento/Theme/Model/Config/Customization.php
@@ -5,23 +5,34 @@
*/
namespace Magento\Theme\Model\Config;
+use Magento\Framework\App\Area;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\View\Design\Theme\ThemeProviderInterface;
+use Magento\Framework\View\Design\ThemeInterface;
+use Magento\Framework\View\DesignInterface;
+use Magento\Store\Model\Store;
+use Magento\Store\Model\StoreManagerInterface;
+use Magento\Theme\Model\ResourceModel\Theme\Collection;
+use Magento\Theme\Model\Theme\StoreThemesResolverInterface;
+use Magento\Theme\Model\Theme\StoreUserAgentThemeResolver;
+
/**
* Theme customization config model
*/
class Customization
{
/**
- * @var \Magento\Store\Model\StoreManagerInterface
+ * @var StoreManagerInterface
*/
protected $_storeManager;
/**
- * @var \Magento\Framework\View\DesignInterface
+ * @var DesignInterface
*/
protected $_design;
/**
- * @var \Magento\Framework\View\Design\Theme\ThemeProviderInterface
+ * @var ThemeProviderInterface
*/
protected $themeProvider;
@@ -40,20 +51,28 @@ class Customization
* @see self::_prepareThemeCustomizations()
*/
protected $_unassignedTheme;
+ /**
+ * @var StoreUserAgentThemeResolver|mixed|null
+ */
+ private $storeThemesResolver;
/**
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Framework\View\DesignInterface $design
- * @param \Magento\Framework\View\Design\Theme\ThemeProviderInterface $themeProvider
+ * @param StoreManagerInterface $storeManager
+ * @param DesignInterface $design
+ * @param ThemeProviderInterface $themeProvider
+ * @param StoreThemesResolverInterface|null $storeThemesResolver
*/
public function __construct(
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Framework\View\DesignInterface $design,
- \Magento\Framework\View\Design\Theme\ThemeProviderInterface $themeProvider
+ StoreManagerInterface $storeManager,
+ DesignInterface $design,
+ ThemeProviderInterface $themeProvider,
+ ?StoreThemesResolverInterface $storeThemesResolver = null
) {
$this->_storeManager = $storeManager;
$this->_design = $design;
$this->themeProvider = $themeProvider;
+ $this->storeThemesResolver = $storeThemesResolver
+ ?? ObjectManager::getInstance()->get(StoreThemesResolverInterface::class);
}
/**
@@ -93,13 +112,14 @@ public function getStoresByThemes()
{
$storesByThemes = [];
$stores = $this->_storeManager->getStores();
- /** @var $store \Magento\Store\Model\Store */
+ /** @var $store Store */
foreach ($stores as $store) {
- $themeId = $this->_getConfigurationThemeId($store);
- if (!isset($storesByThemes[$themeId])) {
- $storesByThemes[$themeId] = [];
+ foreach ($this->storeThemesResolver->getThemes($store) as $themeId) {
+ if (!isset($storesByThemes[$themeId])) {
+ $storesByThemes[$themeId] = [];
+ }
+ $storesByThemes[$themeId][] = $store;
}
- $storesByThemes[$themeId][] = $store;
}
return $storesByThemes;
}
@@ -107,8 +127,8 @@ public function getStoresByThemes()
/**
* Check if current theme has assigned to any store
*
- * @param \Magento\Framework\View\Design\ThemeInterface $theme
- * @param null|\Magento\Store\Model\Store $store
+ * @param ThemeInterface $theme
+ * @param null|Store $store
* @return bool
*/
public function isThemeAssignedToStore($theme, $store = null)
@@ -133,8 +153,8 @@ public function hasThemeAssigned()
/**
* Is theme assigned to specific store
*
- * @param \Magento\Framework\View\Design\ThemeInterface $theme
- * @param \Magento\Store\Model\Store $store
+ * @param ThemeInterface $theme
+ * @param Store $store
* @return bool
*/
protected function _isThemeAssignedToSpecificStore($theme, $store)
@@ -145,21 +165,21 @@ protected function _isThemeAssignedToSpecificStore($theme, $store)
/**
* Get configuration theme id
*
- * @param \Magento\Store\Model\Store $store
+ * @param Store $store
* @return int
*/
protected function _getConfigurationThemeId($store)
{
return $this->_design->getConfigurationDesignTheme(
- \Magento\Framework\App\Area::AREA_FRONTEND,
+ Area::AREA_FRONTEND,
['store' => $store]
);
}
/**
* Fetch theme customization and sort them out to arrays:
- * self::_assignedTheme and self::_unassignedTheme.
*
+ * Set self::_assignedTheme and self::_unassignedTheme.
* NOTE: To get into "assigned" list theme customization not necessary should be assigned to store-view directly.
* It can be set to website or as default theme and be used by store-view via config fallback mechanism.
*
@@ -167,15 +187,15 @@ protected function _getConfigurationThemeId($store)
*/
protected function _prepareThemeCustomizations()
{
- /** @var \Magento\Theme\Model\ResourceModel\Theme\Collection $themeCollection */
- $themeCollection = $this->themeProvider->getThemeCustomizations(\Magento\Framework\App\Area::AREA_FRONTEND);
+ /** @var Collection $themeCollection */
+ $themeCollection = $this->themeProvider->getThemeCustomizations(Area::AREA_FRONTEND);
$assignedThemes = $this->getStoresByThemes();
$this->_assignedTheme = [];
$this->_unassignedTheme = [];
- /** @var $theme \Magento\Framework\View\Design\ThemeInterface */
+ /** @var $theme ThemeInterface */
foreach ($themeCollection as $theme) {
if (isset($assignedThemes[$theme->getId()])) {
$theme->setAssignedStores($assignedThemes[$theme->getId()]);
diff --git a/app/code/Magento/Theme/Model/Theme/StoreDefaultThemeResolver.php b/app/code/Magento/Theme/Model/Theme/StoreDefaultThemeResolver.php
new file mode 100644
index 0000000000000..26bd5604294d1
--- /dev/null
+++ b/app/code/Magento/Theme/Model/Theme/StoreDefaultThemeResolver.php
@@ -0,0 +1,90 @@
+design = $design;
+ $this->themeCollectionFactory = $themeCollectionFactory;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getThemes(StoreInterface $store): array
+ {
+ $theme = $this->design->getConfigurationDesignTheme(
+ Area::AREA_FRONTEND,
+ ['store' => $store]
+ );
+ $themes = [];
+ if ($theme) {
+ if (!is_numeric($theme)) {
+ $registeredThemes = $this->getRegisteredThemes();
+ if (isset($registeredThemes[$theme])) {
+ $themes[] = $registeredThemes[$theme]->getId();
+ }
+ } else {
+ $themes[] = $theme;
+ }
+ }
+ return $themes;
+ }
+
+ /**
+ * Get system registered themes.
+ *
+ * @return ThemeInterface[]
+ */
+ private function getRegisteredThemes(): array
+ {
+ if ($this->registeredThemes === null) {
+ $this->registeredThemes = [];
+ /** @var \Magento\Theme\Model\ResourceModel\Theme\Collection $collection */
+ $collection = $this->themeCollectionFactory->create();
+ $themes = $collection->loadRegisteredThemes();
+ /** @var ThemeInterface $theme */
+ foreach ($themes as $theme) {
+ $this->registeredThemes[$theme->getCode()] = $theme;
+ }
+ }
+ return $this->registeredThemes;
+ }
+}
diff --git a/app/code/Magento/Theme/Model/Theme/StoreThemesResolver.php b/app/code/Magento/Theme/Model/Theme/StoreThemesResolver.php
new file mode 100644
index 0000000000000..5be86c08f7c51
--- /dev/null
+++ b/app/code/Magento/Theme/Model/Theme/StoreThemesResolver.php
@@ -0,0 +1,57 @@
+resolvers = $resolvers;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getThemes(StoreInterface $store): array
+ {
+ $themes = [];
+ foreach ($this->resolvers as $resolver) {
+ foreach ($resolver->getThemes($store) as $theme) {
+ $themes[] = $theme;
+ }
+ }
+ return array_values(array_unique($themes));
+ }
+}
diff --git a/app/code/Magento/Theme/Model/Theme/StoreThemesResolverInterface.php b/app/code/Magento/Theme/Model/Theme/StoreThemesResolverInterface.php
new file mode 100644
index 0000000000000..bb2cd73300c02
--- /dev/null
+++ b/app/code/Magento/Theme/Model/Theme/StoreThemesResolverInterface.php
@@ -0,0 +1,24 @@
+scopeConfig = $scopeConfig;
+ $this->serializer = $serializer;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getThemes(StoreInterface $store): array
+ {
+ $config = $this->scopeConfig->getValue(
+ self::XML_PATH_THEME_USER_AGENT,
+ ScopeInterface::SCOPE_STORE,
+ $store
+ );
+ $rules = $config ? $this->serializer->unserialize($config) : [];
+ $themes = [];
+ if ($rules) {
+ $themes = array_values(array_unique(array_column($rules, 'value')));
+ }
+ return $themes;
+ }
+}
diff --git a/app/code/Magento/Theme/Test/Unit/Block/Html/PagerTest.php b/app/code/Magento/Theme/Test/Unit/Block/Html/PagerTest.php
index ac16c56b17f1b..fd0ef1db0219a 100644
--- a/app/code/Magento/Theme/Test/Unit/Block/Html/PagerTest.php
+++ b/app/code/Magento/Theme/Test/Unit/Block/Html/PagerTest.php
@@ -91,6 +91,60 @@ public function testGetPages(): void
$this->assertEquals($expectedPages, $this->pager->getPages());
}
+ /**
+ * Test get limit url.
+ *
+ * @dataProvider limitUrlDataProvider
+ *
+ * @param int $page
+ * @param int $size
+ * @param int $limit
+ * @param array $expectedParams
+ * @return void
+ */
+ public function testGetLimitUrl(int $page, int $size, int $limit, array $expectedParams): void
+ {
+ $expectedArray = [
+ '_current' => true,
+ '_escape' => true,
+ '_use_rewrite' => true,
+ '_fragment' => null,
+ '_query' => $expectedParams,
+ ];
+
+ $collectionMock = $this->createMock(Collection::class);
+ $collectionMock->expects($this->once())
+ ->method('getCurPage')
+ ->willReturn($page);
+ $collectionMock->expects($this->once())
+ ->method('getSize')
+ ->willReturn($size);
+ $this->setCollectionProperty($collectionMock);
+
+ $this->urlBuilderMock->expects($this->once())
+ ->method('getUrl')
+ ->with('*/*/*', $expectedArray);
+
+ $this->pager->getLimitUrl($limit);
+ }
+
+ /**
+ * DataProvider for testGetLimitUrl
+ *
+ * @return array
+ */
+ public function limitUrlDataProvider(): array
+ {
+ return [
+ [2, 21, 10, ['limit' => 10]],
+ [3, 21, 10, ['limit' => 10]],
+ [2, 21, 20, ['limit' => 20]],
+ [3, 21, 50, ['limit' => 50, 'p' => null]],
+ [2, 11, 20, ['limit' => 20, 'p' => null]],
+ [4, 40, 20, ['limit' => 20, 'p' => 2]],
+ ];
+ }
+
/**
* Set Collection
*
diff --git a/app/code/Magento/Theme/Test/Unit/Model/Config/CustomizationTest.php b/app/code/Magento/Theme/Test/Unit/Model/Config/CustomizationTest.php
index 82678d4b4277d..438853b9935e6 100644
--- a/app/code/Magento/Theme/Test/Unit/Model/Config/CustomizationTest.php
+++ b/app/code/Magento/Theme/Test/Unit/Model/Config/CustomizationTest.php
@@ -13,9 +13,10 @@
use Magento\Framework\App\Area;
use Magento\Framework\DataObject;
use Magento\Framework\View\DesignInterface;
+use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Theme\Model\Config\Customization;
-use Magento\Theme\Model\ResourceModel\Theme\Collection;
+use Magento\Theme\Model\Theme\StoreThemesResolverInterface;
use Magento\Theme\Model\Theme\ThemeProvider;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -32,47 +33,37 @@ class CustomizationTest extends TestCase
*/
protected $designPackage;
- /**
- * @var Collection
- */
- protected $themeCollection;
-
/**
* @var Customization
*/
protected $model;
/**
- * @var ThemeProvider|\PHPUnit\Framework\MockObject_MockBuilder
+ * @var ThemeProvider|MockObject
*/
protected $themeProviderMock;
+ /**
+ * @var StoreThemesResolverInterface|MockObject
+ */
+ private $storeThemesResolver;
protected function setUp(): void
{
- $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class)
- ->getMock();
- $this->designPackage = $this->getMockBuilder(DesignInterface::class)
- ->getMock();
- $this->themeCollection = $this->getMockBuilder(Collection::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $collectionFactory = $this->getMockBuilder(\Magento\Theme\Model\ResourceModel\Theme\CollectionFactory::class)
- ->disableOriginalConstructor()
- ->setMethods(['create'])
- ->getMock();
-
- $collectionFactory->expects($this->any())->method('create')->willReturn($this->themeCollection);
+ $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class)->getMock();
+ $this->designPackage = $this->getMockBuilder(DesignInterface::class)->getMock();
$this->themeProviderMock = $this->getMockBuilder(ThemeProvider::class)
->disableOriginalConstructor()
->setMethods(['getThemeCustomizations', 'getThemeByFullPath'])
->getMock();
+ $this->storeThemesResolver = $this->createMock(StoreThemesResolverInterface::class);
+
$this->model = new Customization(
$this->storeManager,
$this->designPackage,
- $this->themeProviderMock
+ $this->themeProviderMock,
+ $this->storeThemesResolver
);
}
@@ -84,13 +75,15 @@ protected function setUp(): void
*/
public function testGetAssignedThemeCustomizations()
{
- $this->designPackage->expects($this->once())
- ->method('getConfigurationDesignTheme')
- ->willReturn($this->getAssignedTheme()->getId());
-
+ $store = $this->getStore();
$this->storeManager->expects($this->once())
->method('getStores')
- ->willReturn([$this->getStore()]);
+ ->willReturn([$store]);
+
+ $this->storeThemesResolver->expects($this->once())
+ ->method('getThemes')
+ ->with($store)
+ ->willReturn([$this->getAssignedTheme()->getId()]);
$this->themeProviderMock->expects($this->once())
->method('getThemeCustomizations')
@@ -108,13 +101,15 @@ public function testGetAssignedThemeCustomizations()
*/
public function testGetUnassignedThemeCustomizations()
{
+ $store = $this->getStore();
$this->storeManager->expects($this->once())
->method('getStores')
- ->willReturn([$this->getStore()]);
+ ->willReturn([$store]);
- $this->designPackage->expects($this->once())
- ->method('getConfigurationDesignTheme')
- ->willReturn($this->getAssignedTheme()->getId());
+ $this->storeThemesResolver->expects($this->once())
+ ->method('getThemes')
+ ->with($store)
+ ->willReturn([$this->getAssignedTheme()->getId()]);
$this->themeProviderMock->expects($this->once())
->method('getThemeCustomizations')
@@ -131,13 +126,15 @@ public function testGetUnassignedThemeCustomizations()
*/
public function testGetStoresByThemes()
{
+ $store = $this->getStore();
$this->storeManager->expects($this->once())
->method('getStores')
- ->willReturn([$this->getStore()]);
+ ->willReturn([$store]);
- $this->designPackage->expects($this->once())
- ->method('getConfigurationDesignTheme')
- ->willReturn($this->getAssignedTheme()->getId());
+ $this->storeThemesResolver->expects($this->once())
+ ->method('getThemes')
+ ->with($store)
+ ->willReturn([$this->getAssignedTheme()->getId()]);
$stores = $this->model->getStoresByThemes();
$this->assertArrayHasKey($this->getAssignedTheme()->getId(), $stores);
@@ -148,15 +145,17 @@ public function testGetStoresByThemes()
* @covers \Magento\Theme\Model\Config\Customization::_getConfigurationThemeId
* @covers \Magento\Theme\Model\Config\Customization::__construct
*/
- public function testIsThemeAssignedToDefaultStore()
+ public function testIsThemeAssignedToAnyStore()
{
+ $store = $this->getStore();
$this->storeManager->expects($this->once())
->method('getStores')
- ->willReturn([$this->getStore()]);
+ ->willReturn([$store]);
- $this->designPackage->expects($this->once())
- ->method('getConfigurationDesignTheme')
- ->willReturn($this->getAssignedTheme()->getId());
+ $this->storeThemesResolver->expects($this->once())
+ ->method('getThemes')
+ ->with($store)
+ ->willReturn([$this->getAssignedTheme()->getId()]);
$this->themeProviderMock->expects($this->once())
->method('getThemeCustomizations')
@@ -198,10 +197,10 @@ protected function getUnassignedTheme()
}
/**
- * @return DataObject
+ * @return StoreInterface|MockObject
*/
protected function getStore()
{
- return new DataObject(['id' => 55]);
+ return $this->createConfiguredMock(StoreInterface::class, ['getId' => 55]);
}
}
diff --git a/app/code/Magento/Theme/Test/Unit/Model/Theme/StoreDefaultThemeResolverTest.php b/app/code/Magento/Theme/Test/Unit/Model/Theme/StoreDefaultThemeResolverTest.php
new file mode 100644
index 0000000000000..939b47a42ce85
--- /dev/null
+++ b/app/code/Magento/Theme/Test/Unit/Model/Theme/StoreDefaultThemeResolverTest.php
@@ -0,0 +1,115 @@
+createMock(CollectionFactory::class);
+ $this->design = $this->createMock(DesignInterface::class);
+ $this->model = new StoreDefaultThemeResolver(
+ $themeCollectionFactory,
+ $this->design
+ );
+ $registeredThemes = [];
+ $registeredThemes[] = $this->createConfiguredMock(
+ ThemeInterface::class,
+ [
+ 'getId' => 1,
+ 'getCode' => 'Magento/luma',
+ ]
+ );
+ $registeredThemes[] = $this->createConfiguredMock(
+ ThemeInterface::class,
+ [
+ 'getId' => 2,
+ 'getCode' => 'Magento/blank',
+ ]
+ );
+ $collection = $this->createMock(Collection::class);
+ $collection->method('getIterator')
+ ->willReturn(new ArrayIterator($registeredThemes));
+ $collection->method('loadRegisteredThemes')
+ ->willReturnSelf();
+ $themeCollectionFactory->method('create')
+ ->willReturn($collection);
+ }
+
+ /**
+ * Test that method returns default theme associated to given store.
+ *
+ * @param string|null $defaultTheme
+ * @param array $expected
+ * @dataProvider getThemesDataProvider
+ */
+ public function testGetThemes(?string $defaultTheme, array $expected): void
+ {
+ $store = $this->createMock(StoreInterface::class);
+ $this->design->expects($this->once())
+ ->method('getConfigurationDesignTheme')
+ ->with(
+ Area::AREA_FRONTEND,
+ ['store' => $store]
+ )
+ ->willReturn($defaultTheme);
+ $this->assertEquals($expected, $this->model->getThemes($store));
+ }
+
+ /**
+ * @return array
+ */
+ public function getThemesDataProvider(): array
+ {
+ return [
+ [
+ null,
+ []
+ ],
+ [
+ '1',
+ [1]
+ ],
+ [
+ 'Magento/blank',
+ [2]
+ ],
+ [
+ 'Magento/theme',
+ []
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/Theme/Test/Unit/Model/Theme/StoreThemesResolverTest.php b/app/code/Magento/Theme/Test/Unit/Model/Theme/StoreThemesResolverTest.php
new file mode 100644
index 0000000000000..b80ec4ae83887
--- /dev/null
+++ b/app/code/Magento/Theme/Test/Unit/Model/Theme/StoreThemesResolverTest.php
@@ -0,0 +1,115 @@
+resolvers = [];
+ $this->resolvers[] = $this->createMock(StoreThemesResolverInterface::class);
+ $this->resolvers[] = $this->createMock(StoreThemesResolverInterface::class);
+ $this->resolvers[] = $this->createMock(StoreThemesResolverInterface::class);
+ $this->model = new StoreThemesResolver($this->resolvers);
+ }
+
+ /**
+ * Test that constructor SHOULD throw an exception when resolver is not instance of StoreThemesResolverInterface.
+ */
+ public function testInvalidConstructorArguments(): void
+ {
+ $resolver = $this->createMock(StoreInterface::class);
+ $this->expectExceptionObject(
+ new \InvalidArgumentException(
+ sprintf(
+ 'Instance of %s is expected, got %s instead.',
+ StoreThemesResolverInterface::class,
+ get_class($resolver)
+ )
+ )
+ );
+ $this->model = new StoreThemesResolver(
+ [
+ $resolver
+ ]
+ );
+ }
+
+ /**
+ * Test that method returns aggregated themes from resolvers
+ *
+ * @param array $themes
+ * @param array $expected
+ * @dataProvider getThemesDataProvider
+ */
+ public function testGetThemes(array $themes, array $expected): void
+ {
+ $store = $this->createMock(StoreInterface::class);
+ foreach ($this->resolvers as $key => $resolver) {
+ $resolver->expects($this->once())
+ ->method('getThemes')
+ ->willReturn($themes[$key]);
+ }
+ $this->assertEquals($expected, $this->model->getThemes($store));
+ }
+
+ /**
+ * @return array
+ */
+ public function getThemesDataProvider(): array
+ {
+ return [
+ [
+ [
+ [],
+ [],
+ []
+ ],
+ []
+ ],
+ [
+ [
+ ['1'],
+ [],
+ ['1']
+ ],
+ ['1']
+ ],
+ [
+ [
+ ['1'],
+ ['2'],
+ ['1']
+ ],
+ ['1', '2']
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/Theme/Test/Unit/Model/Theme/StoreUserAgentThemeResolverTest.php b/app/code/Magento/Theme/Test/Unit/Model/Theme/StoreUserAgentThemeResolverTest.php
new file mode 100644
index 0000000000000..1ef4b17ca6562
--- /dev/null
+++ b/app/code/Magento/Theme/Test/Unit/Model/Theme/StoreUserAgentThemeResolverTest.php
@@ -0,0 +1,105 @@
+scopeConfig = $this->createMock(ScopeConfigInterface::class);
+ $this->serializer = new Json();
+ $this->model = new StoreUserAgentThemeResolver(
+ $this->scopeConfig,
+ $this->serializer
+ );
+ }
+
+ /**
+ * Test that method returns user-agent rules associated themes.
+ *
+ * @param array|null $config
+ * @param array $expected
+ * @dataProvider getThemesDataProvider
+ */
+ public function testGetThemes(?array $config, array $expected): void
+ {
+ $store = $this->createMock(StoreInterface::class);
+ $this->scopeConfig->expects($this->once())
+ ->method('getValue')
+ ->with('design/theme/ua_regexp', ScopeInterface::SCOPE_STORE, $store)
+ ->willReturn($config !== null ? $this->serializer->serialize($config) : $config);
+ $this->assertEquals($expected, $this->model->getThemes($store));
+ }
+
+ /**
+ * @return array
+ */
+ public function getThemesDataProvider(): array
+ {
+ return [
+ [
+ null,
+ []
+ ],
+ [
+ [],
+ []
+ ],
+ [
+ [
+ [
+ 'search' => '\/Chrome\/i',
+ 'regexp' => '\/Chrome\/i',
+ 'value' => '1',
+ ],
+ ],
+ ['1']
+ ],
+ [
+ [
+ [
+ 'search' => '\/Chrome\/i',
+ 'regexp' => '\/Chrome\/i',
+ 'value' => '1',
+ ],
+ [
+ 'search' => '\/mozila\/i',
+ 'regexp' => '\/mozila\/i',
+ 'value' => '2',
+ ],
+ ],
+ ['1', '2']
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/Theme/etc/di.xml b/app/code/Magento/Theme/etc/di.xml
index 921e6bfc6ecf1..c4da1f860870e 100644
--- a/app/code/Magento/Theme/etc/di.xml
+++ b/app/code/Magento/Theme/etc/di.xml
@@ -18,6 +18,7 @@
+
Magento\Framework\App\Cache\Type\Config
@@ -309,4 +310,12 @@
configured_design_cache
+
+
+
+ - Magento\Theme\Model\Theme\StoreDefaultThemeResolver
+ - Magento\Theme\Model\Theme\StoreUserAgentThemeResolver
+
+
+
diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/lists/editor_plugin_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/lists/editor_plugin_src.js
index a3bd16cab718e..2119426a5c157 100644
--- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/lists/editor_plugin_src.js
+++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/lists/editor_plugin_src.js
@@ -82,9 +82,9 @@
}
}
- function attemptMerge(e1, e2, differentStylesMasterElement, mergeParagraphs) {
- if (canMerge(e1, e2, !!differentStylesMasterElement, mergeParagraphs)) {
- return merge(e1, e2, differentStylesMasterElement);
+ function attemptMerge(e1, e2, differentStylesMainElement, mergeParagraphs) {
+ if (canMerge(e1, e2, !!differentStylesMainElement, mergeParagraphs)) {
+ return merge(e1, e2, differentStylesMainElement);
} else if (e1 && e1.tagName === 'LI' && isList(e2)) {
// Fix invalidly nested lists.
e1.appendChild(e2);
@@ -112,7 +112,7 @@
return firstChild && lastChild && firstChild === lastChild && isList(firstChild);
}
- function merge(e1, e2, masterElement) {
+ function merge(e1, e2, mainElement) {
var lastOriginal = skipWhitespaceNodesBackwards(e1.lastChild), firstNew = skipWhitespaceNodesForwards(e2.firstChild);
if (e1.tagName === 'P') {
e1.appendChild(e1.ownerDocument.createElement('br'));
@@ -120,8 +120,8 @@
while (e2.firstChild) {
e1.appendChild(e2.firstChild);
}
- if (masterElement) {
- e1.style.listStyleType = masterElement.style.listStyleType;
+ if (mainElement) {
+ e1.style.listStyleType = mainElement.style.listStyleType;
}
e2.parentNode.removeChild(e2);
attemptMerge(lastOriginal, firstNew, false);
@@ -164,7 +164,7 @@
}
return false;
}
-
+
// If we are at the end of a paragraph in a list item, pressing enter should create a new list item instead of a new paragraph.
function isEndOfParagraph() {
var node = ed.selection.getNode();
@@ -241,7 +241,7 @@
Event.cancel(e);
}
}
-
+
// Creates a new list item after the current selection's list item parent
function createNewLi(ed, e) {
if (state == LIST_PARAGRAPH) {
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/AssertAdminTargetPathInUrlRewriteGrigActionGroup.xml b/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/AssertAdminTargetPathInUrlRewriteGrigActionGroup.xml
new file mode 100644
index 0000000000000..a409860811837
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/AssertAdminTargetPathInUrlRewriteGrigActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ Assert the target path is shown in the URL Rewrite grid.
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/AssertAdminTargetPathIsNotFoundInUrlRewriteGrigActionGroup.xml b/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/AssertAdminTargetPathIsNotFoundInUrlRewriteGrigActionGroup.xml
new file mode 100644
index 0000000000000..739675ba264ea
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/AssertAdminTargetPathIsNotFoundInUrlRewriteGrigActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ Assert the target path is not shown in the URL Rewrite grid.
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminCheckUrlRewritesMultipleStoreviewsProductImportTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminCheckUrlRewritesMultipleStoreviewsProductImportTest.xml
index 4e46ed8e4fc79..3b140aed5f572 100644
--- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminCheckUrlRewritesMultipleStoreviewsProductImportTest.xml
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminCheckUrlRewritesMultipleStoreviewsProductImportTest.xml
@@ -47,84 +47,103 @@
+
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminUrlRewriteMultipleStoreviewsProductImportWithConfigTurnedOffTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminUrlRewriteMultipleStoreviewsProductImportWithConfigTurnedOffTest.xml
index 1d604ef7648dc..7d5bb78f3b3f9 100644
--- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminUrlRewriteMultipleStoreviewsProductImportWithConfigTurnedOffTest.xml
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest/AdminUrlRewriteMultipleStoreviewsProductImportWithConfigTurnedOffTest.xml
@@ -64,9 +64,9 @@
-
-
-
+
+
+
@@ -74,82 +74,114 @@
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/User/Model/Notificator.php b/app/code/Magento/User/Model/Notificator.php
index 3a5522db4c533..3e36cd1387e39 100644
--- a/app/code/Magento/User/Model/Notificator.php
+++ b/app/code/Magento/User/Model/Notificator.php
@@ -107,6 +107,7 @@ public function sendForgotPassword(UserInterface $user): void
$this->sendNotification(
'admin/emails/forgot_email_template',
[
+ 'username' => $user->getFirstName().' '.$user->getLastName(),
'user' => $user,
'store' => $this->storeManager->getStore(
Store::DEFAULT_STORE_ID
diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/LoginNewUserActionGroup.xml
similarity index 83%
rename from app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserActionGroup.xml
rename to app/code/Magento/User/Test/Mftf/ActionGroup/LoginNewUserActionGroup.xml
index 4049e60e83455..d41ed63678783 100644
--- a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserActionGroup.xml
+++ b/app/code/Magento/User/Test/Mftf/ActionGroup/LoginNewUserActionGroup.xml
@@ -5,10 +5,8 @@
* See COPYING.txt for license details.
*/
-->
-
-
-
+
+
Goes to the Backend Admin Login page. Fill Username and Password. Click on Sign In.
diff --git a/app/code/Magento/User/Test/Mftf/Test/AdminCreateActiveUserEntityTest.xml b/app/code/Magento/User/Test/Mftf/Test/AdminCreateActiveUserEntityTest.xml
index 668ae550f1b3d..ba8d6ef433e13 100644
--- a/app/code/Magento/User/Test/Mftf/Test/AdminCreateActiveUserEntityTest.xml
+++ b/app/code/Magento/User/Test/Mftf/Test/AdminCreateActiveUserEntityTest.xml
@@ -32,7 +32,7 @@
-
+
diff --git a/app/code/Magento/User/Test/Mftf/Test/AdminCreateInactiveUserEntityTest.xml b/app/code/Magento/User/Test/Mftf/Test/AdminCreateInactiveUserEntityTest.xml
index 23a30246bd999..c26821d5be4b2 100644
--- a/app/code/Magento/User/Test/Mftf/Test/AdminCreateInactiveUserEntityTest.xml
+++ b/app/code/Magento/User/Test/Mftf/Test/AdminCreateInactiveUserEntityTest.xml
@@ -20,7 +20,7 @@
-
+
@@ -29,7 +29,7 @@
-
+
diff --git a/app/code/Magento/User/view/adminhtml/email/password_reset_confirmation.html b/app/code/Magento/User/view/adminhtml/email/password_reset_confirmation.html
index dacfa640464a3..42240bff3b8db 100644
--- a/app/code/Magento/User/view/adminhtml/email/password_reset_confirmation.html
+++ b/app/code/Magento/User/view/adminhtml/email/password_reset_confirmation.html
@@ -4,16 +4,17 @@
* See COPYING.txt for license details.
*/
-->
-
+
-{{trans "%name," name=$user.name}}
+{{trans "%name," name=$username}}
{{trans "There was recently a request to change the password for your account."}}
diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml
new file mode 100644
index 0000000000000..af229b3507077
--- /dev/null
+++ b/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDisabledCustomerWishlistFunctionalityTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDisabledCustomerWishlistFunctionalityTest.xml
new file mode 100644
index 0000000000000..17d3ff1009b9b
--- /dev/null
+++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDisabledCustomerWishlistFunctionalityTest.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistWithMoreThanMaximumAllowedEmailsQtyTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistWithMoreThanMaximumAllowedEmailsQtyTest.xml
new file mode 100644
index 0000000000000..7ec06e3f3cf4d
--- /dev/null
+++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistWithMoreThanMaximumAllowedEmailsQtyTest.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistWithNotValidEmailAddressTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistWithNotValidEmailAddressTest.xml
index 20881fa64f8f8..0438a1b58e771 100644
--- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistWithNotValidEmailAddressTest.xml
+++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistWithNotValidEmailAddressTest.xml
@@ -14,6 +14,7 @@
+
diff --git a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less
index f57420deb621d..4b48bbe99ced2 100644
--- a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less
+++ b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less
@@ -457,11 +457,26 @@
.action {
&.delete {
&:extend(.abs-remove-button-for-blocks all);
- line-height: unset;
position: absolute;
right: 0;
top: -1px;
- width: auto;
+ }
+ }
+
+ .block-wishlist {
+ .action {
+ &.delete {
+ line-height: unset;
+ width: auto;
+ }
+ }
+ }
+
+ .block-compare {
+ .action {
+ &.delete {
+ right: initial;
+ }
}
}
@@ -814,6 +829,7 @@
&:extend(.abs-remove-button-for-blocks all);
left: -6px;
position: absolute;
+ right: 0;
top: 0;
}
diff --git a/app/design/frontend/Magento/blank/Magento_Newsletter/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Newsletter/web/css/source/_module.less
index 09759d95c4b10..8434812f20719 100644
--- a/app/design/frontend/Magento/blank/Magento_Newsletter/web/css/source/_module.less
+++ b/app/design/frontend/Magento/blank/Magento_Newsletter/web/css/source/_module.less
@@ -82,6 +82,10 @@
.field {
margin-right: 5px;
+ &.newsletter {
+ max-width: 220px;
+ }
+
.control {
width: 100%;
}
diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less
index d0b7aa1523ad6..e205b20efd17c 100644
--- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less
@@ -998,6 +998,15 @@
}
}
}
+
+ .block-compare {
+ .action {
+ &.delete {
+ left: 0;
+ right: initial;
+ }
+ }
+ }
}
}
@@ -1005,6 +1014,7 @@
.compare.wrapper {
display: none;
}
+
.catalog-product_compare-index {
.columns {
.column {
diff --git a/app/design/frontend/Magento/luma/Magento_Newsletter/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Newsletter/web/css/source/_module.less
index a72f31d72ce48..21ed451a69d10 100644
--- a/app/design/frontend/Magento/luma/Magento_Newsletter/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_Newsletter/web/css/source/_module.less
@@ -81,6 +81,10 @@
.block.newsletter {
max-width: 44%;
width: max-content;
+
+ .field.newsletter {
+ max-width: 220px;
+ }
.form.subscribe {
> .field,
diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/order_new_guest.html b/app/design/frontend/Magento/luma/Magento_Sales/email/order_new_guest.html
index 024f6daf76ace..e51b952281ed5 100644
--- a/app/design/frontend/Magento/luma/Magento_Sales/email/order_new_guest.html
+++ b/app/design/frontend/Magento/luma/Magento_Sales/email/order_new_guest.html
@@ -8,7 +8,7 @@